├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── scripts ├── acrobat │ ├── close_document.js │ ├── new_document.js │ ├── open_document.js │ ├── save_and_close_document.js │ ├── save_as_document.js │ ├── save_document.js │ └── select_document.js ├── after_effects │ ├── close_document.js │ ├── new_document.js │ ├── open_document.js │ ├── save_and_close_document.js │ ├── save_as_document.js │ └── save_document.js ├── animate │ ├── close_document.js │ ├── new_document.js │ ├── open_document.js │ ├── save_and_close_document.js │ ├── save_as_document.js │ ├── save_document.js │ └── select_document.js ├── illustrator │ ├── close_document.js │ ├── new_document.js │ ├── open_document.js │ ├── save_and_close_document.js │ ├── save_as_document.js │ ├── save_document.js │ └── select_document.js ├── indesign │ ├── close_document.js │ ├── new_document.js │ ├── open_document.js │ ├── save_and_close_document.js │ ├── save_as_document.js │ ├── save_document.js │ └── select_document.js └── photoshop │ ├── close_document.js │ ├── new_document.js │ ├── open_document.js │ ├── save_and_close_document.js │ ├── save_as_document.js │ ├── save_document.js │ └── select_document.js ├── src ├── adobe-broadcast.ts ├── api.ts ├── app.ts ├── broadcast.ts ├── commands.ts ├── defaults.ts ├── index.ts ├── listener.ts ├── process.ts ├── script-builder.ts └── script-file-creator.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | .DS_Store 63 | dist/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tsconfig.json 2 | node_modules/ 3 | src/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 rkamysz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # adobe-node 2 | Control Adobe applications - such as Photoshop, Animate, Illustrator, InDesign - from node. Run JavaScript to remotely - from the command line - create, modify or export document content. This module can be used to automate the workflow by creating an action chain that can be executed without user intervention. 3 | 4 | More information about writing scripts for Adobe applications: 5 | - [Photoshop, Illustrator, InDesign etc. scripting center](https://www.adobe.com/devnet/scripting.html) 6 | - [Adobe Animate JavaScript API](https://help.adobe.com/archive/en_US/flash/cs5/flash_cs5_extending.pdf) 7 | 8 | ## Installation 9 | 10 | ``` 11 | npm i adobe-node 12 | ``` 13 | 14 | ## API 15 | 16 | ### Methods 17 | | Method | Arguments | Description | 18 | |:-------|:------------|:------------| 19 | | `init` | - | Initializes the `AdobeApp` instance/ starts Adobe Event Listener. 20 | | `on` | event: string,
callback: (message: BroadcastMessage) => void | Adds an event handler function to listen to an event so that when that event occurs, the callback will be triggered. 21 | | `runScript` | script: string,
options?: Options | Runs custom JavaScript 22 | | `selectDocument` | document: string | Brings selected document to the front of the screen. 23 | | `saveDocument` | ...documents: string[] | Saves changes. 24 | | `saveAsDocument` | document: string,
saveAs: string,
options: object | Saves the document in a specific format and location. Optionally you can specify the save options appropriate to the format.
_For more information on these options, see the script documentation for the selected Adobe product._ 25 | | `openDocument` | ...documents: string[] | Opens documents. 26 | | `closeDocument` | ...documents: string[] | Closes documents. 27 | | `saveAndCloseDocument` | ...documents: string[] | Saves changes and closes the documents. 28 | | `newDocument` | options?: NewDocumentOptions | Creates document. 29 | | `open` | - | Opens the Adobe application. 30 | | `close` | - | Closes the Adobe application. 31 | | `dispose` | - | Closes Adobe Event Listener and rest of the `AdobeApp` components. 32 | 33 | ### Config 34 | 35 | #### Config 36 | 37 | | Method | Type | Description | 38 | |:-------|:------------|:------------| 39 | | `app` | AdobeAppConfig | Adobe application config 40 | | `host` | string | Domain name of the server. 41 | | `port` | number | Port number on which the server is listening. 42 | | `appTimeout` | number | Time after which the application process will close. Useful when the application freezes.
By default - `0` - this option is off. 43 | | `jsPath` | string | Location of the javaScript files.
Default path is `"./js"` 44 | 45 | #### AdobeAppConfig 46 | 47 | | Method | Type | Description | 48 | |:-------|:------------|:------------| 49 | | `name` | AdobeAppName | Adobe application name.
eg. `AdobeAppName.Photoshop` 50 | | `path` | string | Path to the Adobe's app executable file. 51 | | `adobeScriptsPath` | string | Location of the `Scripts` directory of the selected Adobe app. 52 | 53 | ### Others 54 | 55 | #### BroadcastMessage 56 | 57 | | Property | Type | Description | 58 | |:-------|:------------|:------------| 59 | | `command` | string | Command name 60 | | `stdout` | string | Standard output 61 | | `stderr` | string | Standard error 62 | 63 | #### NewDocumentOptions 64 | 65 | | Property | Type | Description | 66 | |:-------|:------------|:------------| 67 | | `title` | string | Name of the document 68 | | `width` | number | Document width 69 | | `height` | number | Document height 70 | | `...` | any | + other custom, optional properties 71 | 72 | ### Events 73 | 74 | | Event | Description | 75 | |:------|:------------| 76 | | JS script file name | the event for a specific function called by the `runScript()` method 77 | |`AdobeAppEvent.OpenApp` | - 78 | |`AdobeAppEvent.CloseApp` | - 79 | |`AdobeAppEvent.NewDocument` | - 80 | |`AdobeAppEvent.OpenDocument` | - 81 | |`AdobeAppEvent.CloseDocument` | - 82 | |`AdobeAppEvent.SelectDocument` | - 83 | |`AdobeAppEvent.SaveDocument` | - 84 | |`AdobeAppEvent.SaveAsDocument` | - 85 | |`AdobeAppEvent.SaveAndCloseDocument` | - 86 | 87 | ## Examples 88 | 89 | ### Basic example 90 | 91 | ``` 92 | import { newAdobeApp, AdobeAppName, AdobeAppEvent, AdobeApp, BroadcastMessage } from "adobe-node"; 93 | 94 | const sleep = (duration: number) => new Promise(resolve => { setTimeout(resolve, duration) }); 95 | 96 | const main = async () => { 97 | const app: AdobeApp = newAdobeApp({ 98 | app: { 99 | name: AdobeAppName.Photoshop, 100 | path: '/Applications/Adobe Photoshop CC 2019/Adobe Photoshop CC 2019.app/Contents/MacOS/Adobe Photoshop CC 2019', 101 | adobeScriptsPath: '/Applications/Adobe Photoshop CC 2019/Presets/Scripts' 102 | }, 103 | host: 'localhost', 104 | port: 5000 105 | }); 106 | 107 | app.on(AdobeAppEvent.OpenApp, () => { 108 | console.log(`The Adobe App is open`); 109 | }) 110 | .on(AdobeAppEvent.NewDocument, () => { 111 | console.log(`The document has been created`); 112 | }) 113 | .on(AdobeAppEvent.OpenDocument, (data: any) => { 114 | console.log(`The document has been opened`); 115 | }) 116 | .on(AdobeAppEvent.CloseDocument, () => { 117 | console.log(`The document has been closed`); 118 | }) 119 | .on(AdobeAppEvent.CloseApp, () => { 120 | console.log(`The Adobe App has been closed`); 121 | }) 122 | .on("test_script", (message: BroadcastMessage) => { 123 | console.log(`Testing custom script - ${message}`); 124 | }); 125 | 126 | app.init(); 127 | 128 | await app.open(); 129 | await app.openDocument('/test1.psd'); 130 | await sleep(2000); 131 | await app.closeDocument('/test1.psd'); 132 | await sleep(2000); 133 | await app.close(); 134 | app.dispose(); 135 | } 136 | 137 | main(); 138 | ``` 139 | 140 | ### Running custom scripts 141 | In Adobe applications you can run scripts in `JSFL` (Adobe Animate) and `JSX` (Photoshop, Illustrator etc.)
142 | One of the features of this module is the ability to run a custom scripts written in `javaScript`.
143 | There are a few things to explain, first of all this is the script template triggered in the selected Adobe application. 144 | 145 | #### Template of Adobe Script 146 | 147 | ``` 148 | var ___{{__command__}} = (function() { 149 | var __stderr; 150 | var __stdout; 151 | 152 | try { 153 | {{__vars__}} 154 | __stdout = {{__fn__}} 155 | } catch (e) { 156 | __stderr = e; 157 | } finally { 158 | {{__broadcast__}} 159 | } 160 | })(); 161 | ``` 162 | Texts between `{{}}` are replaced with values prepared for a specific event/command. The javaScript code is pasted in the `{{__fn__}}` placeholder. 163 | 164 | As you can see `{{__fn__}}`/ JS code is assigned to the `__stdout` variable, this means that your javaScript code must be included in the `IIFE` function and it must also return a value - even if the logic doesn't require it - which will be passed in to the event. 165 | 166 | ``` 167 | // IIFE example 168 | (function(){ 169 | ... some magic 170 | return true; // whatever 171 | }()); 172 | ``` 173 | 174 | #### Running script without any arguments 175 | 176 | ``` 177 | ... 178 | await app.runScript('/some_custom_script.js'); 179 | ... 180 | ``` 181 | 182 | #### Running script with arguments 183 | 184 | ``` 185 | ... 186 | await app.runScript('/some_custom_script.js', { 187 | title: "New Document", 188 | width: 1024, 189 | height: 768 190 | }); 191 | ... 192 | ``` 193 | 194 | The arguments/ options used in the `runScript()` method are pasted in the `{{__vars__}}` placeholder.
195 | These vars are also available in the `IIFE`. 196 | 197 | #### Generated Adobe Script 198 | 199 | Here is an example of a generated script file that runs in Adobe app. 200 | 201 | ``` 202 | var ___new_document = (function() { 203 | var __stderr; 204 | var __stdout; 205 | 206 | try { 207 | var title = "New Document"; 208 | var width = 1024; 209 | var height = 768; 210 | 211 | __stdout = (function(){ 212 | var doc = app.documents.add(width, height, 72, title, NewDocumentMode.RGB, DocumentFill.TRANSPARENT, 1); 213 | return true; 214 | }()); 215 | 216 | } catch (e) { 217 | __stderr = e; 218 | } finally { 219 | app.system("adobe-broadcast --host='localhost' --port=5000 --msg='{\"command\":\"new_document\",\"stdout\":\"" + __stdout + "\", \"stderr\":\"" + __stderr + "\" }'"); 220 | } 221 | })(); 222 | ``` 223 | 224 | The generated adobe scripts files - `jsx`/`jsfl` - are saved in the location specified in the `adobeScriptsPath` configuration. 225 | 226 | ## To Do 227 | - implement more and improve built-in scripts 228 | 229 | ## Changelog 230 | 231 | ## 2.0.0 232 | ### Added 233 | - __API__ `selectDocument()` 234 | - __API__ `saveAsDocument()` 235 | - __API__ `saveAndCloseDocument()` 236 | - Build-in scripts (currently only for Photoshop and Animate) 237 | - `adobe-node/scripts/photoshop|animate|illustrator|indesign|after_effects|acrobat/open_document.js` 238 | - `adobe-node/scripts/photoshop|animate|illustrator|indesign|after_effects|acrobat/new_document.js` 239 | - `adobe-node/scripts/photoshop|animate|illustrator|indesign|after_effects|acrobat/save_document.js` 240 | - `adobe-node/scripts/photoshop|animate|illustrator|indesign|after_effects|acrobat/save_as_document.js` 241 | - `adobe-node/scripts/photoshop|animate|illustrator|indesign|after_effects|acrobat/close_document.js` 242 | - `adobe-node/scripts/photoshop|animate|illustrator|indesign|after_effects|acrobat/save_and_close_document.js` 243 | - `adobe-node/scripts/photoshop|animate|illustrator|indesign|after_effects|acrobat/select_document.js` 244 | 245 | ### Changed 246 | - __API__ `saveDocument()` - removed optional argument `saveAs`, saving multiple documents in one call. 247 | 248 | 249 | ## Tips 250 | 251 | If the Adobe application does not start, try to: 252 | 253 | * Change the access/permissions settings to the directory set in the `adobeScriptsPath` param 254 | eg. 255 | ``` 256 | sudo chown "/Applications/Adobe Photoshop CC 2019/Presets/Scripts/" 257 | ``` 258 | 259 | * It is also possible that you will need to change user config to enable custom scripts. 260 | For example, for Photoshop, add `WarnRunningScripts 0` to the `PSUserConfig.txt` file in the Photoshop settings folder and restart Photoshop. 261 | 262 | If any of the built-in functions does not work as you expected, you can write these functions yourself and run them via the `runScript()` method. 263 | 264 | 265 | 266 | ## Contribute 267 | Feel free to contribute. If you have any ideas, requests just leave a message. 268 | 269 | ## Info 270 | 271 | I'm not using Adobe applications, so it may happen that one of the built-in scripts does not work properly or is not optimal. I wrote it based on available resources. If you find an error or you think the script should be written in a different way. Let me know or even better implement your solution and I will add it. 272 | 273 | ## License 274 | 275 | Copyright (c) 2019 Radoslaw Kamysz 276 | 277 | Licensed under the [MIT license](LICENSE). 278 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adobe-node", 3 | "version": "1.1.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/minimist": { 8 | "version": "1.2.0", 9 | "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", 10 | "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=" 11 | }, 12 | "@types/node": { 13 | "version": "12.12.6", 14 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.6.tgz", 15 | "integrity": "sha512-FjsYUPzEJdGXjwKqSpE0/9QEh6kzhTAeObA54rn6j3rR4C/mzpI9L0KNfoeASSPMMdxIsoJuCLDWcM/rVjIsSA==", 16 | "dev": true 17 | }, 18 | "minimist": { 19 | "version": "1.2.0", 20 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 21 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 22 | }, 23 | "tslib": { 24 | "version": "1.10.0", 25 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", 26 | "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adobe-node", 3 | "version": "1.1.3", 4 | "description": "Control Adobe applications - such as Photoshop, Animate, Illustrator, InDesign - from node. Run JavaScript to remotely - from the command line - create, modify or export document content. This module can be used to automate the workflow by creating an action chain that can be executed without user intervention.", 5 | "main": "dist/index", 6 | "scripts": { 7 | "build": "tsc" 8 | }, 9 | "bin": { 10 | "adobe-broadcast": "dist/adobe-broadcast.js" 11 | }, 12 | "keywords": [ 13 | "adobe", 14 | "photoshop", 15 | "animate", 16 | "illustrator", 17 | "indesign", 18 | "bridge", 19 | "pipeline", 20 | "jsx", 21 | "jsfl", 22 | "remote", 23 | "broadcast" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/rkamysz/adobe-node.git" 28 | }, 29 | "author": "Radoslaw Kamysz", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/rkamysz/adobe-node/issues" 33 | }, 34 | "homepage": "https://github.com/rkamysz/adobe-node#readme", 35 | "devDependencies": { 36 | "@types/node": "^12.12.5" 37 | }, 38 | "dependencies": { 39 | "@types/minimist": "^1.2.0", 40 | "minimist": "^1.2.0", 41 | "tslib": "^1.10.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /scripts/acrobat/close_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | var docs = app.activeDocs; 4 | 5 | for (var d in documents) { 6 | var path = documents[d]; 7 | var name = path.replace(/^.*?([^\\\/]*)$/, '$1'); 8 | for (var i=0; i < docs.length; i++) { 9 | var doc = docs[i]; 10 | if (doc.info.Title == name) { 11 | doc.close(true); 12 | } 13 | } 14 | } 15 | 16 | return true; 17 | 18 | }(documents)); 19 | -------------------------------------------------------------------------------- /scripts/acrobat/new_document.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var _title = "new_document"; 3 | var _width = 612; 4 | var _height = 792; 5 | 6 | try { 7 | _title = title; 8 | } catch (e) { } 9 | try { 10 | _width = width; 11 | } catch (e) { } 12 | try { 13 | _height = height; 14 | } catch (e) { } 15 | 16 | 17 | app.newDoc(_width, _height); 18 | return true; 19 | }()); 20 | -------------------------------------------------------------------------------- /scripts/acrobat/open_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | for (var i in documents) { 4 | app.openDoc(documents[i]); 5 | } 6 | 7 | return true; 8 | 9 | }(documents)); -------------------------------------------------------------------------------- /scripts/acrobat/save_and_close_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | var docs = app.activeDocs; 4 | 5 | for (var d in documents) { 6 | var path = documents[d]; 7 | var name = path.replace(/^.*?([^\\\/]*)$/, '$1'); 8 | for (var i=0; i < docs.length; i++) { 9 | var doc = docs[i]; 10 | if (doc.info.Title == name) { 11 | doc.saveAs(path); 12 | doc.close(true); 13 | } 14 | } 15 | } 16 | 17 | return true; 18 | 19 | }(documents)); 20 | -------------------------------------------------------------------------------- /scripts/acrobat/save_as_document.js: -------------------------------------------------------------------------------- 1 | (function (document, saveAs) { 2 | var name = document.replace(/^.*?([^\\\/]*)$/, '$1'); 3 | var docs = app.activeDocs; 4 | 5 | for (var i = 0; i < docs.length; i++) { 6 | var doc = docs[i]; 7 | if (doc.info.Title == name) { 8 | doc.saveAs(saveAs); 9 | } 10 | } 11 | 12 | return true; 13 | 14 | }(document, saveAs)); 15 | -------------------------------------------------------------------------------- /scripts/acrobat/save_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | var docs = app.activeDocs; 4 | 5 | for (var d in documents) { 6 | var path = documents[d]; 7 | var name = path.replace(/^.*?([^\\\/]*)$/, '$1'); 8 | for (var i=0; i < docs.length; i++) { 9 | var doc = docs[i]; 10 | if (doc.info.Title == name) { 11 | doc.saveAs(path); 12 | } 13 | } 14 | } 15 | 16 | return true; 17 | 18 | }(documents)); -------------------------------------------------------------------------------- /scripts/acrobat/select_document.js: -------------------------------------------------------------------------------- 1 | (function (document) { 2 | var docs = app.activeDocs; 3 | var name = document.replace(/^.*?([^\\\/]*)$/, '$1'); 4 | 5 | for (var i = 0; i < docs.length; i++) { 6 | var doc = docs[i]; 7 | if (doc.info.Title == name) { 8 | doc.bringToFront(); 9 | } 10 | } 11 | 12 | return true; 13 | 14 | }(document)); -------------------------------------------------------------------------------- /scripts/after_effects/close_document.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | app.project.close(CloseOptions.DO_NOT_SAVE_CHANGES); 3 | return true; 4 | 5 | }()); 6 | -------------------------------------------------------------------------------- /scripts/after_effects/new_document.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | app.project.close(CloseOptions.DO_NOT_SAVE_CHANGES); 4 | app.newProject(); 5 | 6 | return true; 7 | }()); 8 | -------------------------------------------------------------------------------- /scripts/after_effects/open_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | app.open(new File(documents[0])); 4 | 5 | return true; 6 | 7 | }(documents)); 8 | -------------------------------------------------------------------------------- /scripts/after_effects/save_and_close_document.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | app.project.close(CloseOptions.SAVE_CHANGES) 4 | return true; 5 | 6 | }()); 7 | -------------------------------------------------------------------------------- /scripts/after_effects/save_as_document.js: -------------------------------------------------------------------------------- 1 | (function (document, saveAs) { 2 | 3 | app.project.save(new File(saveAs)); 4 | 5 | return true; 6 | 7 | }(document, saveAs)); 8 | -------------------------------------------------------------------------------- /scripts/after_effects/save_document.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | app.project.save(); 4 | return true; 5 | 6 | }()); -------------------------------------------------------------------------------- /scripts/animate/close_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | for (var i in documents) { 4 | var name = documents[i].replace(/^.*?([^\\\/]*)$/, '$1'); 5 | for(var i in fl.documents) { 6 | var doc = fl.documents[i]; 7 | if(doc.name === name) { 8 | fl.closeDocument(doc, false); 9 | } 10 | } 11 | } 12 | 13 | return true; 14 | 15 | }(documents)); 16 | -------------------------------------------------------------------------------- /scripts/animate/new_document.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | fl.createDocument(); 3 | return true; 4 | }()); 5 | -------------------------------------------------------------------------------- /scripts/animate/open_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | for (var i in documents) { 4 | fl.openDocument(documents[i]); 5 | } 6 | 7 | return true; 8 | 9 | }(documents)); -------------------------------------------------------------------------------- /scripts/animate/save_and_close_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | for (var i in documents) { 4 | var name = documents[i].replace(/^.*?([^\\\/]*)$/, '$1'); 5 | for(var i in fl.documents) { 6 | var doc = fl.documents[i]; 7 | if(doc.name === name) { 8 | doc.save(); 9 | fl.closeDocument(doc, false); 10 | } 11 | } 12 | } 13 | 14 | return true; 15 | 16 | }(documents)); 17 | -------------------------------------------------------------------------------- /scripts/animate/save_as_document.js: -------------------------------------------------------------------------------- 1 | (function (document, saveAs) { 2 | var _selectionOnly = false; 3 | 4 | try { 5 | _selectionOnly = selectionOnly; 6 | } catch (e) { } 7 | 8 | var doc = fl.openDocument(document); 9 | doc.saveAs(saveAs, _selectionOnly); 10 | 11 | return true; 12 | 13 | }(document, saveAs)); 14 | -------------------------------------------------------------------------------- /scripts/animate/save_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | for (var i in documents) { 3 | var doc = fl.openDocument(documents[i]); 4 | doc.save(); 5 | } 6 | return true; 7 | 8 | }(documents)); -------------------------------------------------------------------------------- /scripts/animate/select_document.js: -------------------------------------------------------------------------------- 1 | (function(document){ 2 | 3 | fl.openDocument(document); 4 | 5 | return true; 6 | 7 | }(document)); -------------------------------------------------------------------------------- /scripts/illustrator/close_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | for (var i in documents) { 4 | var name = documents[i].replace(/^.*?([^\\\/]*)$/, '$1'); 5 | app.documents[name].close(SaveOptions.DONOTSAVECHANGES); 6 | } 7 | 8 | return true; 9 | 10 | }(documents)); 11 | -------------------------------------------------------------------------------- /scripts/illustrator/new_document.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var _documentColorSpace = DocumentColorSpace.RGB; 4 | var _width = 500; 5 | var _height = 500; 6 | var _numArtBoards = 1; 7 | var _artboardLayout = 1; 8 | var _artboardSpacing = 1; 9 | var _artboardRowsOrCols = 1; 10 | 11 | try { 12 | _documentColorSpace = documentColorSpace; 13 | } catch (e) { } 14 | try { 15 | _width = width; 16 | } catch (e) { } 17 | try { 18 | _height = height; 19 | } catch (e) { } 20 | try { 21 | _numArtBoards = numArtBoards; 22 | } catch (e) { } 23 | try { 24 | _artboardLayout = artboardLayout; 25 | } catch (e) { } 26 | try { 27 | _artboardSpacing = artboardSpacing; 28 | } catch (e) { } 29 | try { 30 | _artboardRowsOrCols = artboardRowsOrCols; 31 | } catch (e) { } 32 | 33 | app.documents.add(_documentColorSpace, _width, _height, _numArtBoards, _artboardLayout, _artboardSpacing, _artboardRowsOrCols); 34 | 35 | return true; 36 | }()); 37 | -------------------------------------------------------------------------------- /scripts/illustrator/open_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | for (var i in documents) { 4 | app.open(File(documents[i])); 5 | } 6 | 7 | return true; 8 | 9 | }(documents)); -------------------------------------------------------------------------------- /scripts/illustrator/save_and_close_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | for (var i in documents) { 4 | var name = documents[i].replace(/^.*?([^\\\/]*)$/, '$1'); 5 | app.documents[name].close(SaveOptions.SAVECHANGES); 6 | } 7 | 8 | return true; 9 | 10 | }(documents)); 11 | -------------------------------------------------------------------------------- /scripts/illustrator/save_as_document.js: -------------------------------------------------------------------------------- 1 | (function (document, saveAs) { 2 | var name = document.replace(/^.*?([^\\\/]*)$/, '$1'); 3 | 4 | app.documents[name].saveAs(new File(saveAs)); 5 | 6 | return true; 7 | 8 | }(document, saveAs)); 9 | -------------------------------------------------------------------------------- /scripts/illustrator/save_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | var rootDoc = app.activeDocument; 4 | 5 | for (var i in documents) { 6 | var name = documents[i].replace(/^.*?([^\\\/]*)$/, '$1'); 7 | var doc = app.documents[name]; 8 | app.activeDocument = doc; 9 | doc.save(); 10 | } 11 | app.activeDocument = rootDoc; 12 | return true; 13 | 14 | }(documents)); -------------------------------------------------------------------------------- /scripts/illustrator/select_document.js: -------------------------------------------------------------------------------- 1 | (function(document){ 2 | 3 | var name = document.replace(/^.*?([^\\\/]*)$/, '$1'); 4 | var doc = app.documents[name]; 5 | 6 | app.activeDocument = doc; 7 | 8 | return true; 9 | 10 | }(document)); -------------------------------------------------------------------------------- /scripts/indesign/close_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | for (var i in documents) { 4 | var name = documents[i].replace(/^.*?([^\\\/]*)$/, '$1'); 5 | app.documents.itemByName(name).close(SaveOptions.DONOTSAVECHANGES); 6 | } 7 | 8 | return true; 9 | 10 | }(documents)); 11 | -------------------------------------------------------------------------------- /scripts/indesign/new_document.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var _title = "new_document"; 3 | var _width = 500; 4 | var _height = 500; 5 | var _pageOrientation = PageOrientation.portrait; 6 | var _pagesPerDocument = 16 7 | 8 | try { 9 | _title = title; 10 | } catch (e) { } 11 | try { 12 | _width = width; 13 | } catch (e) { } 14 | try { 15 | _height = height; 16 | } catch (e) { } 17 | try { 18 | _pageOrientation = pageOrientation; 19 | } catch (e) { } 20 | try { 21 | _pagesPerDocument = pagesPerDocument; 22 | } catch (e) { } 23 | 24 | var doc = app.documents.add(); 25 | 26 | with(doc.documentPreferences){ 27 | pageHeight = _height; 28 | pageWidth = _width; 29 | pageOrientation = _pageOrientation; 30 | pagesPerDocument = _pagesPerDocument; 31 | } 32 | 33 | return true; 34 | }()); 35 | -------------------------------------------------------------------------------- /scripts/indesign/open_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | for (var i in documents) { 4 | app.open(File(documents[i])); 5 | } 6 | 7 | return true; 8 | 9 | }(documents)); -------------------------------------------------------------------------------- /scripts/indesign/save_and_close_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | for (var i in documents) { 4 | var path = documents[i]; 5 | var name = path.replace(/^.*?([^\\\/]*)$/, '$1'); 6 | var doc = app.documents.itemByName(name); 7 | if(doc.saved) { 8 | doc.close(SaveOptions.yes); 9 | } else { 10 | doc.close(SaveOptions.yes, File(path)); 11 | } 12 | } 13 | 14 | return true; 15 | 16 | }(documents)); 17 | -------------------------------------------------------------------------------- /scripts/indesign/save_as_document.js: -------------------------------------------------------------------------------- 1 | (function (document, saveAs) { 2 | 3 | var name = document.replace(/^.*?([^\\\/]*)$/, '$1'); 4 | 5 | app.documents.itemByName(name).save(new File(saveAs)); 6 | 7 | return true; 8 | 9 | }(document, saveAs)); 10 | -------------------------------------------------------------------------------- /scripts/indesign/save_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | var rootDoc = app.activeDocument; 4 | 5 | for (var i in documents) { 6 | var path = documents[i]; 7 | var name = path.replace(/^.*?([^\\\/]*)$/, '$1'); 8 | var doc = app.documents.itemByName(name); 9 | app.activeDocument = doc; 10 | 11 | if (doc.saved) { 12 | doc.save(); 13 | } else { 14 | doc.save(new File(path)); 15 | } 16 | } 17 | app.activeDocument = rootDoc; 18 | return true; 19 | 20 | }(documents)); -------------------------------------------------------------------------------- /scripts/indesign/select_document.js: -------------------------------------------------------------------------------- 1 | (function(document){ 2 | 3 | var name = document.replace(/^.*?([^\\\/]*)$/, '$1'); 4 | var doc = app.documents.itemByName(name); 5 | 6 | app.activeDocument = doc; 7 | 8 | return true; 9 | 10 | }(document)); -------------------------------------------------------------------------------- /scripts/photoshop/close_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | for (var i in documents) { 4 | var name = documents[i].replace(/^.*?([^\\\/]*)$/, '$1'); 5 | app.documents[name].close(SaveOptions.DONOTSAVECHANGES); 6 | } 7 | 8 | return true; 9 | 10 | }(documents)); 11 | -------------------------------------------------------------------------------- /scripts/photoshop/new_document.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var _title = "new_document"; 3 | var _width = 500; 4 | var _height = 500; 5 | var _resolution = 72; 6 | var _newDocumentMode = NewDocumentMode.RGB; 7 | var _initialFill = DocumentFill.TRANSPARENT; 8 | var _pixelAspectRatio = 1; 9 | var _bitsPerChannel = BitsPerChannelType.EIGHT; 10 | 11 | try { 12 | _title = title; 13 | } catch (e) { } 14 | try { 15 | _width = width; 16 | } catch (e) { } 17 | try { 18 | _height = height; 19 | } catch (e) { } 20 | try { 21 | _resolution = resolution; 22 | } catch (e) { } 23 | try { 24 | _newDocumentMode = newDocumentMode; 25 | } catch (e) { } 26 | try { 27 | _initialFill = initialFill; 28 | } catch (e) { } 29 | try { 30 | _pixelAspectRatio = pixelAspectRatio; 31 | } catch (e) { } 32 | try { 33 | _bitsPerChannel = bitsPerChannel; 34 | } catch (e) { } 35 | 36 | app.documents.add(_width, _height, _resolution, _title, _newDocumentMode, _initialFill, _pixelAspectRatio, _bitsPerChannel); 37 | return true; 38 | }()); 39 | -------------------------------------------------------------------------------- /scripts/photoshop/open_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | for (var i in documents) { 4 | app.open(File(documents[i])); 5 | } 6 | 7 | return true; 8 | 9 | }(documents)); -------------------------------------------------------------------------------- /scripts/photoshop/save_and_close_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | for (var i in documents) { 4 | var name = documents[i].replace(/^.*?([^\\\/]*)$/, '$1'); 5 | app.documents[name].close(SaveOptions.SAVECHANGES); 6 | } 7 | 8 | return true; 9 | 10 | }(documents)); 11 | -------------------------------------------------------------------------------- /scripts/photoshop/save_as_document.js: -------------------------------------------------------------------------------- 1 | (function (document, saveAs) { 2 | var _options = { typename: PhotoshopSaveOptions }; 3 | var _asCopy = false; 4 | var _extensionType = Extension.NONE; 5 | var name = document.replace(/^.*?([^\\\/]*)$/, '$1'); 6 | 7 | try { 8 | _options = options; 9 | } catch (e) { } 10 | try { 11 | _asCopy = asCopy; 12 | } catch (e) { } 13 | try { 14 | _extensionType = extensionType; 15 | } catch (e) { } 16 | 17 | app.documents[name].saveAs(File(saveAs), _options, _asCopy, _extensionType); 18 | 19 | return true; 20 | 21 | }(document, saveAs)); 22 | -------------------------------------------------------------------------------- /scripts/photoshop/save_document.js: -------------------------------------------------------------------------------- 1 | (function (documents) { 2 | 3 | var rootDoc = app.activeDocument; 4 | 5 | for (var i in documents) { 6 | var name = documents[i].replace(/^.*?([^\\\/]*)$/, '$1'); 7 | var doc = app.documents[name]; 8 | app.activeDocument = doc; 9 | doc.save(); 10 | } 11 | app.activeDocument = rootDoc; 12 | return true; 13 | 14 | }(documents)); -------------------------------------------------------------------------------- /scripts/photoshop/select_document.js: -------------------------------------------------------------------------------- 1 | (function(document){ 2 | 3 | var name = document.replace(/^.*?([^\\\/]*)$/, '$1'); 4 | var doc = app.documents[name]; 5 | 6 | app.activeDocument = doc; 7 | 8 | return true; 9 | 10 | }(document)); -------------------------------------------------------------------------------- /src/adobe-broadcast.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { createConnection, connect, Socket } from "net"; 4 | import * as minimist from 'minimist'; 5 | import defaults from './defaults'; 6 | 7 | const args = minimist(process.argv.slice(2)); 8 | 9 | const host:string = args.host || defaults.host; 10 | const port:number = Number(args.port) || defaults.port; 11 | const message:string = args.msg; 12 | const client: Socket = connect({ host, port }, () => { 13 | console.log('Adobe Broadcast connected'); 14 | }); 15 | 16 | client.setEncoding('utf8'); 17 | 18 | client.on('error', (error: Error) => { 19 | console.error(`Adobe Broadcast error: ${JSON.stringify(error)}`); 20 | }); 21 | 22 | client.write(message, () => { 23 | process.exit(); 24 | }); -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface AdobeApp { 3 | init(): void; 4 | on(event: string, callback: Function): AdobeApp; 5 | runScript(command: string, options?: Options): Promise; 6 | selectDocument(document: string): Promise; 7 | saveDocument(...documents: string[]): Promise; 8 | saveAsDocument(document: string, saveAs: string, options?: object): Promise; 9 | openDocument(...documents: string[]): Promise; 10 | closeDocument(...documents: string[]): Promise; 11 | saveAndCloseDocument(...documents: string[]): Promise; 12 | newDocument(options?: NewDocumentOptions): Promise 13 | open(): Promise; 14 | close(): Promise; 15 | dispose(): void; 16 | } 17 | 18 | export enum AdobeAppName { 19 | Animate = 'animate', 20 | Photoshop = 'photoshop', 21 | Illustrator = 'illustrator', 22 | InDesign = 'indesign', 23 | Acrobat = 'acrobat', 24 | AfterEffects = 'after_effects' 25 | } 26 | 27 | export enum AdobeAppScriptFileType { 28 | Jsfl = 'jsfl', 29 | Jsx = 'jsx' 30 | } 31 | 32 | export type AdobeProcessOptions = { 33 | timeout?: number; 34 | timeoutCallback?: Function; 35 | } 36 | 37 | export type Options = { 38 | [prop:string]:any 39 | } 40 | 41 | export type NewDocumentOptions = Options & { 42 | title?: string; 43 | width?: number, 44 | height?: number 45 | } 46 | 47 | export type AdobeAppConfig = { 48 | name: AdobeAppName; 49 | path: string; 50 | adobeScriptsPath: string; 51 | } 52 | 53 | export type Config = { 54 | app:AdobeAppConfig; 55 | host: string; 56 | port: number; 57 | appTimeout?: number; 58 | jsPath?: string; 59 | } 60 | 61 | export interface AdobeEventListener { 62 | addEventListener(event: string, callback: Function): void; 63 | start(): void; 64 | close(): void; 65 | } 66 | 67 | export interface AdobeAppProcess { 68 | create(script: string): void; 69 | kill(): void; 70 | run(commandPath: string): void; 71 | } 72 | 73 | export interface CommandStack { 74 | push(data: AdobeScriptCommand): void; 75 | resolve(commandName: string): Promise; 76 | } 77 | 78 | export interface AdobeScriptBuilder { 79 | setName(value: string): AdobeScriptBuilder; 80 | setVariables(value: string): AdobeScriptBuilder; 81 | setBody(value: string): AdobeScriptBuilder; 82 | setBroadcast(value: string): AdobeScriptBuilder; 83 | build(): string; 84 | } 85 | 86 | export interface CommandFileCreator { 87 | create(command: string, useBuiltInScript?: boolean, options?: Options): Promise; 88 | } 89 | 90 | export interface BroadcastBuilder { 91 | build(command: string): string; 92 | } 93 | 94 | export interface BroadcastMessage { 95 | command: string; 96 | stdout?: string; 97 | stderr?: string; 98 | } 99 | 100 | export interface AdobeScriptCommand { 101 | command: string; 102 | resolve: Function; 103 | reject: Function; 104 | } 105 | 106 | export enum AdobeAppEvent { 107 | OpenApp = "open_app", 108 | CloseApp = "close_app", 109 | NewDocument = "new_document", 110 | OpenDocument = "open_document", 111 | CloseDocument = "close_document", 112 | SaveAndCloseDocument = "save_and_close_document", 113 | SaveDocument = "save_document", 114 | SelectDocument = "select_document", 115 | SaveAsDocument = "save_as_document", 116 | RunScript = "run_script" 117 | } 118 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { AdobeAppEvent, AdobeAppProcess, CommandFileCreator as AdobeScriptCreator, CommandStack, Config, NewDocumentOptions, Options, AdobeApp, AdobeEventListener } from "./api"; 4 | import newAdobeAppListener from './listener'; 5 | import newAdobeAppProcess from './process'; 6 | import newCommandStack from './commands'; 7 | import newAdobeScriptCreator from './script-file-creator'; 8 | import { broadcast } from './broadcast'; 9 | 10 | export const newAdobeApp = (config: Config, timeoutCallback?: Function): AdobeApp => { 11 | 12 | const scriptCreator: AdobeScriptCreator = newAdobeScriptCreator(config); 13 | const commandStack: CommandStack = newCommandStack(); 14 | const eventListener: AdobeEventListener = newAdobeAppListener(config.host, config.port, eventListenerCallback); 15 | const appProcess: AdobeAppProcess = newAdobeAppProcess(config.app.path, appCloseCallback, { 16 | timeout: config.appTimeout, 17 | timeoutCallback 18 | }); 19 | 20 | function eventListenerCallback(commandName: string) { 21 | commandStack.resolve(commandName); 22 | } 23 | 24 | function appCloseCallback() { 25 | broadcast(config.host, config.port, { command: AdobeAppEvent.CloseApp }); 26 | } 27 | 28 | function useBuiltInScript(command: string): boolean { 29 | return command === AdobeAppEvent.CloseDocument 30 | || command === AdobeAppEvent.SaveDocument 31 | || command === AdobeAppEvent.SaveAndCloseDocument 32 | || command === AdobeAppEvent.SaveAsDocument 33 | || command === AdobeAppEvent.OpenDocument 34 | || command === AdobeAppEvent.NewDocument 35 | || command === AdobeAppEvent.SelectDocument; 36 | } 37 | 38 | const app: AdobeApp = { 39 | init() { 40 | eventListener.start(); 41 | }, 42 | on(event: string, callback: Function): AdobeApp { 43 | eventListener.addEventListener(event, callback); 44 | return app; 45 | }, 46 | 47 | runScript: (command: string, options?: Options): Promise => 48 | new Promise(async (resolve, reject) => { 49 | const commandPath: string = await scriptCreator.create(command, useBuiltInScript(command), options); 50 | commandStack.push({ command, resolve, reject }); 51 | appProcess.run(commandPath); 52 | }), 53 | 54 | saveDocument: (...documents: string[]): Promise => 55 | app.runScript(AdobeAppEvent.SaveDocument, { documents }), 56 | 57 | selectDocument: (document: string): Promise => 58 | app.runScript(AdobeAppEvent.SelectDocument, { document }), 59 | 60 | saveAsDocument: (document: string, saveAs: string, options?: object): Promise => 61 | app.runScript(AdobeAppEvent.SaveAsDocument, { document, saveAs, options }), 62 | 63 | openDocument: (...documents: string[]): Promise => 64 | app.runScript(AdobeAppEvent.OpenDocument, { documents }), 65 | 66 | closeDocument: (...documents: string[]): Promise => 67 | app.runScript(AdobeAppEvent.CloseDocument, { documents }), 68 | 69 | saveAndCloseDocument: (...documents: string[]): Promise => 70 | app.runScript(AdobeAppEvent.SaveAndCloseDocument, { documents }), 71 | 72 | newDocument: (options?: NewDocumentOptions): Promise => 73 | app.runScript(AdobeAppEvent.NewDocument, options), 74 | 75 | open: (): Promise => new Promise(async (resolve, reject) => { 76 | const scriptPath = await scriptCreator.create(AdobeAppEvent.OpenApp) 77 | appProcess.create(scriptPath); 78 | commandStack.push({ 79 | command: AdobeAppEvent.OpenApp, resolve, reject 80 | }); 81 | }), 82 | 83 | close: (): Promise => new Promise(async (resolve, reject) => { 84 | appProcess.kill(); 85 | commandStack.push({ 86 | command: AdobeAppEvent.CloseApp, resolve, reject 87 | }); 88 | }), 89 | dispose: (): void => { 90 | eventListener.close(); 91 | } 92 | } 93 | 94 | return app; 95 | } 96 | -------------------------------------------------------------------------------- /src/broadcast.ts: -------------------------------------------------------------------------------- 1 | import { AdobeAppName, BroadcastBuilder, BroadcastMessage, Config } from "./api"; 2 | import { exec } from 'child_process'; 3 | 4 | const broadcastMethods: Map string> = new Map string>([ 5 | [AdobeAppName.Animate, (cmd: string) => `FLfile.runCommandLine("${cmd}");`], 6 | [AdobeAppName.Photoshop, (cmd: string) => `app.system("${cmd}");`], 7 | [AdobeAppName.Illustrator, (cmd: string) => `app.system("${cmd}");`], 8 | ]); 9 | 10 | const buildBroadcastCommand = (host: string, port: number, message: string) => 11 | `adobe-broadcast --host='${host}' --port=${port} --msg='${message}'`; 12 | 13 | export const newBroadcastBuilder = (config: Config): BroadcastBuilder => { 14 | 15 | return { 16 | build(command: string) { 17 | const payload: string = `{\\"command\\":\\"${command}\\",\\"stdout\\":\\"" + __stdout + "\\", \\"stderr\\":\\"" + __stderr + "\\" }`; 18 | const broadcast: (msg: string) => string = broadcastMethods.get(config.app.name); 19 | const cmd: string = buildBroadcastCommand(config.host, config.port, payload); 20 | return broadcast(cmd); 21 | } 22 | } 23 | } 24 | 25 | export const broadcast = (host: string, port: number, message: BroadcastMessage) => { 26 | const cmd: string = buildBroadcastCommand(host, port, JSON.stringify(message)); 27 | exec(cmd); 28 | } 29 | -------------------------------------------------------------------------------- /src/commands.ts: -------------------------------------------------------------------------------- 1 | import { AdobeScriptCommand, CommandStack } from "./api"; 2 | 3 | const newCommandStack = (): CommandStack => { 4 | 5 | const stack: Map = new Map(); 6 | 7 | return { 8 | push: (data: AdobeScriptCommand): Promise => new Promise(resolve => { 9 | if (!stack.has(data.command)) stack.set(data.command, []); 10 | stack.get(data.command).push(data); 11 | return resolve(); 12 | }), 13 | resolve: (commandName: string): Promise => new Promise(resolve => { 14 | if (stack.has(commandName)) { 15 | const command = stack.get(commandName).shift(); 16 | if(command) { 17 | command.resolve(); 18 | } 19 | } 20 | return resolve(); 21 | }) 22 | } 23 | }; 24 | 25 | export default newCommandStack; -------------------------------------------------------------------------------- /src/defaults.ts: -------------------------------------------------------------------------------- 1 | const adobeScriptsPath: string = 'adobe-scripts'; 2 | const scriptsPath: string = 'js'; 3 | const host: string = 'localhost'; 4 | const port: number = 5000; 5 | 6 | export default { 7 | adobeScriptsPath, 8 | scriptsPath, 9 | host, 10 | port 11 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app'; 2 | export * from './api'; -------------------------------------------------------------------------------- /src/listener.ts: -------------------------------------------------------------------------------- 1 | import { AdobeEventListener, BroadcastMessage } from './api'; 2 | import { Socket, Server, createServer } from 'net'; 3 | 4 | const newAdobeAppListener = (host: string, port: number, callback: (commandName: string) => void): AdobeEventListener => { 5 | 6 | const callbacks: Map = new Map(); 7 | let server: Server; 8 | let client:Socket; 9 | 10 | function connectionListener(socket: Socket) { 11 | client = socket; 12 | socket.on('data', (buffer: Buffer) => { 13 | const data: BroadcastMessage = JSON.parse(buffer.toString()); 14 | 15 | if (callbacks.has(data.command)) { 16 | callbacks.get(data.command)(data.stdout, data.stderr); 17 | } 18 | 19 | callback(data.command); 20 | }); 21 | } 22 | 23 | function disposeServer() { 24 | if(client) { 25 | client.end(); 26 | } 27 | server = null; 28 | console.log(`Adobe Event Listener has been stopped at port ${port}`); 29 | } 30 | 31 | return { 32 | addEventListener: (event: string, callback: Function): void => { 33 | if (callbacks.has(event)) { 34 | console.warn(`${event} listener will be overwritten`); 35 | } 36 | callbacks.set(event, callback); 37 | }, 38 | start: (): void => { 39 | if (server) return; 40 | server = createServer(connectionListener); 41 | server.listen(port, host, () => { 42 | console.log(`Adobe Event Listener running at ${host}:${port}`); 43 | }); 44 | }, 45 | close: (): void => { 46 | server.close(disposeServer); 47 | } 48 | } 49 | } 50 | 51 | export default newAdobeAppListener; 52 | -------------------------------------------------------------------------------- /src/process.ts: -------------------------------------------------------------------------------- 1 | import * as os from "os"; 2 | import { exec, ChildProcess, execFile, ExecException } from "child_process"; 3 | import { AdobeProcessOptions, AdobeAppProcess } from "./api"; 4 | import { existsSync } from 'fs'; 5 | 6 | const newAdobeAppProcess = (appPath: string, closeCallback: Function, options?: AdobeProcessOptions): AdobeAppProcess => { 7 | let process: ChildProcess; 8 | const timeoutCallback: Function = options.timeoutCallback; 9 | const processTimeout: number = options.timeout || 0; 10 | const openCmd: string = os.platform() === "win32" ? "start" : "open -a"; 11 | 12 | const createCallback = (execTime: number) => (error: ExecException, stdout: string, stderr: string) => { 13 | let becauseOfTimeout: boolean = Date.now() - execTime >= processTimeout && processTimeout > 0; 14 | if (becauseOfTimeout && timeoutCallback) { 15 | timeoutCallback(error); 16 | } else { 17 | closeCallback(stdout); 18 | } 19 | }; 20 | 21 | return { 22 | create:(openAppScript: string): void => { 23 | const execFileCallback = createCallback(Date.now()); 24 | if(!existsSync(appPath)) { 25 | throw new Error('Wrong app path'); 26 | } 27 | process = execFile(appPath, [openAppScript], { timeout: processTimeout }, execFileCallback); 28 | }, 29 | kill:(): void => { 30 | process.kill(); 31 | }, 32 | run:(commandPath: string): void => { 33 | exec(`${openCmd} ${appPath.replace(/ /g, "\\ ")} ${commandPath.replace(/ /g, "\\ ")}`); 34 | } 35 | } 36 | } 37 | 38 | export default newAdobeAppProcess; -------------------------------------------------------------------------------- /src/script-builder.ts: -------------------------------------------------------------------------------- 1 | import { AdobeScriptBuilder } from './api'; 2 | 3 | const commandTemplate: string = ` 4 | var ___{{__command__}} = (function() { 5 | var __stderr; 6 | var __stdout; 7 | 8 | try { 9 | {{__vars__}} 10 | __stdout = {{__fn__}} 11 | } catch (e) { 12 | __stderr = e; 13 | } finally { 14 | {{__broadcast__}} 15 | } 16 | })();`; 17 | 18 | const newAdobeScriptBuilder = (): AdobeScriptBuilder => { 19 | 20 | let name: string; 21 | let vars: string; 22 | let body: string; 23 | let broadcast: string; 24 | 25 | const builder = { 26 | setName(value: string) { 27 | name = value; 28 | return builder; 29 | }, 30 | setVariables(value: string) { 31 | vars = value; 32 | return builder; 33 | }, 34 | setBody(value: string) { 35 | body = value; 36 | return builder; 37 | }, 38 | setBroadcast(value: string) { 39 | broadcast = value; 40 | return builder; 41 | }, 42 | build() { 43 | return commandTemplate 44 | .replace('{{__command__}}', name) 45 | .replace('{{__vars__}}', vars) 46 | .replace('{{__fn__}}', body) 47 | .replace('{{__broadcast__}}', broadcast); 48 | } 49 | } 50 | 51 | return builder; 52 | } 53 | 54 | export default newAdobeScriptBuilder; -------------------------------------------------------------------------------- /src/script-file-creator.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as fs from "fs"; 3 | import { AdobeAppName, AdobeAppScriptFileType, CommandFileCreator, AdobeScriptBuilder, BroadcastBuilder, Config, Options } from "./api"; 4 | import newAdobeScriptBuilder from './script-builder'; 5 | import { newBroadcastBuilder } from './broadcast'; 6 | import defaults from './defaults'; 7 | 8 | const scriptingExtension: Map = new Map([ 9 | [AdobeAppName.Animate, AdobeAppScriptFileType.Jsfl], 10 | [AdobeAppName.Photoshop, AdobeAppScriptFileType.Jsx], 11 | [AdobeAppName.Illustrator, AdobeAppScriptFileType.Jsx], 12 | [AdobeAppName.InDesign, AdobeAppScriptFileType.Jsx] 13 | ]); 14 | 15 | const newAdobeScriptFileCreator = (config: Config): CommandFileCreator => { 16 | const appName: AdobeAppName = config.app.name 17 | const jsPath: string = config.jsPath || defaults.scriptsPath; 18 | const adobeScriptsPath: string = config.app.adobeScriptsPath || defaults.adobeScriptsPath; 19 | const scriptBuilder: AdobeScriptBuilder = newAdobeScriptBuilder(); 20 | const broadcastBuilder: BroadcastBuilder = newBroadcastBuilder(config); 21 | 22 | const buildVars = (args: any): string => { 23 | let list: string[] = []; 24 | let arg: any; 25 | for (let name in args) { 26 | arg = args[name]; 27 | 28 | if (arg) { 29 | list.push(`var ${name}=${arg.constructor.name === "String" 30 | ? `"${arg}"` 31 | : arg.constructor.name === "Array" ? `${JSON.stringify(arg)}` 32 | : arg};`); 33 | 34 | } else { 35 | list.push(`var ${name};`); 36 | } 37 | } 38 | 39 | return `${list.length ? list.join('\n') : ''}` 40 | }; 41 | 42 | const buildBody = (command: string, useBuiltInScript: boolean): string => { 43 | const scriptPath: string = path.join(jsPath, appName, `${command}.js`); 44 | const builtInScript: string = path.join(__dirname, '..', 'scripts', appName, `${command}.js`); 45 | 46 | if (useBuiltInScript && fs.existsSync(builtInScript)) { 47 | console.info(`Built-in Script file found: ${builtInScript}`); 48 | return fs.readFileSync(builtInScript).toString(); 49 | } 50 | 51 | if (fs.existsSync(scriptPath)) { 52 | console.info(`Custom script file found: ${scriptPath}`); 53 | return fs.readFileSync(scriptPath).toString(); 54 | } 55 | 56 | return `"";`; 57 | } 58 | 59 | const createFile = (command: string, content: string): Promise => new Promise((resolve, reject) => { 60 | const filePath: string = path.join(adobeScriptsPath, `${command}.${scriptingExtension.get(appName)}`); 61 | const fileDirname: string = path.dirname(filePath); 62 | if(fs.existsSync(fileDirname)) { 63 | fs.writeFile(filePath, content, "utf-8", (err) => { 64 | return err ? reject(err) : resolve(filePath) 65 | }); 66 | } else { 67 | return reject(`The path (${fileDirname}) is not valid.`) 68 | } 69 | }); 70 | 71 | return { 72 | create: (command: string, useBuiltInScript: boolean, args?: Options): Promise => 73 | new Promise((resolve, reject) => { 74 | const variables: string = buildVars(args); 75 | const body: string = buildBody(command, useBuiltInScript); 76 | const broadcast: string = broadcastBuilder.build(command); 77 | 78 | let content: string = scriptBuilder 79 | .setName(command) 80 | .setVariables(variables) 81 | .setBody(body) 82 | .setBroadcast(broadcast) 83 | .build(); 84 | return createFile(command, content).then(resolve).catch(reject); 85 | }) 86 | } 87 | } 88 | 89 | export default newAdobeScriptFileCreator; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "target": "es5", 5 | "lib": ["es7", "dom"], 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "noImplicitAny": true, 10 | "sourceMap": true, 11 | "inlineSources": true, 12 | "experimentalDecorators": true, 13 | "emitDecoratorMetadata": true, 14 | "importHelpers": true, 15 | "noEmit" : false, 16 | "noEmitOnError": true 17 | }, 18 | "include": [ 19 | "./src/**/*.ts" 20 | ] 21 | } --------------------------------------------------------------------------------