├── .gitignore ├── README.md ├── package.json ├── bin └── generator ├── .jshintrc ├── Gruntfile.js ├── lib ├── utils.js ├── config.js ├── versions.js └── stdlog.js ├── test └── test-config.js └── app.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | generator-cli 2 | ============= 3 | 4 | Command line launcher for Photoshop Generator 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-cli", 3 | "version": "0.0.1-dev", 4 | "description": "Command line launcher for Photoshop Generator", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "dependencies": { 10 | "optimist": "~0.3.5", 11 | "q": "0.9.6", 12 | "semver": "~2.1.0", 13 | "resolve": "~0.6.1" 14 | }, 15 | "devDependencies": { 16 | "grunt": "~0.4.1", 17 | "grunt-contrib-jshint": "~0.5.4", 18 | "nodeunit": "~0.8.1", 19 | "grunt-contrib-nodeunit": "~0.2.0" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/joelrbrandt/generator-cli.git" 24 | }, 25 | "bin": { 26 | "generator": "bin/generator" 27 | }, 28 | "preferGlobal": true, 29 | "author": "Joel Brandt ", 30 | "license": "MIT", 31 | "readmeFilename": "README.md", 32 | "bugs": { 33 | "url": "https://github.com/joelrbrandt/generator-cli/issues" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /bin/generator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | "use strict"; 27 | 28 | process.title = "generator"; 29 | 30 | var resolve = require("resolve").sync, 31 | core = null; 32 | 33 | try { 34 | core = resolve("generator-core", {basedir: process.cwd()}); 35 | } catch (e) { 36 | // do nothing 37 | } finally { 38 | if (!core) { 39 | console.error("Unable to find local generator-core."); 40 | process.exit(-1); 41 | } 42 | } 43 | 44 | var generatorCore = require(core), 45 | app = require("../app.js"); 46 | 47 | app.init(generatorCore); 48 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise" : true, 3 | "camelcase" : true, 4 | "curly" : true, 5 | "eqeqeq" : true, 6 | "es3" : false, 7 | "forin" : true, 8 | "immed" : true, 9 | "indent" : 4, 10 | "latedef" : true, 11 | "newcap" : true, 12 | "noarg" : true, 13 | "noempty" : true, 14 | "nonew" : true, 15 | "plusplus" : false, 16 | "quotmark" : "double", 17 | "undef" : true, 18 | "unused" : true, 19 | "strict" : true, 20 | "trailing" : true, 21 | "maxlen" : 120, 22 | 23 | "asi" : false, 24 | "boss" : false, 25 | "debug" : false, 26 | "eqnull" : false, 27 | "es5" : false, 28 | "esnext" : false, 29 | "evil" : false, 30 | "expr" : false, 31 | "funcscope" : false, 32 | "globalstrict" : false, 33 | "iterator" : false, 34 | "lastsemic" : false, 35 | "laxbreak" : false, 36 | "laxcomma" : false, 37 | "loopfunc" : false, 38 | "moz" : false, 39 | "multistr" : false, 40 | "proto" : false, 41 | "scripturl" : false, 42 | "smarttabs" : false, 43 | "shadow" : false, 44 | "sub" : false, 45 | "supernew" : false, 46 | "validthis" : false, 47 | 48 | "browser" : false, 49 | "couch" : false, 50 | "devel" : false, 51 | "dojo" : false, 52 | "jquery" : false, 53 | "mootools" : false, 54 | "node" : true, 55 | "nonstandard" : false, 56 | "prototypejs" : false, 57 | "rhino" : false, 58 | "worker" : false, 59 | "wsh" : false, 60 | "yui" : false, 61 | 62 | "nomen" : false, 63 | "onevar" : false, 64 | "passfail" : false, 65 | "white" : true 66 | } -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | 24 | module.exports = function (grunt) { 25 | "use strict"; 26 | 27 | grunt.initConfig({ 28 | jshint : { 29 | options : { 30 | jshintrc : ".jshintrc" 31 | }, 32 | all : [ 33 | "*.js", 34 | "package.json", 35 | ".jshintrc", 36 | "lib/**/*.js", 37 | "bin/generator" 38 | ] 39 | }, 40 | 41 | nodeunit : { 42 | all : ["test/test-*.js"] 43 | } 44 | 45 | }); 46 | 47 | grunt.loadNpmTasks("grunt-contrib-jshint"); 48 | grunt.loadNpmTasks("grunt-contrib-nodeunit"); 49 | 50 | grunt.registerTask("test", ["jshint", "nodeunit"]); 51 | 52 | grunt.registerTask("default", ["test"]); 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | 24 | (function () { 25 | "use strict"; 26 | 27 | function filterWriteStream(stream, filter) { 28 | var write = stream.write; 29 | 30 | stream.write = function (chunk, encoding, callback) { 31 | // Do not apply filters to buffers, just use them as they are 32 | if (!Buffer.isBuffer(chunk) && filter) { 33 | // The documentation specifies chunk to be of type String or Buffer. 34 | // Let's make sure it's a string anyway so we can use chunk.replace 35 | chunk = filter(String(chunk)); 36 | } 37 | 38 | // Write the chunk to the stream 39 | return write.call(this, chunk, encoding, callback); 40 | }; 41 | } 42 | 43 | function replaceBullet(string) { 44 | // Replace the character that might be turned into BEL 45 | return string.replace(/•/g, "·"); 46 | } 47 | 48 | exports.filterWriteStream = filterWriteStream; 49 | exports.replaceBullet = replaceBullet; 50 | }()); 51 | -------------------------------------------------------------------------------- /test/test-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | 24 | (function () { 25 | "use strict"; 26 | 27 | var merge = require("../lib/config")._merge; 28 | 29 | exports.testMerge = function (test) { 30 | test.deepEqual( 31 | merge({}, {a : 1}), 32 | {a : 1}, 33 | "merge into empty" 34 | ); 35 | 36 | test.deepEqual( 37 | merge({a : 1}, {}), 38 | {a : 1}, 39 | "merge empty into filled" 40 | ); 41 | 42 | test.deepEqual( 43 | merge({a : 1}, {a : 2}), 44 | {a : 2}, 45 | "merge overwrite" 46 | ); 47 | 48 | test.deepEqual( 49 | merge({a : 1}, {a : {b : 2}}), 50 | {a : {b : 2}}, 51 | "merge overwrite primitive with object" 52 | ); 53 | 54 | test.deepEqual( 55 | merge({a : {b : 1}}, {a : 2}), 56 | {a : 2}, 57 | "merge overwrite object with primitive" 58 | ); 59 | 60 | test.deepEqual( 61 | merge({a : {b : 1}}, {a : {c : 2}}), 62 | {a : {b : 1, c : 2}}, 63 | "merge recursive" 64 | ); 65 | 66 | test.deepEqual( 67 | merge({a : {b : 1, c: 3}}, {a : {b : 2, d : 4}}), 68 | {a : {b : 2, c : 3, d : 4}}, 69 | "merge recursive with overwrite" 70 | ); 71 | 72 | test.deepEqual( 73 | merge(1, {a : {b : 2, d : 4}}), 74 | 1, 75 | "merge into primitive" 76 | ); 77 | 78 | test.deepEqual( 79 | merge({a : 1}, 1), 80 | {a : 1}, 81 | "merging in primitive is noop" 82 | ); 83 | 84 | var a = {a : 1}, 85 | b = {b : 2}, 86 | c = merge(a, b); 87 | 88 | test.strictEqual(a, c, "src modified in place"); 89 | test.deepEqual(a, {a : 1, b : 2}, "src modified in place correctly"); 90 | 91 | test.done(); 92 | 93 | }; 94 | }()); -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | 24 | (function () { 25 | "use strict"; 26 | 27 | var resolve = require("path").resolve, 28 | home = process.env[(process.platform === "win32") ? "USERPROFILE" : "HOME"], 29 | cwd = process.cwd(), 30 | configLocations = [ // locations are listed from lowest to highest precedence 31 | resolve(home, ".generator.json"), 32 | resolve(home, ".generator.js"), 33 | resolve(home, "generator.json"), 34 | resolve(home, "generator.js"), 35 | resolve(cwd, ".generator.json"), 36 | resolve(cwd, ".generator.js"), 37 | resolve(cwd, "generator.json"), 38 | resolve(cwd, "generator.js") 39 | ], 40 | config = null; 41 | 42 | // Does a merge of the properties of src into dest. Dest is modified and returned (though the 43 | // return value can be safely ignored). If src and dest are not both objects, then dest is not 44 | // modified. 45 | // 46 | // Important: This is *not* a deep copy and should not be treated as such. Sub-objects of 47 | // src may get added to dest by reference. So, changing sub-properties of src after calling merge 48 | // may change dest. 49 | function merge(dest, src) { 50 | if (dest instanceof Object && src instanceof Object) { 51 | Object.keys(src).forEach(function (key) { 52 | if (!dest[key] || !(dest[key] instanceof Object && src[key] instanceof Object)) { 53 | dest[key] = src[key]; 54 | } else { // both objects, so merge 55 | merge(dest[key], src[key]); 56 | } 57 | }); 58 | } 59 | return dest; 60 | } 61 | 62 | function getConfig() { 63 | if (!config) { 64 | config = {}; 65 | configLocations.forEach(function (loc) { 66 | try { 67 | var addition = require(loc); 68 | if (addition && addition instanceof Object) { 69 | merge(config, addition); 70 | } 71 | console.log("Parsed config file: " + loc); 72 | } catch (e) { 73 | // do nothing 74 | } 75 | }); 76 | } 77 | return config; 78 | } 79 | 80 | exports.getConfig = getConfig; 81 | 82 | // for unit tests 83 | exports._merge = merge; 84 | 85 | }()); -------------------------------------------------------------------------------- /lib/versions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | 24 | (function () { 25 | "use strict"; 26 | 27 | var fs = require("fs"), 28 | resolve = require("path").resolve; 29 | 30 | function logGitInformation(prefix, directory) { 31 | var gitDir = resolve(directory, ".git"), 32 | headFile = resolve(gitDir, "HEAD"); 33 | 34 | try { 35 | if (!fs.existsSync(gitDir)) { 36 | console.log("%s No Git repository found", prefix); 37 | return; 38 | } 39 | if (!fs.statSync(gitDir).isDirectory()) { 40 | console.warn("%s .git is not a directory", prefix); 41 | return; 42 | } 43 | if (!fs.existsSync(headFile)) { 44 | console.warn("%s Did not find HEAD file", prefix); 45 | return; 46 | } 47 | 48 | var head = fs.readFileSync(headFile).toString(), 49 | match = head.match(/ref:\s*([^\r\n]+)/), 50 | ref = match[1], 51 | branch = ref.replace(/^refs\/heads\//, ""), 52 | refFile = resolve(gitDir, ref); 53 | 54 | if (!match) { 55 | console.error("%s Could not interpret HEAD file with contents %j", prefix, head); 56 | return; 57 | } 58 | 59 | var message = "Git branch " + branch; 60 | 61 | if (!fs.existsSync(refFile)) { 62 | console.error("%s Git ref file %j does not exist", prefix, refFile); 63 | } else { 64 | message += " @ " + fs.readFileSync(refFile).toString().trim(); 65 | } 66 | 67 | console.log("%s %s", prefix, message); 68 | 69 | } 70 | catch (e) { 71 | console.error("%s Error while reading Git information: %s", prefix, e.stack); 72 | } 73 | } 74 | 75 | function logPackageInformation(prefix, directory) { 76 | try { 77 | var packageInfoPath = resolve(directory, "package.json"); 78 | if (fs.existsSync(packageInfoPath)) { 79 | var packageInfo = require(packageInfoPath); 80 | console.log("%s Package %s %s", 81 | prefix, 82 | packageInfo.name, 83 | packageInfo.version ? "v" + packageInfo.version : "(version not specified in package.json)"); 84 | } 85 | } catch (e) { 86 | console.warn("Could not read package.json: %s", e.stack); 87 | } 88 | } 89 | 90 | exports.logGitInformation = logGitInformation; 91 | exports.logPackageInformation = logPackageInformation; 92 | }()); -------------------------------------------------------------------------------- /lib/stdlog.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | 24 | (function () { 25 | "use strict"; 26 | 27 | var fs = require("fs"), 28 | resolve = require("path").resolve; 29 | 30 | var LOG_SIZE_LIMIT = 50 * 1024 * 1024; // 50 MB 31 | 32 | var REGULAR_LOG_NAMES = [ 33 | "generator_latest.txt", 34 | "generator_1.txt", 35 | "generator_2.txt", 36 | "generator_3.txt", 37 | "generator_4.txt" 38 | ]; 39 | 40 | var EXCEPTION_LOG_NAME = "generator_exceptions.txt"; 41 | 42 | var _logDir, 43 | _logBytesWritten = 0, 44 | _logFilePaths = []; 45 | 46 | 47 | function handleUncaughtException(err) { 48 | var logFile = resolve(_logDir, EXCEPTION_LOG_NAME); 49 | 50 | var lines = []; 51 | lines.push("Uncaught exception on " + new Date() + ":"); 52 | if (!err) { 53 | lines.push("Error value was " + JSON.stringify(err)); 54 | } else if (err.stack) { 55 | lines.push("Error trace: " + err.stack); 56 | } else { 57 | lines.push("Error object: " + JSON.stringify(err, null, " ")); 58 | } 59 | lines.push("", ""); 60 | lines = lines.join("\n"); 61 | 62 | console.error(lines); 63 | 64 | try { 65 | fs.appendFileSync(logFile, lines); 66 | } catch (e) { 67 | // do nothing 68 | } 69 | } 70 | 71 | // Use tail -F logs/generator_latest.txt to see the latest output 72 | 73 | function writeToLog(data, encoding) { 74 | _logFilePaths.forEach(function (path) { 75 | try { 76 | fs.appendFileSync(path, data, { encoding: encoding }); 77 | } catch (e) { 78 | // do nothing 79 | } 80 | }); 81 | } 82 | 83 | function logStream(stream, colorFunction) { 84 | var write = stream.write; 85 | 86 | // The third parameter, callback, will be passed implicitely using arguments 87 | stream.write = function (chunk, encoding) { 88 | // Write to STDOUT right away 89 | try { 90 | write.apply(this, arguments); 91 | } catch (streamWriteError) { } 92 | 93 | // We stopped logging in a previous call to write: return now 94 | if (_logBytesWritten > LOG_SIZE_LIMIT) { return; } 95 | 96 | var chunkLength; 97 | 98 | if (!Buffer.isBuffer(chunk)) { 99 | chunk = String(chunk); 100 | if (colorFunction) { 101 | chunk = colorFunction(chunk); 102 | } 103 | chunkLength = Buffer.byteLength(chunk, encoding); 104 | } else { 105 | chunkLength = chunk.length; 106 | } 107 | 108 | // Calculate the new log file size 109 | _logBytesWritten += chunkLength; 110 | 111 | // Limit not yet reached: write to log file 112 | if (_logBytesWritten > LOG_SIZE_LIMIT) { 113 | writeToLog("The log file size limit of " + LOG_SIZE_LIMIT + " bytes was reached." + 114 | " No further output will be written to this file."); 115 | return; 116 | } 117 | 118 | writeToLog(chunk, encoding); 119 | }; 120 | } 121 | 122 | // Define the log directory as an array so we can easily create the individual 123 | // subdirectories if necessary, without requiring an external library like mkdirp 124 | function getLogDirectoryElements(settings) { 125 | var elements, 126 | platform = process.platform; 127 | 128 | if (platform === "darwin") { 129 | elements = [process.env.HOME, "Library", "Logs", settings.vendor, settings.application, settings.module]; 130 | } 131 | else if (platform === "win32") { 132 | elements = [process.env.APPDATA, settings.vendor, settings.application, settings.module, "logs"]; 133 | } 134 | else { 135 | elements = [process.env.HOME, settings.vendor, settings.application, settings.module, "logs"]; 136 | } 137 | 138 | return elements; 139 | } 140 | 141 | function createDirectoryWithElements(elements) { 142 | var path = elements.shift(); 143 | 144 | // Assume the first element exists 145 | while (elements.length) { 146 | path = resolve(path, elements.shift()); 147 | if (!fs.existsSync(path)) { 148 | fs.mkdirSync(path); 149 | } 150 | } 151 | 152 | return path; 153 | } 154 | 155 | function rotateLogs() { 156 | var i; 157 | for (i = REGULAR_LOG_NAMES.length - 1; i > 0; i--) { 158 | try { 159 | fs.renameSync( 160 | resolve(_logDir, REGULAR_LOG_NAMES[i - 1]), 161 | resolve(_logDir, REGULAR_LOG_NAMES[i]) 162 | ); 163 | } catch (e) { } 164 | } 165 | } 166 | 167 | function setup(settings) { 168 | try { 169 | // Create the log file directory 170 | _logDir = createDirectoryWithElements(getLogDirectoryElements(settings)); 171 | } catch (e) { 172 | // Do nothing except report the error 173 | console.error(e); 174 | } 175 | 176 | console.log("Log directory:", _logDir); 177 | 178 | // The directory now exists, so we can log exceptions there 179 | process.on("uncaughtException", handleUncaughtException); 180 | 181 | rotateLogs(); 182 | 183 | _logFilePaths.push(resolve(_logDir, REGULAR_LOG_NAMES[0])); 184 | 185 | logStream(process.stdout); 186 | // Print output via STDERR in red 187 | logStream(process.stderr, function (string) { 188 | return "\x1B[1;31m" + string + "\x1B[0m"; 189 | }); 190 | 191 | writeToLog("Log file created on " + new Date() + "\n\n"); 192 | } 193 | 194 | exports.setup = setup; 195 | }()); -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | 24 | (function () { 25 | "use strict"; 26 | 27 | var utils = require("./lib/utils"), 28 | versions = require("./lib/versions"); 29 | 30 | var PLUGIN_KEY_PREFIX = "PLUGIN-"; 31 | 32 | // On Windows, the bullet character is sometimes replaced with the bell character BEL (0x07). 33 | // This causes Windows to make a beeping noise every time • is printed to the console. 34 | // Use · instead. This needs to happen before adding stdlog to not affect the log files. 35 | if (process.platform === "win32") { 36 | utils.filterWriteStream(process.stdout, utils.replaceBullet); 37 | utils.filterWriteStream(process.stderr, utils.replaceBullet); 38 | } 39 | 40 | require("./lib/stdlog").setup({ 41 | vendor: "Adobe", 42 | application: "Adobe Photoshop CC", 43 | module: "Generator" 44 | }); 45 | 46 | 47 | var util = require("util"), 48 | config = require("./lib/config").getConfig(), 49 | generator = null, 50 | Q = require("q"), 51 | optimist = require("optimist"); 52 | 53 | 54 | var optionParser = optimist["default"]({ 55 | "r" : "independent", 56 | "m" : null, 57 | "p" : 49494, 58 | "h" : "localhost", 59 | "P" : "password", 60 | "i" : null, 61 | "o" : null, 62 | "f" : ".", 63 | }); 64 | 65 | var argv = optionParser 66 | .usage("Run generator service.\nUsage: $0") 67 | .describe({ 68 | "r": "launch reason, one of: independent, menu, metadata, alwayson", 69 | "m": "menu ID of action that should be executed immediately after startup", 70 | "p": "Photoshop server port", 71 | "h": "Photoshop server host", 72 | "P": "Photoshop server password", 73 | "i": "file descriptor of input pipe", 74 | "o": "file descriptor of output pipe", 75 | "f": "folder to search for plugins (can be used multiple times)", 76 | "help": "display help message" 77 | }).alias({ 78 | "r": "launchreason", 79 | "m": "menu", 80 | "p": "port", 81 | "h": "host", 82 | "P": "password", 83 | "i": "input", 84 | "o": "output", 85 | "f": "pluginfolder", 86 | }).argv; 87 | 88 | if (argv.help) { 89 | console.log(optimist.help()); 90 | process.exit(0); 91 | } 92 | 93 | function stop(exitCode, reason) { 94 | if (!reason) { 95 | reason = "no reason given"; 96 | } 97 | console.error("Exiting with code " + exitCode + ": " + reason); 98 | process.exit(exitCode); 99 | } 100 | 101 | function scanPluginDirectories(folders, theGenerator) { 102 | var allPlugins = []; 103 | 104 | function listPluginsInDirectory(directory) { 105 | 106 | function verifyPluginAtPath(absolutePath) { 107 | var result = null, 108 | metadata = null, 109 | compatibility = null; 110 | 111 | try { 112 | metadata = theGenerator.getPluginMetadata(absolutePath); 113 | compatibility = theGenerator.checkPluginCompatibility(metadata); 114 | if (compatibility.compatible) { 115 | result = { 116 | path: absolutePath, 117 | metadata: metadata 118 | }; 119 | } 120 | } catch (metadataLoadError) { 121 | // Do nothing 122 | } 123 | return result; 124 | } 125 | 126 | // relative paths are resolved relative to the current working directory 127 | var resolve = require("path").resolve, 128 | fs = require("fs"), 129 | absolutePath = resolve(process.cwd(), directory), 130 | plugins = [], 131 | potentialPlugin = null; 132 | 133 | if (!fs.statSync(absolutePath).isDirectory()) { 134 | console.error("Error: specified plugin path '%s' is not a directory", absolutePath); 135 | return plugins; 136 | } 137 | 138 | console.log("Scanning for plugins in", absolutePath); 139 | 140 | // First, try treating the directory as a plugin 141 | 142 | potentialPlugin = verifyPluginAtPath(absolutePath); 143 | if (potentialPlugin) { 144 | plugins.push(potentialPlugin); 145 | } 146 | 147 | // If we didn't find a compatible plugin at the root level, 148 | // then scan one level deep for plugins 149 | if (plugins.length === 0) { 150 | fs.readdirSync(absolutePath) 151 | .map(function (child) { 152 | return resolve(absolutePath, child); 153 | }) 154 | .filter(function (absoluteChildPath) { 155 | return fs.statSync(absoluteChildPath).isDirectory(); 156 | }) 157 | .forEach(function (absolutePluginPath) { 158 | potentialPlugin = verifyPluginAtPath(absolutePluginPath); 159 | if (potentialPlugin) { 160 | plugins.push(potentialPlugin); 161 | } 162 | }); 163 | } 164 | 165 | return plugins; 166 | } 167 | 168 | if (!util.isArray(folders)) { 169 | folders = [folders]; 170 | } 171 | 172 | folders.forEach(function (f) { 173 | try { 174 | allPlugins = allPlugins.concat(listPluginsInDirectory(f)); 175 | } catch (e) { 176 | console.error("Error processing plugin directory %s\n", f, e); 177 | } 178 | }); 179 | 180 | return allPlugins; 181 | } 182 | 183 | function setupGenerator() { 184 | var deferred = Q.defer(); 185 | var theGenerator = generator.createGenerator(); 186 | 187 | // NOTE: It *should* be the case that node automatically cleans up all pipes/sockets 188 | // on exit. However, on node v0.10.15 mac 64-bit there seems to be a bug where 189 | // the native-side process exit hangs if node is blocked on the read of a pipe. 190 | // This meant that if Generator had an unhandled exception after starting to read 191 | // from PS's pipe, the node process wouldn't fully exit until PS closed the pipe. 192 | process.on("exit", function () { 193 | if (theGenerator) { 194 | theGenerator.shutdown(); 195 | } 196 | }); 197 | 198 | theGenerator.on("close", function () { 199 | setTimeout(function () { 200 | console.log("Exiting"); 201 | stop(0, "generator close event"); 202 | }, 1000); 203 | }); 204 | 205 | var options = {}; 206 | if ((typeof argv.input === "number" && typeof argv.output === "number") || 207 | (typeof argv.input === "string" && typeof argv.output === "string")) { 208 | options.inputFd = argv.input; 209 | options.outputFd = argv.output; 210 | options.password = null; // No encryption over pipes 211 | } else if (typeof argv.port === "number" && argv.host && argv.password) { 212 | options.port = argv.port; 213 | options.host = argv.host; 214 | options.password = argv.password; 215 | } 216 | 217 | options.config = config; 218 | 219 | theGenerator.start(options).done( 220 | function () { 221 | console.log("[init] Generator started!"); 222 | 223 | var semver = require("semver"), 224 | totalPluginCount = 0, 225 | pluginMap = {}, 226 | plugins = scanPluginDirectories(argv.pluginfolder, theGenerator); 227 | 228 | // Ensure all plugins have a valid semver, then put them in to a map 229 | // keyed on plugin name 230 | plugins.forEach(function (p) { 231 | if (!semver.valid(p.metadata.version)) { 232 | p.metadata.version = "0.0.0"; 233 | } 234 | if (!pluginMap[PLUGIN_KEY_PREFIX + p.metadata.name]) { 235 | pluginMap[PLUGIN_KEY_PREFIX + p.metadata.name] = []; 236 | } 237 | pluginMap[PLUGIN_KEY_PREFIX + p.metadata.name].push(p); 238 | }); 239 | 240 | // For each unique plugin name, try to load a plugin with that name 241 | // in decending order of version 242 | Object.keys(pluginMap).forEach(function (pluginSetKey) { 243 | var pluginSet = pluginMap[pluginSetKey], 244 | i, 245 | loaded = false; 246 | 247 | pluginSet.sort(function (a, b) { 248 | return semver.rcompare(a.metadata.version, b.metadata.version); 249 | }); 250 | 251 | console.log("Processing plugin set: " + JSON.stringify(pluginSet)); 252 | 253 | for (i = 0; i < pluginSet.length; i++) { 254 | try { 255 | theGenerator.loadPlugin(pluginSet[i].path); 256 | loaded = true; 257 | var logPrefix = "[" + pluginSet[i].metadata.name + "]"; 258 | versions.logPackageInformation(logPrefix, pluginSet[i].path); 259 | versions.logGitInformation(logPrefix, pluginSet[i].path); 260 | } catch (loadingException) { 261 | console.error("Unable to load plugin at '" + pluginSet[i].path + "': " + 262 | loadingException.message); 263 | } 264 | 265 | if (loaded) { 266 | totalPluginCount++; 267 | break; 268 | } 269 | } 270 | 271 | }); 272 | 273 | 274 | if (totalPluginCount === 0) { 275 | // Without any plugins, Generator will never do anything. So, we exit. 276 | deferred.reject("Generator requires at least one plugin to function, zero were loaded."); 277 | } else { 278 | deferred.resolve(theGenerator); 279 | } 280 | }, 281 | function (err) { 282 | deferred.reject(err); 283 | } 284 | ); 285 | 286 | return deferred.promise; 287 | } 288 | 289 | function init(generatorCore) { 290 | process.on("uncaughtException", function (err) { 291 | if (err) { 292 | if (err.stack) { 293 | console.error(err.stack); 294 | } else { 295 | console.error(err); 296 | } 297 | } 298 | 299 | stop(-1, "uncaught exception" + (err ? (": " + err.message) : "undefined")); 300 | }); 301 | 302 | generator = generatorCore; 303 | 304 | var os = require("os"), 305 | versions = require("./lib/versions"); 306 | 307 | versions.logPackageInformation("[init]", __dirname); 308 | versions.logGitInformation("[init]", __dirname); 309 | 310 | // Record command line arguments 311 | console.log("[init] Node.js version: %j", process.versions); 312 | console.log("[init] OS: %s %s (%s), platform: %s", os.type(), os.release(), os.arch(), process.platform); 313 | console.log("[init] unparsed command line: %j", process.argv); 314 | console.log("[init] parsed command line: %j", argv); 315 | 316 | // Start async process to initialize generator 317 | setupGenerator().done( 318 | function () { 319 | console.log("Generator initialized"); 320 | }, 321 | function (err) { 322 | stop(-3, "generator failed to initialize: " + err); 323 | } 324 | ); 325 | } 326 | 327 | exports.init = init; 328 | 329 | }()); 330 | --------------------------------------------------------------------------------