HTML Panels Tips: #13 Automate ZXP Packaging with Gulp.js

Davide Barranca —  — 12 Comments

As a HTML Panels Tips: #10 Packaging / ZXP Installers follow-up, in this tip I will show you how to automate (i.e. make less error prone) the utterly annoying job of building ZXP installers: i.e. with the help of Gulp.js, a task runner based on Node.js.


If you come from Photoshop scripting like me, you might be pretty green when it comes to the plethora of tools that web developers are used to. Since they often need to perform repetitive tasks (such as compile LESS/SASS stylesheets, minify CSS, lint, uglify and concatenate JS, rename and move files, etc.) some tools known as build tools or task runners appeared – a couple of remarkable ones being Grunt and Gulp.

They do basically the same thing: Grunt is based on configuration, Gulp is (newer, and) code based. If the previous sentence makes no sense to you it doesn’t matter too much – if you want to learn more about task runners I strongly suggest you to watch this screencast made by the great and prolific @sayanee_ from the Build Podcast series (60 free videos about all kind of tech tools). Amazing resource, really.

I won’t dig deeper on Gulp here (also because I’m not a task runner expert myself), I will just share the particular code I’m using and that proved to work, explaining what each part does and trying to show a glimpse of the big picture. Hopefully, my workflow is simple enough for you to tweak it to fit your own needs.


Gulp – like tons of useful stuff – is based on Node.js (a platform based on Google Chrome runtimes), so you’d better install it first downloading from the Node website if you haven’t already done that.

Then, you need to know that there are about 88 thousands packages extending Node – which are managed by npm, the Node Package Manager. Npm is automatically installed alongside Node.js.

You must have Gulp installed globally (i.e. not only in your project directory but available everywhere), so fire the Terminal and:

Enter your user password when requested (sudo is a mandatory step on OSX as far as I know – I can’t say if it’s needed on Windows shell too).

Folders’ structure

Folder structure

I will package a hybrid extension – the very same I showed in HTML Panels Tips: #10 Packaging / ZXP Installers. That is, an installer that deploys an HTML Panel, a Mac and Windows specific plugin files and a shared Photoshop script. Please refer to the original post for information about the manual process – here I’ll just automate it.

The folder hierarchy is as follows:

  • src/ contains all the source code; build/ is empty and will contain just the final installer.
  • The source for the HTML panel is the src/com.example.myExtension folder.
  • The src/MXI folder gathers all the assets needed to pack the hybrid extension:
    • src/MXI/com.example.myExtension.mxi is the configuration file (info here).
    • src/MXI/HTML is empty but will contain the ZXP of the HTML panel.
    • src/MXI/MACsrc/MXI/WIN and src/MXI/SCRIPT are the plugins and scripts needed by the extension.
  • Finally, ucf.jar, ZXPSignCmd and myCertificate.p12 are the command line tools and the signing certificate that you should well know about.

I’ve left alone the package.json and gulpfile.js files, which I will disclose in a moment.

Setting up the project

You can either create yourself the package.json file with the following content:

Or let npm do it for you: cd into the project directory (the root), and

The Node Package Manager will initialise a project asking you few questions; as follows my answers (when there’s none, I just hit enter to confirm the suggested defaults):

One way or the other, a package.json file should be there. Now let’s install (locally – i.e. in this very folder) gulp:

We need two gulp plugins: gulp-clean is for deleting files:

while gulp-shell is a command line interface:

The --save-dev flag instructs npm to add these ones as project’s dependencies, automatically modifying the json file for you:

You’ll see that npm has installed these dependencies in a node_modules folder.


Here comes the fun: the gulpfile.js is where you start defining tasks, that you’ll be calling from the Terminal. Before digging into the actual, entire workflow, let’s familiarize with tasks – here’s a simple one:

You first require gulp, then call the gulp.task function. The first parameter is the task name as a string (here 'clean-all'), the second is the function that will return a Stream (if you feel inclined, find here info about Streams).

Don’t worry too much about that: what you need to know is just that you usually define a gulp source (here every zxp file from any subdirectory) and you pipe that to the clean function – which deletes files. The { read: false } is a configuration object that instructs gulp not to read them, to speed up the process.

How do you call that task? In the Terminal (root folder):

And presto! all the ZXPs that might have been around are gone.

Here is another useful task – it uses the gulp-shell plugin to call the ZXPSignCmd command line and actually pack files:

If you run in the Terminal:

it is like if you had typed the whole, long ZXPSignCmd string yourself. Hopefully you start appreciating the power of task runners now.

Combining tasks…

A task can rely on other tasks – dependencies are defined into an array of tasks:

In the above example, the 'clean-all' task is performed first, then the 'pack-html' task runs.

If you have multiple dependencies (that is: your task needs to run after several other tasks) you can define them this way:

Mind you: while the 'test-task' actually runs after 'first-dep' and 'second-dep', you cannot be sure that 'first-dep' and 'second-dep' are executed in this very order! Dependencies are processed asynchronously'second-dep' may finish before 'first-dep'.

… synchronously!

But in the ZXP automation we need to be sure that a task is done before the next one starts – i.e. the whole thing must be synchronous. There are few ways to trick gulp into this behaviour: returning a stream, defining a callback, using promises and – the easiest one and therefore my preferred – chaining dependencies:

  • Task #4 depends on Task #3
  • Task #3 depends on Task #2
  • Task #2 depends on Task #1

You run Task #4 and in fact the whole sequence is #1 > #2 > #3 > #4.

There might be more elegant ways to get there, but this approach has the advantage of being easily testable: if the result isn’t what you’d expect, just run Task #3 to test the gulpfile up to that point; if it’s still broken, try Task #2, and so on, until you find the bugged task.

Complete gulpfile.js

Given the folder structure I’ve outlined, the entire gulpfile is as follows:

Hopefully the code and the comments are self-explanatory. Few caveats that might save you some search time on Google:

  • './**/*.zxp' is a way to mean: every zxp file in every subfolder of the root (being the root './').
  • For a reason that I don’t know, a semicolon after the shell.task() breaks gulp.
  • { read: false } speeds up the process when you don’t need gulp to read files.
  • { base: './src/' } (as used in task #2) tells gulp to use ./src/ as a base for the relative paths. If you omit it or set it to the root './', you’ll end up with /src/MXI/HTML/src/com.example.myExtension.zxp (and extra /src/) and not /src/MXI/HTML/com.example.myExtension.zxp which is what you actually want.

This tip just scratches the surface of gulp.js as a task runner for CC Extensions – I hope to have time to dig deeper and share my findings again – feel free to add yours in the comments!

Print Friendly

12 responses to HTML Panels Tips: #13 Automate ZXP Packaging with Gulp.js

  1. Great article! thank you for sharing your knowledge and experience!
    Now all we need is a yeoman panel-generator for the folder structure
    (or even the panel boilerplate code) and we will never work hard again 🙂

    • Thanks Nir!
      In fact I’ve been tweaking the gulpfile a bit, removing some unnecessary steps (e.g. I create the ZXP in one folder; copy to the correct folder; remove the old one – while it is more efficient to instruct the command line to output the ZXP to the right folder) but it’s been such a relief to be able to automatise the whole thing that I immediately posted the first draft 🙂
      I admit that I thought about a yo generator, but it’s not something that I can handle (another learning curve… I can’t afford, too many learning curves already!) but one never knows.
      The next big hit IMHO would be custom installers – relying to Extension Manager is such a PITA.
      Thanks again and best regards!

      (EDIT: actually I’m playing with a Slush generator for CC Extensions now… if you don’t know it, slug is to yeoman as gulp is to grunt – so to speak 😉 )

  2. This is a great solution! Is Grunt incapable of doing this task? Did you choose Gulp out of preference?

    • Thanks Jake!
      On the contrary, I guess Grunt is capable as well; but (personally) I’ve found the Grunt learning curve steeper than Gulp.
      I’ve always wanted to approach Grunt, but the time I had wasn’t enough. With Gulp I went from zero to automation in less than a couple of days of casual study. I’m surely an unsophisticated user, but at least I’ve built a working tool 🙂

  3. Olav Martin Kvern September 10, 2014 at 3:39 AM

    Hi Davide,

    Great post! I’d been looking around for an approach for doing this, and had been thinking about writing my own packaging tool. This really helped! What’s nice about this is that it makes it easy to package multiple extensions at once, in addition to plug-ins–just add each extension to an array in your pack-html task, and it works beautifully.

    The Adobe Exchange Packager Utility ( does what the ucf.jar does, but I like your approach better.

    One thing to mention: the pack-hybrid-zxp step can take some time! For my extension(s), at least, more than seven minutes. I kept killing the process, because I thought it had crashed.

    One more thing: you might mention that the last step, once you’ve debugged everything else, is to run “gulp default”. It really should be clear from the gulp file, but it took me a bit to figure it out. I’m slow.:-)



  4. Olav Martin Kvern September 11, 2014 at 3:45 AM

    Hi Davide,

    One further thought–as far as I can tell, there’s no need to use the ucf.jar at all–you can replace it with ZXPSignCmd in the pack-hybrid-zxp step (at least for CC-only packages). It’s quite a bit faster than ucf.jar. Something like this (for the example above):

    shell.task(“./ZXPSignCmd -sign src/MXI build/SiliconConnectorForBox.zxp myCertificate.p12 myPassword -tsa“)



    • Thanks for the comments Olav!
      Actually the code I’m using now is more streamlined, I have to update the blogpost (there’s no need to copy/delete the files, just instruct the command line to use correct input/output folders).
      You’re right, ZXPSignCmd is faster – I (try to) support CS6 as well with script dialogs alongside CC HTML panels, so that’s the why I’m using ucf.jar

      What I would like to do next, time permitting (so when cows will fly up to Mars) is to write my own installers, not relying on Extension Manager. In principle this is doable using ExtendScript, which as you know has file system management features (XBytor’s Photoshop XTools for instance installs via script). We lack a couple of folder’s tokens, but luckily we can get to CEP in a consistent manner, no matter what is the user’s OS language.
      In fact my only doubt is that in some foreign (say, japanese) language ExtendScript has troubles starting from Library/Application Support to get to /Adobe/CEP/extensions or the like.

      I would skip OS installers (Windows .exe and Mac .dmg or .pkg) because I’ve no idea how to build them and how to use folders tokens from there. I’ve possibly heard about (bur frankly I don’t remember) the possibility to compile into executable Node.js apps, which would be another way – have you ever approached such things yourself? Looks like a minefield…

      Thank you Olav!

  5. Hey Davide! I read “For a reason that I don’t know, a semicolon after the shell.task() breaks gulp” and wanted to clarify. Shell.task is a function that is inside the parameters of gulp.task(), and there aren’t semi colons between params in Javascript.

    If it’s easier, think about it this way:

    gulp.task(‘example’, function ( ) { } ; ) would return an error. The semi colon shouldn’t be there. In the same way, placing a semi colon after shell.task is an error.

    If you want, you can also place your ZXPSignCmd file inside /usr/local/bin (where node and npm are located). This allows you to call ZXPSignCmd globally, so you don’t have to copy/paste it across multiple project folders.

    Lastly, gulp-clean has been deprecated in favor of del:
    Gulp-clean is fine, but del has a few protections that help prevent accidents, like deleting your working directory by mistake, etc. Cheers!

    • Thank you very much Jake!
      Your semicolon explanation now makes things clearer.
      As for the gulp-clean, I’ve been using rimraf too; but in fact it seems that del is what’s recommended now so I second your suggestion.
      Thanks again for your comments!

  6. Good stuff.

    Just to let others know I have used Grunt to automate our panel builds using ZXPSignCmd and all driven using Jenkins. Works well.

Trackbacks and Pingbacks:

  1. Adobe CEP 扩展开发教程 「 4 」签名与打包 – 浅藏的宝藏 - March 19, 2016

    […] 另外如果你喜欢用 gulp 的话,可以看看这篇文章: Automate ZXP Packaging with Gulp.js […]

Leave a Reply

Text formatting is available via select .

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">