├── .gitignore ├── Example ├── ASSETS │ ├── FLASH │ │ ├── CSXS │ │ │ └── manifest.xml │ │ └── example.swf │ ├── HTML │ │ ├── CSXS │ │ │ └── manifest.xml │ │ └── example.html │ ├── MAC_PLUGIN │ │ └── example.plugin │ ├── SCRIPT │ │ ├── Folder A │ │ │ ├── Subfolder A │ │ │ │ └── fileD.jsx │ │ │ └── fileC.jsx │ │ ├── Folder B │ │ │ └── fileE.jsx │ │ ├── fileA.jsx │ │ └── fileB.jsx │ ├── WIN_PLUGIN │ │ └── example.8bf │ └── readme.txt ├── init.json └── installer.jsx ├── LICENSE ├── PS-Installer.coffee ├── PS-Installer.js ├── README.md └── init.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | draft -------------------------------------------------------------------------------- /Example/ASSETS/FLASH/CSXS/manifest.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undavide/PS-Installer/8725332792c37751a5c3d3790e84590a94bdeee8/Example/ASSETS/FLASH/CSXS/manifest.xml -------------------------------------------------------------------------------- /Example/ASSETS/FLASH/example.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undavide/PS-Installer/8725332792c37751a5c3d3790e84590a94bdeee8/Example/ASSETS/FLASH/example.swf -------------------------------------------------------------------------------- /Example/ASSETS/HTML/CSXS/manifest.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undavide/PS-Installer/8725332792c37751a5c3d3790e84590a94bdeee8/Example/ASSETS/HTML/CSXS/manifest.xml -------------------------------------------------------------------------------- /Example/ASSETS/HTML/example.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undavide/PS-Installer/8725332792c37751a5c3d3790e84590a94bdeee8/Example/ASSETS/HTML/example.html -------------------------------------------------------------------------------- /Example/ASSETS/MAC_PLUGIN/example.plugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undavide/PS-Installer/8725332792c37751a5c3d3790e84590a94bdeee8/Example/ASSETS/MAC_PLUGIN/example.plugin -------------------------------------------------------------------------------- /Example/ASSETS/SCRIPT/Folder A/Subfolder A/fileD.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undavide/PS-Installer/8725332792c37751a5c3d3790e84590a94bdeee8/Example/ASSETS/SCRIPT/Folder A/Subfolder A/fileD.jsx -------------------------------------------------------------------------------- /Example/ASSETS/SCRIPT/Folder A/fileC.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undavide/PS-Installer/8725332792c37751a5c3d3790e84590a94bdeee8/Example/ASSETS/SCRIPT/Folder A/fileC.jsx -------------------------------------------------------------------------------- /Example/ASSETS/SCRIPT/Folder B/fileE.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undavide/PS-Installer/8725332792c37751a5c3d3790e84590a94bdeee8/Example/ASSETS/SCRIPT/Folder B/fileE.jsx -------------------------------------------------------------------------------- /Example/ASSETS/SCRIPT/fileA.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undavide/PS-Installer/8725332792c37751a5c3d3790e84590a94bdeee8/Example/ASSETS/SCRIPT/fileA.jsx -------------------------------------------------------------------------------- /Example/ASSETS/SCRIPT/fileB.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undavide/PS-Installer/8725332792c37751a5c3d3790e84590a94bdeee8/Example/ASSETS/SCRIPT/fileB.jsx -------------------------------------------------------------------------------- /Example/ASSETS/WIN_PLUGIN/example.8bf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undavide/PS-Installer/8725332792c37751a5c3d3790e84590a94bdeee8/Example/ASSETS/WIN_PLUGIN/example.8bf -------------------------------------------------------------------------------- /Example/ASSETS/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | README -------------------------------------------------------------------------------- /Example/init.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | /* INFO ======================================= */ 4 | COMPANY: "CS-EXTENSIONS", 5 | CONTACT_INFO: "support@cs-extensions.com", 6 | PRODUCT_NAME: "Test product", 7 | PRODUCT_ID: "com.cs-extensions.test", 8 | PRODUCT_VERSION: "0.3.0", 9 | 10 | /* PRODUCTS =================================== */ 11 | 12 | /* Photoshop versions range */ 13 | 14 | /* Leave undefined for no limit */ 15 | MIN_VERSION: void 0, 16 | MAX_VERSION: void 0, 17 | 18 | /* Product Source Folders */ 19 | 20 | /* Leave undefined for no product to install */ 21 | 22 | /* Make sure tha paths (relative to the installer.jsx) do NOT start or end with "/" */ 23 | HTML_PANEL: "ASSETS/HTML", 24 | FLASH_PANEL: "ASSETS/FLASH", 25 | SCRIPT: "ASSETS/SCRIPT", 26 | MAC_PLUGIN: "ASSETS/MAC_PLUGIN", 27 | WIN_PLUGIN: "ASSETS/WIN_PLUGIN", 28 | EXTRA: void 0, 29 | 30 | /* If you have a readme file to display, put it here (path and filename) */ 31 | README: "ASSETS/readme.txt", 32 | 33 | /* System vs. User installation */ 34 | SYSTEM_INSTALL: false, 35 | /* Use PS shared folder for plugins */ 36 | SHARED_PLUGINS: false, 37 | 38 | /* DEBUG ===================================== */ 39 | INSTALLER_VERSION: "0.1.2", 40 | ENABLE_LOG: true, 41 | LOG_FILE_PATH: "~/Desktop", 42 | 43 | /* will be created as LOG_FILE_PATH / PRODUCT_NAME.log */ 44 | LOG_FILE: "", 45 | 46 | /* Array of RegExp for Files to be ignored (escape "\" -> "\\") 47 | Possibly a very bad idea, because the HTML Panel Signing and Timestamping 48 | may easily get corrupted if something is missing in the folder. 49 | Examples: 50 | IGNORE : [ 51 | "^\\.\\w+", // starting with . 52 | "^\\_\\w+", // starting with _ 53 | ] 54 | Leaving undefined means: don't ignore 55 | */ 56 | IGNORE: void 0, 57 | 58 | /* UTILS ===================================== */ 59 | CURRENT_PATH: File($.fileName).path, 60 | CURRENT_PS_VERSION: app.version.split('.')[0] 61 | }; -------------------------------------------------------------------------------- /Example/installer.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * =============================================== 3 | * = Adobe Photoshop Add-ons Installer = 4 | * = (Because Adobe Extension Manager Sucks) = 5 | * =============================================== 6 | * 7 | * Copyright: (c)2015-2019, Davide Barranca 8 | * www.davidebarranca.com || www.cs-extensions.com 9 | * MIT License: http://opensource.org/licenses/MIT 10 | * 11 | * Thanks to xbytor for his xtools installer, 12 | * which has been a source of inspiration! 13 | * 14 | * =============================================== 15 | */ 16 | 17 | /* 18 | * Global object from the JSON 19 | */ 20 | var PSInstaller, PSU, e, errorMessage, initFile, initFileString, psInstaller; 21 | 22 | initFile = new File((File($.fileName).path) + "/init.json"); 23 | 24 | if (!initFile.exists) { 25 | alert("Broken installer!\nThe init.json file is missing...\nPlease get in touch with the original developer."); 26 | throw new Error(); 27 | } 28 | 29 | initFile.open("r"); 30 | 31 | initFileString = initFile.read(); 32 | 33 | initFile.close(); 34 | 35 | eval("var G = " + initFileString); 36 | 37 | 38 | /* 39 | * Utility functions 40 | * Mostly borrowed from xbytor's xtools installer 41 | * http://sourceforge.net/projects/ps-scripts/files/xtools/ 42 | * License: http://www.opensource.org/licenses/bsd-license.php 43 | */ 44 | 45 | PSU = (function(GLOBAL) { 46 | var createFolder, exceptionMessage, init, isMac, isWindows, log, that, throwFileError; 47 | that = this; 48 | this.enableLog = void 0; 49 | this.logFile = void 0; 50 | this.logFilePointer = void 0; 51 | isWindows = function() { 52 | return $.os.match(/windows/i); 53 | }; 54 | isMac = function() { 55 | return !isWindows(); 56 | }; 57 | throwFileError = function(f, msg) { 58 | if (msg == null) { 59 | msg = ''; 60 | } 61 | return Error.runtimeError(9002, msg + "\"" + f + "\": " + f.error + "."); 62 | }; 63 | exceptionMessage = function(e) { 64 | var fname, str; 65 | fname = !e.fileName ? '???' : decodeURI(e.fileName); 66 | str = "\tMessage: " + e.message + "\n\tFile: " + fname + "\n\tLine: " + (e.line || '???') + "\n\tError Name: " + e.name + "\n\tError Number: " + e.number; 67 | if ($.stack) { 68 | str += "\t" + $.stack; 69 | } 70 | return str; 71 | }; 72 | log = function(msg) { 73 | var file; 74 | if (!that.enableLog) { 75 | return; 76 | } 77 | file = that.logFilePointer; 78 | if (!file.open('e')) { 79 | throwFileError(file, "Unable to open Log file"); 80 | } 81 | file.seek(0, 2); 82 | if (!file.writeln("" + msg)) { 83 | return throwFileError(file, "Unable to write to log file"); 84 | } 85 | }; 86 | createFolder = function(fptr) { 87 | var rc; 88 | if (fptr == null) { 89 | Error.runtimeError(19, "No Folder name specified"); 90 | } 91 | if (fptr.constructor === String) { 92 | fptr = new Folder(fptr); 93 | } 94 | 95 | /* Recursion if the arg is a File */ 96 | if (fptr instanceof File) { 97 | return createFolder(fptr.parent); 98 | } 99 | 100 | /* Are we done? */ 101 | if (fptr.exists) { 102 | return true; 103 | } 104 | if (!(fptr instanceof Folder)) { 105 | log(fptr.constructor); 106 | Error.runtimeError(21, "Folder is not a Folder?"); 107 | } 108 | if ((fptr.parent != null) && !fptr.parent.exists) { 109 | if (!createFolder(fptr.parent)) { 110 | return false; 111 | } 112 | } 113 | 114 | /* eventually...! */ 115 | rc = fptr.create(); 116 | if (!rc) { 117 | Error.runtimeError(9002, "Unable to create folder " + fptr + " (" + fptr.error + ")\nPlease create it manually and run this script again."); 118 | } 119 | return rc; 120 | }; 121 | 122 | /* 123 | * Sets up the logging 124 | * @param {string} logFile Log file name 125 | * @param {Boolean} isLogEnabled To log or not to log... 126 | * @return {void} 127 | */ 128 | init = function(logFile, isLogEnabled) { 129 | var file; 130 | if (!isLogEnabled) { 131 | return; 132 | } 133 | 134 | /* LOG Stuff */ 135 | that.enableLog = isLogEnabled; 136 | that.logFile = logFile; 137 | 138 | /* Create Log File Pointer */ 139 | file = new File(that.logFile); 140 | if (file.exists) { 141 | file.remove(); 142 | } 143 | if (!file.open('w')) { 144 | throwFileError(file, "Unable to open Log file"); 145 | } 146 | if (isMac()) { 147 | file.lineFeed = 'unix'; 148 | } 149 | that.logFilePointer = file; 150 | }; 151 | return { 152 | "isMac": isMac, 153 | "exceptionMessage": exceptionMessage, 154 | "log": log, 155 | "createFolder": createFolder, 156 | "init": init 157 | }; 158 | })(this); 159 | 160 | PSInstaller = (function() { 161 | 162 | /* 163 | * Set globals and start the logging 164 | * @return {void} 165 | */ 166 | function PSInstaller() { 167 | 168 | /* set the log file name */ 169 | G.LOG_FILE = G.LOG_FILE_PATH + "/" + G.PRODUCT_NAME + ".log"; 170 | 171 | /* init the logging */ 172 | PSU.init(G.LOG_FILE, G.ENABLE_LOG); 173 | 174 | /* SCRIPT, FLASH_PANEL, etc. */ 175 | this.productsToInstall = []; 176 | 177 | /* All the folders to be copied (relative to the ASSETS path) */ 178 | this.foldersList = []; 179 | 180 | /* List of Deployed Files and Folder - to be used by the uninstaller */ 181 | this.installedFiles = []; 182 | this.installedFolders = []; 183 | PSU.log("=======================================\n " + (new Date()) + "\n \tCompany: " + G.COMPANY + "\n \tProduct: " + G.PRODUCT_NAME + "\n \tProduct version: " + G.PRODUCT_VERSION + "\n \tApp: " + BridgeTalk.appName + "\n \tApp Version: " + app.version + "\n \tOS: " + $.os + "\n \tLocale: " + $.locale + "\n ---------------------------------------\n \tInstaller Version: " + G.INSTALLER_VERSION + "\n ======================================="); 184 | return; 185 | } 186 | 187 | 188 | /* 189 | * App compatibility check 190 | * @return {} 191 | */ 192 | 193 | PSInstaller.prototype.preflight = function() { 194 | var ref; 195 | G.MIN_VERSION = G.MIN_VERSION || 0; 196 | G.MAX_VERSION = G.MAX_VERSION || 99; 197 | PSU.log("\nPreflight \n----------------------------"); 198 | if ((G.MIN_VERSION <= (ref = G.CURRENT_PS_VERSION) && ref <= G.MAX_VERSION)) { 199 | PSU.log("OK: PS version " + G.CURRENT_PS_VERSION + " in the range [" + G.MIN_VERSION + ", " + G.MAX_VERSION + "]"); 200 | alert(G.COMPANY + " - " + G.PRODUCT_NAME + "\nPress OK to start the installation.\nThe process is going to be completed in a short while."); 201 | return true; 202 | } else { 203 | PSU.log("\nFAIL: PS version " + G.CURRENT_PS_VERSION + " not in the range [" + G.MIN_VERSION + ", " + G.MAX_VERSION + "]"); 204 | return Error.runtimeError(9002, "Bad Photoshop version.\n" + G.CURRENT_PS_VERSION + " not in the range [" + G.MIN_VERSION + ", " + G.MAX_VERSION + "]"); 205 | } 206 | }; 207 | 208 | 209 | /* 210 | * Depending on the PS version, sets the available products options 211 | * to install (@productsToInstall) and log them 212 | * @return {void} 213 | */ 214 | 215 | PSInstaller.prototype.init = function() { 216 | var dependencyObj, j, len, product, ref, that; 217 | that = this; 218 | 219 | /* Depending on the PS version, what to install 220 | (not the classiest way I know but it works) 221 | */ 222 | dependencyObj = { 223 | "10": ["SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 224 | "11": ["FLASH_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 225 | "12": ["FLASH_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 226 | "13": ["FLASH_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 227 | "14": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 228 | "15": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 229 | "16": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 230 | "17": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 231 | "18": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 232 | "19": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 233 | "20": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 234 | "21": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] 235 | }; 236 | 237 | /* Array */ 238 | this.productsToInstall = dependencyObj[G.CURRENT_PS_VERSION]; 239 | PSU.log("\nItems to be installed \n----------------------------"); 240 | ref = this.productsToInstall; 241 | for (j = 0, len = ref.length; j < len; j++) { 242 | product = ref[j]; 243 | PSU.log("- " + product); 244 | } 245 | }; 246 | 247 | 248 | /* 249 | * TODO!! 250 | * @param {[Object]} oldVersionsObj An Object containing info about stuff to clean before installing new ones 251 | * @return {[void]} 252 | */ 253 | 254 | PSInstaller.prototype.clean = function(oldVersionsObj) { 255 | 256 | /* 257 | * Delete a Folder (recursively, with ALL its content) 258 | * or a single file 259 | * @param {File or Folder object} thingToDestroy The object to remove 260 | */ 261 | var recursivelyDeleteThing, recursivelyDeleteThingFromFolder; 262 | recursivelyDeleteThing = function(thingToDestroy) { 263 | var aFile, aFolder, e, inFiles, inFolders, j, k, len, len1, processFolder; 264 | inFolders = []; 265 | inFiles = []; 266 | processFolder = function(fold) { 267 | var aThing, fileList, j, len, results; 268 | fileList = fold.getFiles(); 269 | results = []; 270 | for (j = 0, len = fileList.length; j < len; j++) { 271 | aThing = fileList[j]; 272 | if (aThing instanceof Folder) { 273 | inFolders.push(aThing); 274 | results.push(processFolder(aThing)); 275 | } else { 276 | results.push(inFiles.push(aThing)); 277 | } 278 | } 279 | return results; 280 | }; 281 | if (thingToDestroy instanceof File) { 282 | try { 283 | thingToDestroy.remove(); 284 | } catch (_error) { 285 | e = _error; 286 | } 287 | return; 288 | } 289 | if (thingToDestroy instanceof Folder) { 290 | processFolder(thingToDestroy); 291 | inFolders.unshift(thingToDestroy); 292 | inFolders.reverse(); 293 | for (j = 0, len = inFiles.length; j < len; j++) { 294 | aFile = inFiles[j]; 295 | try { 296 | aFile.remove(); 297 | } catch (_error) { 298 | e = _error; 299 | } 300 | } 301 | for (k = 0, len1 = inFolders.length; k < len1; k++) { 302 | aFolder = inFolders[k]; 303 | try { 304 | aFolder.remove(); 305 | } catch (_error) { 306 | e = _error; 307 | } 308 | } 309 | return; 310 | } 311 | }; 312 | 313 | /* 314 | * Delete recursively a File or Folder which CONTAINS a string, 315 | * e.g. recursivelyDeleteThing("this", "~/Desktop/TEST") will catch 316 | * "com.this.example" and so on and so forth 317 | * @param {String} thingToDestroy What the File or Folder name should contain 318 | * @param {String} startFolder The start folder 319 | */ 320 | recursivelyDeleteThingFromFolder = function(thingToDestroy, startFolder) { 321 | var aFile, aFolder, escapeRegExp, inFiles, inFolders, j, k, len, len1, matchString, processFolder, re; 322 | inFolders = []; 323 | inFiles = []; 324 | escapeRegExp = function(stringToGoIntoTheRegex) { 325 | if (stringToGoIntoTheRegex instanceof Folder) { 326 | stringToGoIntoTheRegex = stringToGoIntoTheRegex.name; 327 | } 328 | return stringToGoIntoTheRegex.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); 329 | }; 330 | processFolder = function(fold) { 331 | var aThing, fileList, j, len, results; 332 | fileList = fold.getFiles(); 333 | results = []; 334 | for (j = 0, len = fileList.length; j < len; j++) { 335 | aThing = fileList[j]; 336 | if (aThing instanceof Folder) { 337 | inFolders.push(aThing); 338 | results.push(processFolder(aThing)); 339 | } else { 340 | results.push(inFiles.push(aThing)); 341 | } 342 | } 343 | return results; 344 | }; 345 | processFolder(Folder(startFolder)); 346 | matchString = escapeRegExp(thingToDestroy); 347 | re = new RegExp(matchString, "gi"); 348 | for (j = 0, len = inFiles.length; j < len; j++) { 349 | aFile = inFiles[j]; 350 | if (aFile.name.match(re)) { 351 | aFile.remove(); 352 | } 353 | } 354 | for (k = 0, len1 = inFolders.length; k < len1; k++) { 355 | aFolder = inFolders[k]; 356 | if (aFolder.name.match(re)) { 357 | recursivelyDeleteThing(aFolder); 358 | } 359 | } 360 | }; 361 | }; 362 | 363 | PSInstaller.prototype.copy = function() { 364 | var allFiles, copyFiles, createRelativeFolder, destinationFolder, destinationPath, eachFolder, getFoldersList, ignoreRegExp, j, k, len, len1, panelsPath, pluginsPath, product, ref, ref1, results, saveFolder, scriptsPath, sourceFolder, that; 365 | that = this; 366 | 367 | /* 368 | * [createRelativeFolder description] 369 | * @param {folder} destination the destination Folder 370 | * @param {folder} origin the original, existing Folder 371 | * @param {folder} base the Folder used as a base 372 | * @return {folder} a new created folder in the destination 373 | */ 374 | createRelativeFolder = function(destination, origin, base) { 375 | var destinationArray, destinationToWriteArray, destinationToWriteFolder, destinationToWriteString, originArray, originBaseArray; 376 | destinationArray = decodeURI(destination).toString().split('/'); 377 | originArray = decodeURI(origin).toString().split('/'); 378 | originBaseArray = decodeURI(base).toString().split('/'); 379 | originArray.splice(0, originBaseArray.length); 380 | destinationToWriteArray = destinationArray.concat(originArray); 381 | destinationToWriteString = destinationToWriteArray.join('/'); 382 | destinationToWriteFolder = Folder(destinationToWriteString); 383 | if (!destinationToWriteFolder.exists) { 384 | destinationToWriteFolder.create(); 385 | } 386 | PSU.log("Created Folder:\t" + destinationToWriteFolder.fsName); 387 | that.installedFolders.push("" + destinationToWriteFolder.fsName); 388 | return destinationToWriteFolder; 389 | }; 390 | 391 | /* 392 | * process the folder and fills the external 393 | * array of folders foldersList 394 | * @param {folder} folder 395 | * @return {void} 396 | */ 397 | getFoldersList = function(folder) { 398 | var i, item, j, len, list; 399 | if (folder.constructor === String) { 400 | folder = new Folder(folder); 401 | } 402 | list = folder.getFiles(); 403 | i = 0; 404 | for (j = 0, len = list.length; j < len; j++) { 405 | item = list[j]; 406 | if (item instanceof Folder) { 407 | that.foldersList.push(item); 408 | PSU.log("Folder: " + item.fsName); 409 | getFoldersList(item); 410 | } 411 | } 412 | }; 413 | 414 | /* 415 | * Copy Files to a Folder 416 | * @param {array} files Array of strings (File paths) 417 | * @param {string} folder Folder path to copy to 418 | * @return {void} 419 | */ 420 | copyFiles = function(files, folder) { 421 | var eachFile, file, filesList, j, k, len, len1, results; 422 | filesList = []; 423 | for (j = 0, len = files.length; j < len; j++) { 424 | file = files[j]; 425 | filesList.push(decodeURI(file)); 426 | } 427 | results = []; 428 | for (k = 0, len1 = filesList.length; k < len1; k++) { 429 | eachFile = filesList[k]; 430 | if (File(eachFile).exists) { 431 | File(eachFile).copy(folder + "/" + (File(eachFile).name)); 432 | 433 | /* For the uninstaller */ 434 | that.installedFiles.push(folder + "/" + (File(eachFile).name)); 435 | results.push(PSU.log("Copied:\t\t" + (File(eachFile).name))); 436 | } else { 437 | results.push(void 0); 438 | } 439 | } 440 | return results; 441 | }; 442 | 443 | /* Routine */ 444 | ref = this.productsToInstall; 445 | results = []; 446 | for (j = 0, len = ref.length; j < len; j++) { 447 | product = ref[j]; 448 | if (!G[product]) { 449 | PSU.log("\n" + product + " - Nothing to install\n"); 450 | continue; 451 | } 452 | switch (product) { 453 | case "SCRIPT": 454 | scriptsPath = app.path + "/" + (localize('$$$/ScriptingSupport/InstalledScripts=Presets/Scripts')); 455 | destinationPath = scriptsPath + "/" + G.COMPANY + "/" + G.PRODUCT_NAME; 456 | break; 457 | case "FLASH_PANEL": 458 | panelsPath = (G.SYSTEM_INSTALL ? Folder.commonFiles : Folder.userData) + "/Adobe/CS" + (G.CURRENT_PS_VERSION - 7) + "ServiceManager/extensions"; 459 | destinationPath = panelsPath + "/" + G.PRODUCT_ID; 460 | break; 461 | case "HTML_PANEL": 462 | panelsPath = (G.SYSTEM_INSTALL ? Folder.commonFiles : Folder.userData) + "/Adobe/" + (G.CURRENT_PS_VERSION === '14' ? 'CEPServiceManager4' : 'CEP') + "/extensions"; 463 | destinationPath = panelsPath + "/" + G.PRODUCT_ID; 464 | break; 465 | case "MAC_PLUGIN": 466 | if ($.os.match(/windows/i)) { 467 | destinationPath = ""; 468 | break; 469 | } 470 | if (G.SHARED_PLUGINS && G.CURRENT_PS_VERSION > 13) { 471 | pluginsPath = Folder.commonFiles + "/Adobe/" + (localize('$$$/private/Plugins/DefaultPluginFolder=Plug-Ins')) + "/CC"; 472 | } else { 473 | pluginsPath = app.path + "/" + (localize('$$$/private/Plugins/DefaultPluginFolder=Plug-Ins')); 474 | } 475 | destinationPath = pluginsPath + "/" + G.COMPANY; 476 | break; 477 | case "WIN_PLUGIN": 478 | if (!$.os.match(/windows/i)) { 479 | destinationPath = ""; 480 | break; 481 | } 482 | if (G.SHARED_PLUGINS && G.CURRENT_PS_VERSION > 13) { 483 | pluginsPath = Folder.commonFiles + "/Adobe/" + (localize('$$$/private/Plugins/DefaultPluginFolder=Plug-Ins')) + "/CC"; 484 | } else { 485 | pluginsPath = app.path + "/" + (localize('$$$/private/Plugins/DefaultPluginFolder=Plug-Ins')); 486 | } 487 | destinationPath = pluginsPath + "/" + G.COMPANY; 488 | break; 489 | case "EXTRA": 490 | 491 | /* TODO */ 492 | destinationPath = G.EXTRA; 493 | } 494 | if (destinationPath === "") { 495 | continue; 496 | } 497 | PSU.log("\n\nAdding " + product + "\n----------------------------\nDestination folder: " + (Folder(destinationPath).fsName)); 498 | 499 | /* Create destination Folder */ 500 | if (PSU.createFolder(destinationPath)) { 501 | PSU.log("Destination Folder successfully created.\n"); 502 | } else { 503 | PSU.log("ERROR! Can't create destination folder."); 504 | } 505 | 506 | /* Create the Folder for the source from the string path */ 507 | sourceFolder = Folder(G.CURRENT_PATH + "/" + G[product]); 508 | 509 | /* Create the Folder for the destination from the string path */ 510 | destinationFolder = Folder(destinationPath); 511 | 512 | /* Reset the array containing all the folders to be created in the destination */ 513 | this.foldersList = []; 514 | 515 | /* Fill the foldersList */ 516 | PSU.log("List of Folders to be copied for the " + product + ":"); 517 | 518 | /* Log is in the getFolderl */ 519 | getFoldersList(sourceFolder); 520 | 521 | /* Add the root folder to the list */ 522 | this.foldersList.unshift(sourceFolder); 523 | PSU.log("Folder: " + sourceFolder); 524 | 525 | /* Create Folders tree in destination */ 526 | PSU.log("\nCreating Folders in destination and copying files:\n"); 527 | 528 | /* RegExp for ignoring files to be copied */ 529 | ignoreRegExp = G.IGNORE ? new RegExp(G.IGNORE.join("|"), "i") : new RegExp("$."); 530 | ref1 = this.foldersList; 531 | for (k = 0, len1 = ref1.length; k < len1; k++) { 532 | eachFolder = ref1[k]; 533 | saveFolder = createRelativeFolder(destinationFolder, eachFolder, sourceFolder); 534 | allFiles = eachFolder.getFiles(function(f) { 535 | if (f instanceof Folder) { 536 | return false; 537 | } else { 538 | if ((f.name.match(ignoreRegExp)) != null) { 539 | return false; 540 | } 541 | return true; 542 | } 543 | }); 544 | if (allFiles.length) { 545 | copyFiles(allFiles, saveFolder); 546 | } 547 | } 548 | results.push(PSU.log("\nEnded copying files for " + product + ".")); 549 | } 550 | return results; 551 | }; 552 | 553 | PSInstaller.prototype.wrapUp = function() { 554 | alert("Complete!" + (G.ENABLE_LOG ? "\nAn installation LOG file has been created in:\n#{G.LOG_FILE}" : "")); 555 | alert("Restart Photoshop\nYou must restart the application in orded to use " + G.PRODUCT_NAME + ", thank you!"); 556 | if (G.README) { 557 | return (File(G.CURRENT_PATH + "/" + G.README)).execute(); 558 | } 559 | }; 560 | 561 | PSInstaller.prototype.createUninstaller = function() { 562 | var uninstall, uninstaller; 563 | uninstall = function(files, folders) { 564 | var e, eachFile, eachFolder, file, folder, j, k, len, performInstallation, uninstallErrors; 565 | if (!(performInstallation = confirm(G.PRODUCT_NAME + " Version " + G.PRODUCT_VERSION + " Uninstaller\nAre you sure to remove " + G.PRODUCT_NAME + "?"))) { 566 | return; 567 | } 568 | uninstallErrors = false; 569 | G.LOG_FILE = G.LOG_FILE_PATH + "/" + G.PRODUCT_NAME + " Uninstaller.log"; 570 | 571 | /* init the logging */ 572 | PSU.init(G.LOG_FILE, true); 573 | PSU.log("=======================================\n " + (new Date()) + "\n \tCompany: " + G.COMPANY + "\n \tProduct: " + G.PRODUCT_NAME + "\n \tProduct version: " + G.PRODUCT_VERSION + "\n \tApp: " + BridgeTalk.appName + "\n \tApp Version: " + app.version + "\n \tOS: " + $.os + "\n \tLocale: " + $.locale + "\n ---------------------------------------\n \tInstaller Version: " + G.INSTALLER_VERSION + "\n ======================================="); 574 | PSU.log("\nRemoving FILES..."); 575 | for (j = 0, len = files.length; j < len; j++) { 576 | eachFile = files[j]; 577 | try { 578 | file = File(eachFile); 579 | PSU.log("Removing:\t" + file.fsName + "..."); 580 | file.remove(); 581 | PSU.log("Done!"); 582 | } catch (_error) { 583 | e = _error; 584 | PSU.log("ERROR!"); 585 | uninstallErrors = true; 586 | } 587 | } 588 | PSU.log("---------------------------------------\n Removing FOLDERS..."); 589 | for (k = folders.length - 1; k >= 0; k += -1) { 590 | eachFolder = folders[k]; 591 | try { 592 | folder = Folder(eachFolder); 593 | PSU.log("Removing:\t" + folder.fsName + "..."); 594 | folder.remove(); 595 | PSU.log("Done!"); 596 | } catch (_error) { 597 | e = _error; 598 | PSU.log("ERROR!"); 599 | uninstallErrors = true; 600 | } 601 | if (uninstallErrors) { 602 | alert("Something went wrong!\nA uninstallation LOG file has been created in:\n" + G.LOG_FILE + ", please send it to " + G.CONTACT_INFO); 603 | throw Error("Restart Photoshop and see if the product has been uninstalled anyway."); 604 | } 605 | } 606 | return alert(G.PRODUCT_NAME + " successfully Removed\nPlease Restart Photoshop for the changes to take effect."); 607 | }; 608 | uninstaller = new File(G.CURRENT_PATH + "/" + G.PRODUCT_NAME + "_V" + G.PRODUCT_VERSION + " - UNINSTALLER.jsx"); 609 | if (!uninstaller.open('w')) { 610 | throwFileError(uninstaller, "Unable to Write the Uninstaller file"); 611 | } 612 | if (PSU.isMac()) { 613 | uninstaller.lineFeed = 'unix'; 614 | } 615 | uninstaller.writeln("var G = " + (G.toSource())); 616 | 617 | /* This won't work :-/ 618 | uninstaller.writeln "var PSU = #{PSU.toSource()}" 619 | */ 620 | uninstaller.writeln("var PSU=(function(GLOBAL){var createFolder,exceptionMessage,init,isMac,isWindows,log,that,throwFileError;that=this;this.enableLog=void 0;this.logFile=void 0;this.logFilePointer=void 0;isWindows=function(){return $.os.match(/windows/i);};isMac=function(){return!isWindows();};throwFileError=function(f,msg){if(msg==null){msg='';}return Error.runtimeError(9002,''+msg+''+f+': '+f.error+'.');};exceptionMessage=function(e){var fname,str;fname=!e.fileName?'???':decodeURI(e.fileName);str=' Message: '+e.message+' File: '+fname+'\\tLine: '+(e.line||'???')+'\\n\\tError Name: '+e.name+'\\n\\tError Number: '+e.number;if($.stack){str+=' '+$.stack;}return str;};log=function(msg){var file;if(!that.enableLog){return;}file=that.logFilePointer;if(!file.open('e')){throwFileError(file,'Unable to open Log file');}file.seek(0,2);if(!file.writeln(''+msg)){return throwFileError(file,'Unable to write to log file');}};createFolder=function(fptr){var rc;if(fptr==null){Error.runtimeError(19,'No Folder name specified');}if(fptr.constructor===String){fptr=new Folder(fptr);}if(fptr instanceof File){return createFolder(fptr.parent);}if(fptr.exists){return true;}if(!(fptr instanceof Folder)){log(fptr.constructor);Error.runtimeError(21,'Folder is not a Folder?');}if((fptr.parent!=null)&&!fptr.parent.exists){if(!createFolder(fptr.parent)){return false;}}rc=fptr.create();if(!rc){Error.runtimeError(9002,'Unable to create folder '+fptr+' ('+fptr.error+') Please create it manually and run this script again.');}return rc;};init=function(logFile,isLogEnabled){var file;if(!isLogEnabled){return;}that.enableLog=isLogEnabled;that.logFile=logFile;file=new File(that.logFile);if(file.exists){file.remove();}if(!file.open('w')){throwFileError(file,'Unable to open Log file');}if(isMac()){file.lineFeed='unix';}that.logFilePointer=file;};return{'isMac':isMac,'exceptionMessage':exceptionMessage,'log':log,'createFolder':createFolder,'init':init};})(this);"); 621 | uninstaller.writeln("var filesToRemove = " + (this.installedFiles.toSource()) + ";"); 622 | uninstaller.writeln("var foldersToRemove = " + (this.installedFolders.toSource()) + ";"); 623 | uninstaller.writeln("var uninstall = " + (uninstall.toSource()) + ";"); 624 | uninstaller.writeln("uninstall(filesToRemove, foldersToRemove);"); 625 | return uninstaller.close(); 626 | }; 627 | 628 | return PSInstaller; 629 | 630 | })(); 631 | 632 | try { 633 | psInstaller = new PSInstaller(); 634 | psInstaller.preflight(); 635 | psInstaller.init(); 636 | psInstaller.clean(); 637 | psInstaller.copy(); 638 | psInstaller.createUninstaller(); 639 | psInstaller.wrapUp(); 640 | } catch (_error) { 641 | e = _error; 642 | errorMessage = "Installation failed: " + (PSU.exceptionMessage(e)); 643 | PSU.log(errorMessage); 644 | alert("Something went wrong!\n" + errorMessage + "\nPlease contact " + G.CONTACT_INFO + ", thank you."); 645 | } 646 | 647 | 648 | /* EOF */ 649 | 650 | "psInstaller"; 651 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Davide Barranca 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 | 23 | -------------------------------------------------------------------------------- /PS-Installer.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | * =============================================== 3 | * = Adobe Photoshop Add-ons Installer = 4 | * = (Because Adobe Extension Manager Sucks) = 5 | * =============================================== 6 | * 7 | * Copyright: (c)2015-2019, Davide Barranca 8 | * www.davidebarranca.com || www.cs-extensions.com 9 | * MIT License: http://opensource.org/licenses/MIT 10 | * 11 | * Thanks to xbytor for his xtools installer, 12 | * which has been a source of inspiration! 13 | * 14 | * =============================================== 15 | ### 16 | 17 | ### 18 | * Global object from the JSON 19 | ### 20 | 21 | initFile = new File "#{File($.fileName).path}/init.json" 22 | unless initFile.exists 23 | alert "Broken installer!\nThe init.json file is missing...\nPlease get in touch with the original developer." 24 | throw new Error() 25 | 26 | initFile.open "r" 27 | initFileString = initFile.read() 28 | initFile.close() 29 | eval "var G = #{initFileString}" 30 | 31 | ### 32 | * Utility functions 33 | * Mostly borrowed from xbytor's xtools installer 34 | * http://sourceforge.net/projects/ps-scripts/files/xtools/ 35 | * License: http://www.opensource.org/licenses/bsd-license.php 36 | ### 37 | PSU = ((GLOBAL) -> 38 | that = this 39 | @enableLog = undefined 40 | @logFile = undefined 41 | @logFilePointer = undefined 42 | 43 | isWindows = () -> $.os.match /windows/i 44 | isMac = () -> !isWindows() 45 | 46 | throwFileError = (f, msg) -> 47 | unless msg? then msg = '' 48 | Error.runtimeError 9002, "#{msg}\"#{f}\": #{f.error}." 49 | 50 | exceptionMessage = (e) -> 51 | fname = if !e.fileName then '???' else decodeURI e.fileName 52 | str = "\tMessage: #{e.message}\n\tFile: #{fname}\n\tLine: #{e.line or '???'}\n\tError Name: #{e.name}\n\tError Number: #{e.number}" 53 | if $.stack then str += "\t#{$.stack}" 54 | str 55 | 56 | log = (msg) -> 57 | return unless that.enableLog 58 | file = that.logFilePointer 59 | unless file.open 'e' then throwFileError file, "Unable to open Log file" 60 | file.seek 0, 2 # jump to the end of the file (0 from the end) 61 | unless file.writeln "#{msg}" then throwFileError file, "Unable to write to log file" 62 | 63 | createFolder = (fptr) -> 64 | unless fptr? then Error.runtimeError 19, "No Folder name specified" 65 | fptr = new Folder fptr if fptr.constructor is String 66 | 67 | ### Recursion if the arg is a File ### 68 | return createFolder fptr.parent if fptr instanceof File 69 | 70 | ### Are we done? ### 71 | return true if fptr.exists 72 | 73 | unless fptr instanceof Folder 74 | log fptr.constructor 75 | Error.runtimeError 21, "Folder is not a Folder?" 76 | 77 | if fptr.parent? and not fptr.parent.exists 78 | return false unless createFolder fptr.parent 79 | 80 | ### eventually...! ### 81 | rc = fptr.create() 82 | unless rc then Error.runtimeError 9002, "Unable to create folder #{fptr} (#{fptr.error})\nPlease create it manually and run this script again." 83 | return rc 84 | 85 | ### 86 | * Sets up the logging 87 | * @param {string} logFile Log file name 88 | * @param {Boolean} isLogEnabled To log or not to log... 89 | * @return {void} 90 | ### 91 | init = (logFile, isLogEnabled) -> 92 | return unless isLogEnabled 93 | ### LOG Stuff ### 94 | that.enableLog = isLogEnabled 95 | that.logFile = logFile 96 | ### Create Log File Pointer ### 97 | file = new File that.logFile 98 | if file.exists then file.remove() 99 | unless file.open 'w' then throwFileError file, "Unable to open Log file" 100 | file.lineFeed = 'unix' if isMac() 101 | that.logFilePointer = file 102 | return 103 | 104 | return { 105 | "isMac" : isMac 106 | "exceptionMessage" : exceptionMessage 107 | "log" : log 108 | "createFolder" : createFolder 109 | "init" : init 110 | } 111 | 112 | )(this) 113 | 114 | class PSInstaller 115 | 116 | ### 117 | * Set globals and start the logging 118 | * @return {void} 119 | ### 120 | constructor: () -> 121 | 122 | 123 | ### set the log file name ### 124 | G.LOG_FILE = "#{G.LOG_FILE_PATH}/#{G.PRODUCT_NAME}.log" 125 | ### init the logging ### 126 | PSU.init G.LOG_FILE, G.ENABLE_LOG 127 | ### SCRIPT, FLASH_PANEL, etc. ### 128 | @productsToInstall = [] 129 | ### All the folders to be copied (relative to the ASSETS path) ### 130 | @foldersList = [] 131 | ### List of Deployed Files and Folder - to be used by the uninstaller ### 132 | @installedFiles = [] 133 | @installedFolders = [] 134 | 135 | PSU.log " 136 | =======================================\n 137 | #{new Date()}\n 138 | \tCompany: #{G.COMPANY}\n 139 | \tProduct: #{G.PRODUCT_NAME}\n 140 | \tProduct version: #{G.PRODUCT_VERSION}\n 141 | \tApp: #{BridgeTalk.appName}\n 142 | \tApp Version: #{app.version}\n 143 | \tOS: #{$.os}\n 144 | \tLocale: #{$.locale}\n 145 | ---------------------------------------\n 146 | \tInstaller Version: #{G.INSTALLER_VERSION}\n 147 | ======================================= 148 | " 149 | 150 | return # constructor 151 | 152 | ### 153 | * App compatibility check 154 | * @return {} 155 | ### 156 | preflight: () -> 157 | G.MIN_VERSION = G.MIN_VERSION || 0 158 | G.MAX_VERSION = G.MAX_VERSION || 99 159 | 160 | PSU.log "\nPreflight 161 | \n----------------------------" 162 | 163 | if G.MIN_VERSION <= G.CURRENT_PS_VERSION <= G.MAX_VERSION 164 | 165 | PSU.log "OK: PS version #{G.CURRENT_PS_VERSION} in the range [#{G.MIN_VERSION}, #{G.MAX_VERSION}]" 166 | alert "#{G.COMPANY} - #{G.PRODUCT_NAME}\nPress OK to start the installation.\nThe process is going to be completed in a short while." 167 | return true 168 | 169 | else 170 | PSU.log "\nFAIL: PS version #{G.CURRENT_PS_VERSION} not in the range [#{G.MIN_VERSION}, #{G.MAX_VERSION}]" 171 | Error.runtimeError 9002, "Bad Photoshop version.\n#{G.CURRENT_PS_VERSION} not in the range [#{G.MIN_VERSION}, #{G.MAX_VERSION}]" 172 | 173 | 174 | ### 175 | * Depending on the PS version, sets the available products options 176 | * to install (@productsToInstall) and log them 177 | * @return {void} 178 | ### 179 | init: () -> 180 | 181 | that = @ 182 | 183 | ### Depending on the PS version, what to install 184 | (not the classiest way I know but it works) ### 185 | dependencyObj = { 186 | "10" : [ "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] # CS3 187 | "11" : [ "FLASH_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] # CS4 188 | "12" : [ "FLASH_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] # CS5 189 | "13" : [ "FLASH_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] # CS6 190 | "14" : [ "HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] # CC 191 | "15" : [ "HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] # CC 2014 192 | "16" : [ "HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] # CC 2015 193 | "17" : [ "HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] # CC 2015.5 194 | "18" : [ "HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] # CC 2017 195 | "19" : [ "HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] # CC 2018 196 | "20" : [ "HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] # CC 2019 197 | "21" : [ "HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] # 2020 198 | } 199 | 200 | ### Array ### 201 | @productsToInstall = dependencyObj[G.CURRENT_PS_VERSION] 202 | 203 | PSU.log "\nItems to be installed 204 | \n----------------------------" 205 | 206 | for product in @productsToInstall 207 | PSU.log "- #{product}" 208 | 209 | return # init 210 | 211 | ### 212 | * TODO!! 213 | * @param {[Object]} oldVersionsObj An Object containing info about stuff to clean before installing new ones 214 | * @return {[void]} 215 | ### 216 | clean: (oldVersionsObj) -> 217 | 218 | ### 219 | * Delete a Folder (recursively, with ALL its content) 220 | * or a single file 221 | * @param {File or Folder object} thingToDestroy The object to remove 222 | ### 223 | recursivelyDeleteThing = (thingToDestroy) -> 224 | 225 | inFolders = [] 226 | inFiles = [] 227 | 228 | processFolder = (fold) -> 229 | fileList = fold.getFiles() 230 | for aThing in fileList 231 | if aThing instanceof Folder 232 | inFolders.push aThing 233 | processFolder aThing 234 | else 235 | inFiles.push aThing 236 | 237 | if thingToDestroy instanceof File 238 | try 239 | thingToDestroy.remove() 240 | catch e 241 | 242 | return 243 | 244 | if thingToDestroy instanceof Folder 245 | 246 | processFolder thingToDestroy 247 | inFolders.unshift thingToDestroy 248 | inFolders.reverse() 249 | # $.writeln aFile for aFile in inFiles 250 | # $.writeln aFolder for aFolder in inFolders 251 | for aFile in inFiles 252 | try 253 | aFile.remove() 254 | catch e 255 | 256 | for aFolder in inFolders 257 | try 258 | aFolder.remove() 259 | catch e 260 | 261 | return 262 | return 263 | ### 264 | * Delete recursively a File or Folder which CONTAINS a string, 265 | * e.g. recursivelyDeleteThing("this", "~/Desktop/TEST") will catch 266 | * "com.this.example" and so on and so forth 267 | * @param {String} thingToDestroy What the File or Folder name should contain 268 | * @param {String} startFolder The start folder 269 | ### 270 | recursivelyDeleteThingFromFolder = (thingToDestroy, startFolder) -> 271 | 272 | inFolders = [] 273 | inFiles = [] 274 | 275 | escapeRegExp = (stringToGoIntoTheRegex) -> 276 | stringToGoIntoTheRegex = stringToGoIntoTheRegex.name if stringToGoIntoTheRegex instanceof Folder 277 | stringToGoIntoTheRegex.replace /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' 278 | 279 | processFolder = (fold) -> 280 | fileList = fold.getFiles() 281 | for aThing in fileList 282 | if aThing instanceof Folder 283 | inFolders.push aThing 284 | processFolder aThing 285 | else 286 | inFiles.push aThing 287 | 288 | processFolder Folder startFolder 289 | 290 | matchString = escapeRegExp thingToDestroy 291 | re = new RegExp matchString, "gi" 292 | 293 | for aFile in inFiles 294 | aFile.remove() if aFile.name.match re 295 | 296 | for aFolder in inFolders 297 | recursivelyDeleteThing aFolder if aFolder.name.match re 298 | 299 | return 300 | 301 | return 302 | 303 | copy: () -> 304 | 305 | that = @ 306 | 307 | ### 308 | * [createRelativeFolder description] 309 | * @param {folder} destination the destination Folder 310 | * @param {folder} origin the original, existing Folder 311 | * @param {folder} base the Folder used as a base 312 | * @return {folder} a new created folder in the destination 313 | ### 314 | createRelativeFolder = (destination, origin, base) -> 315 | 316 | destinationArray = decodeURI(destination).toString().split('/') 317 | originArray = decodeURI(origin).toString().split('/') 318 | originBaseArray = decodeURI(base).toString().split('/') 319 | # normalize the url removing the origin folder hierarchy 320 | originArray.splice 0, originBaseArray.length 321 | 322 | destinationToWriteArray = destinationArray.concat originArray 323 | destinationToWriteString = destinationToWriteArray.join '/' 324 | destinationToWriteFolder = Folder destinationToWriteString 325 | unless destinationToWriteFolder.exists then destinationToWriteFolder.create() 326 | PSU.log "Created Folder:\t#{destinationToWriteFolder.fsName}" 327 | that.installedFolders.push "#{destinationToWriteFolder.fsName}" 328 | destinationToWriteFolder 329 | 330 | ### 331 | * process the folder and fills the external 332 | * array of folders foldersList 333 | * @param {folder} folder 334 | * @return {void} 335 | ### 336 | getFoldersList = (folder) -> 337 | if folder.constructor is String then folder = new Folder folder 338 | list = folder.getFiles() 339 | i = 0 340 | for item in list 341 | if item instanceof Folder 342 | that.foldersList.push item 343 | PSU.log "Folder: #{item.fsName}" 344 | getFoldersList item 345 | return 346 | 347 | ### 348 | * Copy Files to a Folder 349 | * @param {array} files Array of strings (File paths) 350 | * @param {string} folder Folder path to copy to 351 | * @return {void} 352 | ### 353 | copyFiles = (files, folder) -> 354 | filesList = [] 355 | filesList.push decodeURI file for file in files 356 | for eachFile in filesList 357 | if File(eachFile).exists 358 | File(eachFile).copy "#{folder}/#{File(eachFile).name}" 359 | ### For the uninstaller ### 360 | that.installedFiles.push "#{folder}/#{File(eachFile).name}" 361 | PSU.log "Copied:\t\t#{File(eachFile).name}" 362 | 363 | ### Routine ### 364 | for product in @productsToInstall 365 | 366 | unless G[product] 367 | PSU.log "\n#{product} - Nothing to install\n" 368 | continue 369 | 370 | switch product 371 | 372 | when "SCRIPT" 373 | scriptsPath = "#{app.path}/#{localize '$$$/ScriptingSupport/InstalledScripts=Presets/Scripts'}" 374 | destinationPath = "#{scriptsPath}/#{G.COMPANY}/#{G.PRODUCT_NAME}" 375 | 376 | when "FLASH_PANEL" 377 | panelsPath = "#{if G.SYSTEM_INSTALL then Folder.commonFiles else Folder.userData}/Adobe/CS#{G.CURRENT_PS_VERSION - 7}ServiceManager/extensions" 378 | destinationPath = "#{panelsPath}/#{G.PRODUCT_ID}" 379 | 380 | when "HTML_PANEL" 381 | panelsPath = "#{if G.SYSTEM_INSTALL then Folder.commonFiles else Folder.userData}/Adobe/#{if G.CURRENT_PS_VERSION is '14' then 'CEPServiceManager4' else 'CEP'}/extensions" 382 | destinationPath = "#{panelsPath}/#{G.PRODUCT_ID}" 383 | 384 | when "MAC_PLUGIN" 385 | if $.os.match /windows/i 386 | destinationPath = "" 387 | break 388 | if G.SHARED_PLUGINS and G.CURRENT_PS_VERSION > 13 389 | pluginsPath = "#{Folder.commonFiles}/Adobe/#{localize '$$$/private/Plugins/DefaultPluginFolder=Plug-Ins'}/CC" 390 | else 391 | pluginsPath = "#{app.path}/#{localize '$$$/private/Plugins/DefaultPluginFolder=Plug-Ins'}" 392 | destinationPath = "#{pluginsPath}/#{G.COMPANY}" 393 | 394 | when "WIN_PLUGIN" 395 | unless $.os.match /windows/i 396 | destinationPath = "" 397 | break 398 | if G.SHARED_PLUGINS and G.CURRENT_PS_VERSION > 13 399 | pluginsPath = "#{Folder.commonFiles}/Adobe/#{localize '$$$/private/Plugins/DefaultPluginFolder=Plug-Ins'}/CC" 400 | else 401 | pluginsPath = "#{app.path}/#{localize '$$$/private/Plugins/DefaultPluginFolder=Plug-Ins'}" 402 | destinationPath = "#{pluginsPath}/#{G.COMPANY}" 403 | 404 | when "EXTRA" 405 | ### TODO ### 406 | destinationPath = G.EXTRA 407 | 408 | if destinationPath is "" then continue 409 | PSU.log "\n\nAdding #{product}\n----------------------------\nDestination folder: #{Folder(destinationPath).fsName}" 410 | ### Create destination Folder ### 411 | if PSU.createFolder destinationPath then PSU.log "Destination Folder successfully created.\n" else PSU.log "ERROR! Can't create destination folder." 412 | 413 | ### Create the Folder for the source from the string path ### 414 | sourceFolder = Folder "#{G.CURRENT_PATH}/#{G[product]}" 415 | ### Create the Folder for the destination from the string path ### 416 | destinationFolder = Folder destinationPath 417 | ### Reset the array containing all the folders to be created in the destination ### 418 | @foldersList = [] 419 | ### Fill the foldersList ### 420 | PSU.log "List of Folders to be copied for the #{product}:" 421 | ### Log is in the getFolderl ### 422 | getFoldersList sourceFolder 423 | ### Add the root folder to the list ### 424 | @foldersList.unshift sourceFolder 425 | PSU.log "Folder: #{sourceFolder}" 426 | 427 | ### Create Folders tree in destination ### 428 | PSU.log "\nCreating Folders in destination and copying files:\n" 429 | ### RegExp for ignoring files to be copied ### 430 | ignoreRegExp = if G.IGNORE then new RegExp((G.IGNORE.join "|"), "i") else new RegExp "$." 431 | 432 | for eachFolder in @foldersList 433 | saveFolder = createRelativeFolder destinationFolder, eachFolder, sourceFolder 434 | allFiles = eachFolder.getFiles( (f) -> 435 | if (f instanceof Folder) 436 | return false 437 | else 438 | if (f.name.match ignoreRegExp)? then return false 439 | return true 440 | ) 441 | if allFiles.length then copyFiles allFiles, saveFolder 442 | 443 | PSU.log "\nEnded copying files for #{product}." 444 | 445 | wrapUp: () -> 446 | alert "Complete!" + (G.ENABLE_LOG ? "\nAn installation LOG file has been created in:\n#{G.LOG_FILE}" : "") 447 | alert "Restart Photoshop\nYou must restart the application in orded to use #{G.PRODUCT_NAME}, thank you!" 448 | if G.README then (File "#{G.CURRENT_PATH}/#{G.README}").execute() 449 | 450 | createUninstaller: () -> 451 | 452 | uninstall = (files, folders) -> 453 | return unless performInstallation = confirm "#{G.PRODUCT_NAME} Version #{G.PRODUCT_VERSION} Uninstaller\nAre you sure to remove #{G.PRODUCT_NAME}?" 454 | uninstallErrors = false 455 | G.LOG_FILE = "#{G.LOG_FILE_PATH}/#{G.PRODUCT_NAME} Uninstaller.log" 456 | ### init the logging ### 457 | PSU.init G.LOG_FILE, true 458 | PSU.log " 459 | =======================================\n 460 | #{new Date()}\n 461 | \tCompany: #{G.COMPANY}\n 462 | \tProduct: #{G.PRODUCT_NAME}\n 463 | \tProduct version: #{G.PRODUCT_VERSION}\n 464 | \tApp: #{BridgeTalk.appName}\n 465 | \tApp Version: #{app.version}\n 466 | \tOS: #{$.os}\n 467 | \tLocale: #{$.locale}\n 468 | ---------------------------------------\n 469 | \tInstaller Version: #{G.INSTALLER_VERSION}\n 470 | ======================================= 471 | " 472 | 473 | PSU.log "\nRemoving FILES..." 474 | 475 | for eachFile in files 476 | try 477 | file = File eachFile 478 | PSU.log "Removing:\t#{file.fsName}..." 479 | file.remove() 480 | PSU.log "Done!" 481 | catch e 482 | PSU.log "ERROR!" 483 | uninstallErrors = true 484 | PSU.log "---------------------------------------\n 485 | Removing FOLDERS..." 486 | 487 | for eachFolder in folders by -1 488 | try 489 | folder = Folder eachFolder 490 | PSU.log "Removing:\t#{folder.fsName}..." 491 | folder.remove() 492 | PSU.log "Done!" 493 | catch e 494 | PSU.log "ERROR!" 495 | uninstallErrors = true 496 | 497 | if uninstallErrors 498 | alert "Something went wrong!\nA uninstallation LOG file has been created in:\n#{G.LOG_FILE}, please send it to #{G.CONTACT_INFO}" 499 | throw Error "Restart Photoshop and see if the product has been uninstalled anyway." 500 | 501 | alert "#{G.PRODUCT_NAME} successfully Removed\nPlease Restart Photoshop for the changes to take effect." 502 | 503 | uninstaller = new File "#{G.CURRENT_PATH}/#{G.PRODUCT_NAME}_V#{G.PRODUCT_VERSION} - UNINSTALLER.jsx" 504 | unless uninstaller.open 'w' then throwFileError uninstaller, "Unable to Write the Uninstaller file" 505 | uninstaller.lineFeed = 'unix' if PSU.isMac() 506 | uninstaller.writeln "var G = #{G.toSource()}" 507 | ### This won't work :-/ 508 | uninstaller.writeln "var PSU = #{PSU.toSource()}" ### 509 | uninstaller.writeln "var PSU=(function(GLOBAL){var createFolder,exceptionMessage,init,isMac,isWindows,log,that,throwFileError;that=this;this.enableLog=void 0;this.logFile=void 0;this.logFilePointer=void 0;isWindows=function(){return $.os.match(/windows/i);};isMac=function(){return!isWindows();};throwFileError=function(f,msg){if(msg==null){msg='';}return Error.runtimeError(9002,''+msg+''+f+': '+f.error+'.');};exceptionMessage=function(e){var fname,str;fname=!e.fileName?'???':decodeURI(e.fileName);str=' Message: '+e.message+' File: '+fname+'\\tLine: '+(e.line||'???')+'\\n\\tError Name: '+e.name+'\\n\\tError Number: '+e.number;if($.stack){str+=' '+$.stack;}return str;};log=function(msg){var file;if(!that.enableLog){return;}file=that.logFilePointer;if(!file.open('e')){throwFileError(file,'Unable to open Log file');}file.seek(0,2);if(!file.writeln(''+msg)){return throwFileError(file,'Unable to write to log file');}};createFolder=function(fptr){var rc;if(fptr==null){Error.runtimeError(19,'No Folder name specified');}if(fptr.constructor===String){fptr=new Folder(fptr);}if(fptr instanceof File){return createFolder(fptr.parent);}if(fptr.exists){return true;}if(!(fptr instanceof Folder)){log(fptr.constructor);Error.runtimeError(21,'Folder is not a Folder?');}if((fptr.parent!=null)&&!fptr.parent.exists){if(!createFolder(fptr.parent)){return false;}}rc=fptr.create();if(!rc){Error.runtimeError(9002,'Unable to create folder '+fptr+' ('+fptr.error+') Please create it manually and run this script again.');}return rc;};init=function(logFile,isLogEnabled){var file;if(!isLogEnabled){return;}that.enableLog=isLogEnabled;that.logFile=logFile;file=new File(that.logFile);if(file.exists){file.remove();}if(!file.open('w')){throwFileError(file,'Unable to open Log file');}if(isMac()){file.lineFeed='unix';}that.logFilePointer=file;};return{'isMac':isMac,'exceptionMessage':exceptionMessage,'log':log,'createFolder':createFolder,'init':init};})(this);" 510 | uninstaller.writeln "var filesToRemove = #{@installedFiles.toSource()};" 511 | uninstaller.writeln "var foldersToRemove = #{@installedFolders.toSource()};" 512 | uninstaller.writeln "var uninstall = #{uninstall.toSource()};" 513 | uninstaller.writeln "uninstall(filesToRemove, foldersToRemove);" 514 | uninstaller.close() 515 | 516 | try 517 | psInstaller = new PSInstaller() 518 | psInstaller.preflight() 519 | psInstaller.init() 520 | psInstaller.clean() 521 | psInstaller.copy() 522 | psInstaller.createUninstaller() 523 | psInstaller.wrapUp() 524 | catch e 525 | errorMessage = "Installation failed: #{PSU.exceptionMessage e}" 526 | PSU.log errorMessage 527 | alert "Something went wrong!\n#{errorMessage}\nPlease contact #{G.CONTACT_INFO}, thank you." 528 | 529 | ### EOF ### 530 | "psInstaller" 531 | -------------------------------------------------------------------------------- /PS-Installer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * =============================================== 3 | * = Adobe Photoshop Add-ons Installer = 4 | * = (Because Adobe Extension Manager Sucks) = 5 | * =============================================== 6 | * 7 | * Copyright: (c)2015-2019, Davide Barranca 8 | * www.davidebarranca.com || www.cs-extensions.com 9 | * MIT License: http://opensource.org/licenses/MIT 10 | * 11 | * Thanks to xbytor for his xtools installer, 12 | * which has been a source of inspiration! 13 | * 14 | * =============================================== 15 | */ 16 | 17 | /* 18 | * Global object from the JSON 19 | */ 20 | var PSInstaller, PSU, e, errorMessage, initFile, initFileString, psInstaller; 21 | 22 | initFile = new File((File($.fileName).path) + "/init.json"); 23 | 24 | if (!initFile.exists) { 25 | alert("Broken installer!\nThe init.json file is missing...\nPlease get in touch with the original developer."); 26 | throw new Error(); 27 | } 28 | 29 | initFile.open("r"); 30 | 31 | initFileString = initFile.read(); 32 | 33 | initFile.close(); 34 | 35 | eval("var G = " + initFileString); 36 | 37 | 38 | /* 39 | * Utility functions 40 | * Mostly borrowed from xbytor's xtools installer 41 | * http://sourceforge.net/projects/ps-scripts/files/xtools/ 42 | * License: http://www.opensource.org/licenses/bsd-license.php 43 | */ 44 | 45 | PSU = (function(GLOBAL) { 46 | var createFolder, exceptionMessage, init, isMac, isWindows, log, that, throwFileError; 47 | that = this; 48 | this.enableLog = void 0; 49 | this.logFile = void 0; 50 | this.logFilePointer = void 0; 51 | isWindows = function() { 52 | return $.os.match(/windows/i); 53 | }; 54 | isMac = function() { 55 | return !isWindows(); 56 | }; 57 | throwFileError = function(f, msg) { 58 | if (msg == null) { 59 | msg = ''; 60 | } 61 | return Error.runtimeError(9002, msg + "\"" + f + "\": " + f.error + "."); 62 | }; 63 | exceptionMessage = function(e) { 64 | var fname, str; 65 | fname = !e.fileName ? '???' : decodeURI(e.fileName); 66 | str = "\tMessage: " + e.message + "\n\tFile: " + fname + "\n\tLine: " + (e.line || '???') + "\n\tError Name: " + e.name + "\n\tError Number: " + e.number; 67 | if ($.stack) { 68 | str += "\t" + $.stack; 69 | } 70 | return str; 71 | }; 72 | log = function(msg) { 73 | var file; 74 | if (!that.enableLog) { 75 | return; 76 | } 77 | file = that.logFilePointer; 78 | if (!file.open('e')) { 79 | throwFileError(file, "Unable to open Log file"); 80 | } 81 | file.seek(0, 2); 82 | if (!file.writeln("" + msg)) { 83 | return throwFileError(file, "Unable to write to log file"); 84 | } 85 | }; 86 | createFolder = function(fptr) { 87 | var rc; 88 | if (fptr == null) { 89 | Error.runtimeError(19, "No Folder name specified"); 90 | } 91 | if (fptr.constructor === String) { 92 | fptr = new Folder(fptr); 93 | } 94 | 95 | /* Recursion if the arg is a File */ 96 | if (fptr instanceof File) { 97 | return createFolder(fptr.parent); 98 | } 99 | 100 | /* Are we done? */ 101 | if (fptr.exists) { 102 | return true; 103 | } 104 | if (!(fptr instanceof Folder)) { 105 | log(fptr.constructor); 106 | Error.runtimeError(21, "Folder is not a Folder?"); 107 | } 108 | if ((fptr.parent != null) && !fptr.parent.exists) { 109 | if (!createFolder(fptr.parent)) { 110 | return false; 111 | } 112 | } 113 | 114 | /* eventually...! */ 115 | rc = fptr.create(); 116 | if (!rc) { 117 | Error.runtimeError(9002, "Unable to create folder " + fptr + " (" + fptr.error + ")\nPlease create it manually and run this script again."); 118 | } 119 | return rc; 120 | }; 121 | 122 | /* 123 | * Sets up the logging 124 | * @param {string} logFile Log file name 125 | * @param {Boolean} isLogEnabled To log or not to log... 126 | * @return {void} 127 | */ 128 | init = function(logFile, isLogEnabled) { 129 | var file; 130 | if (!isLogEnabled) { 131 | return; 132 | } 133 | 134 | /* LOG Stuff */ 135 | that.enableLog = isLogEnabled; 136 | that.logFile = logFile; 137 | 138 | /* Create Log File Pointer */ 139 | file = new File(that.logFile); 140 | if (file.exists) { 141 | file.remove(); 142 | } 143 | if (!file.open('w')) { 144 | throwFileError(file, "Unable to open Log file"); 145 | } 146 | if (isMac()) { 147 | file.lineFeed = 'unix'; 148 | } 149 | that.logFilePointer = file; 150 | }; 151 | return { 152 | "isMac": isMac, 153 | "exceptionMessage": exceptionMessage, 154 | "log": log, 155 | "createFolder": createFolder, 156 | "init": init 157 | }; 158 | })(this); 159 | 160 | PSInstaller = (function() { 161 | 162 | /* 163 | * Set globals and start the logging 164 | * @return {void} 165 | */ 166 | function PSInstaller() { 167 | 168 | /* set the log file name */ 169 | G.LOG_FILE = G.LOG_FILE_PATH + "/" + G.PRODUCT_NAME + ".log"; 170 | 171 | /* init the logging */ 172 | PSU.init(G.LOG_FILE, G.ENABLE_LOG); 173 | 174 | /* SCRIPT, FLASH_PANEL, etc. */ 175 | this.productsToInstall = []; 176 | 177 | /* All the folders to be copied (relative to the ASSETS path) */ 178 | this.foldersList = []; 179 | 180 | /* List of Deployed Files and Folder - to be used by the uninstaller */ 181 | this.installedFiles = []; 182 | this.installedFolders = []; 183 | PSU.log("=======================================\n " + (new Date()) + "\n \tCompany: " + G.COMPANY + "\n \tProduct: " + G.PRODUCT_NAME + "\n \tProduct version: " + G.PRODUCT_VERSION + "\n \tApp: " + BridgeTalk.appName + "\n \tApp Version: " + app.version + "\n \tOS: " + $.os + "\n \tLocale: " + $.locale + "\n ---------------------------------------\n \tInstaller Version: " + G.INSTALLER_VERSION + "\n ======================================="); 184 | return; 185 | } 186 | 187 | 188 | /* 189 | * App compatibility check 190 | * @return {} 191 | */ 192 | 193 | PSInstaller.prototype.preflight = function() { 194 | var ref; 195 | G.MIN_VERSION = G.MIN_VERSION || 0; 196 | G.MAX_VERSION = G.MAX_VERSION || 99; 197 | PSU.log("\nPreflight \n----------------------------"); 198 | if ((G.MIN_VERSION <= (ref = G.CURRENT_PS_VERSION) && ref <= G.MAX_VERSION)) { 199 | PSU.log("OK: PS version " + G.CURRENT_PS_VERSION + " in the range [" + G.MIN_VERSION + ", " + G.MAX_VERSION + "]"); 200 | alert(G.COMPANY + " - " + G.PRODUCT_NAME + "\nPress OK to start the installation.\nThe process is going to be completed in a short while."); 201 | return true; 202 | } else { 203 | PSU.log("\nFAIL: PS version " + G.CURRENT_PS_VERSION + " not in the range [" + G.MIN_VERSION + ", " + G.MAX_VERSION + "]"); 204 | return Error.runtimeError(9002, "Bad Photoshop version.\n" + G.CURRENT_PS_VERSION + " not in the range [" + G.MIN_VERSION + ", " + G.MAX_VERSION + "]"); 205 | } 206 | }; 207 | 208 | 209 | /* 210 | * Depending on the PS version, sets the available products options 211 | * to install (@productsToInstall) and log them 212 | * @return {void} 213 | */ 214 | 215 | PSInstaller.prototype.init = function() { 216 | var dependencyObj, j, len, product, ref, that; 217 | that = this; 218 | 219 | /* Depending on the PS version, what to install 220 | (not the classiest way I know but it works) 221 | */ 222 | dependencyObj = { 223 | "10": ["SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 224 | "11": ["FLASH_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 225 | "12": ["FLASH_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 226 | "13": ["FLASH_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 227 | "14": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 228 | "15": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 229 | "16": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 230 | "17": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 231 | "18": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 232 | "19": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 233 | "20": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"], 234 | "21": ["HTML_PANEL", "SCRIPT", "MAC_PLUGIN", "WIN_PLUGIN", "EXTRA"] 235 | }; 236 | 237 | /* Array */ 238 | this.productsToInstall = dependencyObj[G.CURRENT_PS_VERSION]; 239 | PSU.log("\nItems to be installed \n----------------------------"); 240 | ref = this.productsToInstall; 241 | for (j = 0, len = ref.length; j < len; j++) { 242 | product = ref[j]; 243 | PSU.log("- " + product); 244 | } 245 | }; 246 | 247 | PSInstaller.prototype.copy = function() { 248 | var allFiles, copyFiles, createRelativeFolder, destinationFolder, destinationPath, eachFolder, getFoldersList, ignoreRegExp, j, k, len, len1, panelsPath, pluginsPath, product, ref, ref1, results, saveFolder, scriptsPath, sourceFolder, that; 249 | that = this; 250 | 251 | /* 252 | * [createRelativeFolder description] 253 | * @param {folder} destination the destination Folder 254 | * @param {folder} origin the original, existing Folder 255 | * @param {folder} base the Folder used as a base 256 | * @return {folder} a new created folder in the destination 257 | */ 258 | createRelativeFolder = function(destination, origin, base) { 259 | var destinationArray, destinationToWriteArray, destinationToWriteFolder, destinationToWriteString, originArray, originBaseArray; 260 | destinationArray = decodeURI(destination).toString().split('/'); 261 | originArray = decodeURI(origin).toString().split('/'); 262 | originBaseArray = decodeURI(base).toString().split('/'); 263 | originArray.splice(0, originBaseArray.length); 264 | destinationToWriteArray = destinationArray.concat(originArray); 265 | destinationToWriteString = destinationToWriteArray.join('/'); 266 | destinationToWriteFolder = Folder(destinationToWriteString); 267 | if (!destinationToWriteFolder.exists) { 268 | destinationToWriteFolder.create(); 269 | } 270 | PSU.log("Created Folder:\t" + destinationToWriteFolder.fsName); 271 | that.installedFolders.push("" + destinationToWriteFolder.fsName); 272 | return destinationToWriteFolder; 273 | }; 274 | 275 | /* 276 | * process the folder and fills the external 277 | * array of folders foldersList 278 | * @param {folder} folder 279 | * @return {void} 280 | */ 281 | getFoldersList = function(folder) { 282 | var i, item, j, len, list; 283 | if (folder.constructor === String) { 284 | folder = new Folder(folder); 285 | } 286 | list = folder.getFiles(); 287 | i = 0; 288 | for (j = 0, len = list.length; j < len; j++) { 289 | item = list[j]; 290 | if (item instanceof Folder) { 291 | that.foldersList.push(item); 292 | PSU.log("Folder: " + item.fsName); 293 | getFoldersList(item); 294 | } 295 | } 296 | }; 297 | 298 | /* 299 | * Copy Files to a Folder 300 | * @param {array} files Array of strings (File paths) 301 | * @param {string} folder Folder path to copy to 302 | * @return {void} 303 | */ 304 | copyFiles = function(files, folder) { 305 | var eachFile, file, filesList, j, k, len, len1, results; 306 | filesList = []; 307 | for (j = 0, len = files.length; j < len; j++) { 308 | file = files[j]; 309 | filesList.push(decodeURI(file)); 310 | } 311 | results = []; 312 | for (k = 0, len1 = filesList.length; k < len1; k++) { 313 | eachFile = filesList[k]; 314 | if (File(eachFile).exists) { 315 | File(eachFile).copy(folder + "/" + (File(eachFile).name)); 316 | 317 | /* For the uninstaller */ 318 | that.installedFiles.push(folder + "/" + (File(eachFile).name)); 319 | results.push(PSU.log("Copied:\t\t" + (File(eachFile).name))); 320 | } else { 321 | results.push(void 0); 322 | } 323 | } 324 | return results; 325 | }; 326 | 327 | /* Routine */ 328 | ref = this.productsToInstall; 329 | results = []; 330 | for (j = 0, len = ref.length; j < len; j++) { 331 | product = ref[j]; 332 | if (!G[product]) { 333 | PSU.log("\n" + product + " - Nothing to install\n"); 334 | continue; 335 | } 336 | switch (product) { 337 | case "SCRIPT": 338 | scriptsPath = app.path + "/" + (localize('$$$/ScriptingSupport/InstalledScripts=Presets/Scripts')); 339 | destinationPath = scriptsPath + "/" + G.COMPANY + "/" + G.PRODUCT_NAME; 340 | break; 341 | case "FLASH_PANEL": 342 | panelsPath = (G.SYSTEM_INSTALL ? Folder.commonFiles : Folder.userData) + "/Adobe/CS" + (G.CURRENT_PS_VERSION - 7) + "ServiceManager/extensions"; 343 | destinationPath = panelsPath + "/" + G.PRODUCT_ID; 344 | break; 345 | case "HTML_PANEL": 346 | panelsPath = (G.SYSTEM_INSTALL ? Folder.commonFiles : Folder.userData) + "/Adobe/" + (G.CURRENT_PS_VERSION === '14' ? 'CEPServiceManager4' : 'CEP') + "/extensions"; 347 | destinationPath = panelsPath + "/" + G.PRODUCT_ID; 348 | break; 349 | case "MAC_PLUGIN": 350 | if ($.os.match(/windows/i)) { 351 | destinationPath = ""; 352 | break; 353 | } 354 | pluginsPath = app.path + "/" + (localize('$$$/private/Plugins/DefaultPluginFolder=Plug-Ins')); 355 | destinationPath = pluginsPath + "/" + G.COMPANY; 356 | break; 357 | case "WIN_PLUGIN": 358 | if (!$.os.match(/windows/i)) { 359 | destinationPath = ""; 360 | break; 361 | } 362 | pluginsPath = app.path + "/" + (localize('$$$/private/Plugins/DefaultPluginFolder=Plug-Ins')); 363 | destinationPath = pluginsPath + "/" + G.COMPANY; 364 | break; 365 | case "EXTRA": 366 | 367 | /* TODO */ 368 | destinationPath = G.EXTRA; 369 | } 370 | if (destinationPath === "") { 371 | continue; 372 | } 373 | PSU.log("\n\nAdding " + product + "\n----------------------------\nDestination folder: " + (Folder(destinationPath).fsName)); 374 | 375 | /* Create destination Folder */ 376 | if (PSU.createFolder(destinationPath)) { 377 | PSU.log("Destination Folder successfully created.\n"); 378 | } else { 379 | PSU.log("ERROR! Can't create destination folder."); 380 | } 381 | 382 | /* Create the Folder for the source from the string path */ 383 | sourceFolder = Folder(G.CURRENT_PATH + "/" + G[product]); 384 | 385 | /* Create the Folder for the destination from the string path */ 386 | destinationFolder = Folder(destinationPath); 387 | 388 | /* Reset the array containing all the folders to be created in the destination */ 389 | this.foldersList = []; 390 | 391 | /* Fill the foldersList */ 392 | PSU.log("List of Folders to be copied for the " + product + ":"); 393 | 394 | /* Log is in the getFolderl */ 395 | getFoldersList(sourceFolder); 396 | 397 | /* Add the root folder to the list */ 398 | this.foldersList.unshift(sourceFolder); 399 | PSU.log("Folder: " + sourceFolder); 400 | 401 | /* Create Folders tree in destination */ 402 | PSU.log("\nCreating Folders in destination and copying files:\n"); 403 | 404 | /* RegExp for ignoring files to be copied */ 405 | ignoreRegExp = G.IGNORE ? new RegExp(G.IGNORE.join("|"), "i") : new RegExp("$."); 406 | ref1 = this.foldersList; 407 | for (k = 0, len1 = ref1.length; k < len1; k++) { 408 | eachFolder = ref1[k]; 409 | saveFolder = createRelativeFolder(destinationFolder, eachFolder, sourceFolder); 410 | allFiles = eachFolder.getFiles(function(f) { 411 | if (f instanceof Folder) { 412 | return false; 413 | } else { 414 | if ((f.name.match(ignoreRegExp)) != null) { 415 | return false; 416 | } 417 | return true; 418 | } 419 | }); 420 | if (allFiles.length) { 421 | copyFiles(allFiles, saveFolder); 422 | } 423 | } 424 | results.push(PSU.log("\nEnded copying files for " + product + ".")); 425 | } 426 | return results; 427 | }; 428 | 429 | PSInstaller.prototype.wrapUp = function() { 430 | alert("Complete!" + (G.ENABLE_LOG ? "\nAn installation LOG file has been created in:\n#{G.LOG_FILE}" : "")); 431 | alert("Restart Photoshop\nYou must restart the application in orded to use " + G.PRODUCT_NAME + ", thank you!"); 432 | if (G.README) { 433 | return (File(G.CURRENT_PATH + "/" + G.README)).execute(); 434 | } 435 | }; 436 | 437 | PSInstaller.prototype.createUninstaller = function() { 438 | var uninstall, uninstaller; 439 | uninstall = function(files, folders) { 440 | var e, eachFile, eachFolder, file, folder, j, k, len, performInstallation, uninstallErrors; 441 | if (!(performInstallation = confirm(G.PRODUCT_NAME + " Version " + G.PRODUCT_VERSION + " Uninstaller\nAre you sure to remove " + G.PRODUCT_NAME + "?"))) { 442 | return; 443 | } 444 | uninstallErrors = false; 445 | G.LOG_FILE = G.LOG_FILE_PATH + "/" + G.PRODUCT_NAME + " Uninstaller.log"; 446 | 447 | /* init the logging */ 448 | PSU.init(G.LOG_FILE, true); 449 | PSU.log("=======================================\n " + (new Date()) + "\n \tCompany: " + G.COMPANY + "\n \tProduct: " + G.PRODUCT_NAME + "\n \tProduct version: " + G.PRODUCT_VERSION + "\n \tApp: " + BridgeTalk.appName + "\n \tApp Version: " + app.version + "\n \tOS: " + $.os + "\n \tLocale: " + $.locale + "\n ---------------------------------------\n \tInstaller Version: " + G.INSTALLER_VERSION + "\n ======================================="); 450 | PSU.log("\nRemoving FILES..."); 451 | for (j = 0, len = files.length; j < len; j++) { 452 | eachFile = files[j]; 453 | try { 454 | file = File(eachFile); 455 | PSU.log("Removing:\t" + file.fsName + "..."); 456 | file.remove(); 457 | PSU.log("Done!"); 458 | } catch (_error) { 459 | e = _error; 460 | PSU.log("ERROR!"); 461 | uninstallErrors = true; 462 | } 463 | } 464 | PSU.log("---------------------------------------\n Removing FOLDERS..."); 465 | for (k = folders.length - 1; k >= 0; k += -1) { 466 | eachFolder = folders[k]; 467 | try { 468 | folder = Folder(eachFolder); 469 | PSU.log("Removing:\t" + folder.fsName + "..."); 470 | folder.remove(); 471 | PSU.log("Done!"); 472 | } catch (_error) { 473 | e = _error; 474 | PSU.log("ERROR!"); 475 | uninstallErrors = true; 476 | } 477 | if (uninstallErrors) { 478 | alert("Something went wrong!\nA uninstallation LOG file has been created in:\n" + G.LOG_FILE + ", please send it to " + G.CONTACT_INFO); 479 | throw Error("Restart Photoshop and see if the product has been uninstalled anyway."); 480 | } 481 | } 482 | return alert(G.PRODUCT_NAME + " successfully Removed\nPlease Restart Photoshop for the changes to take effect."); 483 | }; 484 | uninstaller = new File(G.CURRENT_PATH + "/" + G.PRODUCT_NAME + "_V" + G.PRODUCT_VERSION + " - UNINSTALLER.jsx"); 485 | if (!uninstaller.open('w')) { 486 | throwFileError(uninstaller, "Unable to Write the Uninstaller file"); 487 | } 488 | if (PSU.isMac()) { 489 | uninstaller.lineFeed = 'unix'; 490 | } 491 | uninstaller.writeln("var G = " + (G.toSource())); 492 | 493 | /* This won't work :-/ 494 | uninstaller.writeln "var PSU = #{PSU.toSource()}" 495 | */ 496 | uninstaller.writeln("var PSU=(function(GLOBAL){var createFolder,exceptionMessage,init,isMac,isWindows,log,that,throwFileError;that=this;this.enableLog=void 0;this.logFile=void 0;this.logFilePointer=void 0;isWindows=function(){return $.os.match(/windows/i);};isMac=function(){return!isWindows();};throwFileError=function(f,msg){if(msg==null){msg='';}return Error.runtimeError(9002,''+msg+''+f+': '+f.error+'.');};exceptionMessage=function(e){var fname,str;fname=!e.fileName?'???':decodeURI(e.fileName);str=' Message: '+e.message+' File: '+fname+'\\tLine: '+(e.line||'???')+'\\n\\tError Name: '+e.name+'\\n\\tError Number: '+e.number;if($.stack){str+=' '+$.stack;}return str;};log=function(msg){var file;if(!that.enableLog){return;}file=that.logFilePointer;if(!file.open('e')){throwFileError(file,'Unable to open Log file');}file.seek(0,2);if(!file.writeln(''+msg)){return throwFileError(file,'Unable to write to log file');}};createFolder=function(fptr){var rc;if(fptr==null){Error.runtimeError(19,'No Folder name specified');}if(fptr.constructor===String){fptr=new Folder(fptr);}if(fptr instanceof File){return createFolder(fptr.parent);}if(fptr.exists){return true;}if(!(fptr instanceof Folder)){log(fptr.constructor);Error.runtimeError(21,'Folder is not a Folder?');}if((fptr.parent!=null)&&!fptr.parent.exists){if(!createFolder(fptr.parent)){return false;}}rc=fptr.create();if(!rc){Error.runtimeError(9002,'Unable to create folder '+fptr+' ('+fptr.error+') Please create it manually and run this script again.');}return rc;};init=function(logFile,isLogEnabled){var file;if(!isLogEnabled){return;}that.enableLog=isLogEnabled;that.logFile=logFile;file=new File(that.logFile);if(file.exists){file.remove();}if(!file.open('w')){throwFileError(file,'Unable to open Log file');}if(isMac()){file.lineFeed='unix';}that.logFilePointer=file;};return{'isMac':isMac,'exceptionMessage':exceptionMessage,'log':log,'createFolder':createFolder,'init':init};})(this);"); 497 | uninstaller.writeln("var filesToRemove = " + (this.installedFiles.toSource()) + ";"); 498 | uninstaller.writeln("var foldersToRemove = " + (this.installedFolders.toSource()) + ";"); 499 | uninstaller.writeln("var uninstall = " + (uninstall.toSource()) + ";"); 500 | uninstaller.writeln("uninstall(filesToRemove, foldersToRemove);"); 501 | return uninstaller.close(); 502 | }; 503 | 504 | return PSInstaller; 505 | 506 | })(); 507 | 508 | try { 509 | psInstaller = new PSInstaller(); 510 | psInstaller.preflight(); 511 | psInstaller.init(); 512 | psInstaller.copy(); 513 | psInstaller.createUninstaller(); 514 | psInstaller.wrapUp(); 515 | } catch (_error) { 516 | e = _error; 517 | errorMessage = "Installation failed: " + (PSU.exceptionMessage(e)); 518 | PSU.log(errorMessage); 519 | alert("Something went wrong!\n" + errorMessage + "\nPlease contact " + G.CONTACT_INFO + ", thank you."); 520 | } 521 | 522 | 523 | /* EOF */ 524 | 525 | "psInstaller"; 526 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PS-Installer 2 | ============ 3 | 4 | Multi-version, semi-automatic installation script for Adobe Photoshop ® Add-ons (Adobe Extension Manager **has been discontinued** so it will be of no use for CC2015.x products. Please have a look at this [CC 2015 Survival Guide](http://www.davidebarranca.com/2015/06/html-panel-tips-17-cc2015-survival-guide/) and [CC 2015.5 Survival Guide](http://www.davidebarranca.com/2016/06/html-panel-tips-21-photoshop-cc2015-5-2016-survival-guide/)for more info). Compatibility is in the range CS3..CC2015.5. 5 | 6 | If you develop Photoshop add-ons and want them to be deployed to your users' machines avoiding AEM and/or Creative Cloud app installation, this little project of mine might be of some use for you too. 7 | 8 | ## Features 9 | 10 | PS-Installer is able to deploy to their PS or System default folders: 11 | 12 | - Scripts 13 | - HTML Panels 14 | - FLASH Panels 15 | - PS Mac Plug-ins 16 | - PS Win Plug-ins 17 | - Extra items (you provide the destination) 18 | 19 | You can choose between per User installation and System wide installation. 20 | 21 | # In a nutshell 22 | 23 | 1. Distribute your product as a ZIP, let your users unZip it. 24 | 2. Instruct the users to open Photoshop, ```File > Scripts > Browse...``` and point to the ```installer.jsx``` 25 | 3. The script will grab the assets for each product (say, an HTML Panel and a Script, or a Plugin) and deploy them into the correct folders depending on the PS version (i.e. HTML Panel for CC onwards, Flash one for CS6, etc.) 26 | 4. A complete LOG file is created, for debugging purposes 27 | 28 | An example folder tree is as follows - find it in the project's example folder: 29 | 30 | . 31 | ├── ASSETS 32 | │   ├── FLASH 33 | │   │   ├── CSXS 34 | │   │   │   └── manifest.xml 35 | │   │   └── example.swf 36 | │   ├── HTML 37 | │   │   ├── CSXS 38 | │   │   │   └── manifest.xml 39 | │   │   └── example.html 40 | │   ├── MAC_PLUGIN 41 | │   │   └── example.plugin 42 | │   ├── SCRIPT 43 | │   │   ├── Folder\ A 44 | │   │   │   ├── Subfolder\ A 45 | │   │   │   │   └── fileD.jsx 46 | │   │   │   └── fileC.jsx 47 | │   │   ├── Folder\ B 48 | │   │   │   └── fileE.jsx 49 | │   │   ├── fileA.jsx 50 | │   │   └── fileB.jsx 51 | │   ├── WIN_PLUGIN 52 | │   │   └── example.8bf 53 | │   └── readme.txt 54 | └── installer.jsx 55 | 56 | 57 | # Uninstallation 58 | The installer, while deploying your assets, will write a uninstaller script in its same folder - this can be used later on to remove the product. 59 | 60 | # Options 61 | You can configure the installer using properties of the object contained in the ```init.json``` sidecar file (which must be included alongside the ```installer.jsx```). Here they are documented. 62 | 63 | ### COMPANY 64 | [String] - Your company's name 65 | ### CONTACT_INFO 66 | [String] - The email for customer support inquiries. 67 | ### PRODUCT_NAME 68 | [String] - The descriptive name of the product to install. 69 | ### PRODUCT_ID 70 | [String] - Needed for Panels: ID as a reverse URL (e.g. com.cs-extensions.VitaminBW) 71 | ### PRODUCT_VERSION 72 | [String] - Yours (not PS), to be written in the LOG file 73 | ### MIN_VERSION 74 | [Number] - Minimum Photoshop version required (e.g. 13.0), leave ```undefined``` for no limit. 75 | ### MAX_VERSION 76 | [Number] - Maximum Photoshop version supported (e.g. 15.2), leave ```undefined``` for no limit. 77 | ### HTML_PANEL 78 | [String] - Relative Path for HTML Panel (no ```/``` at the start or end, e.g. "ASSETS/HTML") 79 | ### FLASH_PANEL 80 | [String] - Relative Path for Flash Panel (no ```/``` at the start or end, e.g. "ASSETS/FLASH") 81 | ### SCRIPT 82 | [String] - Relative Path for Script (no ```/``` at the start or end, e.g. "ASSETS/SCRIPT") 83 | ### MAC_PLUGIN 84 | [String] - Relative Path for Mac Plugin (no ```/``` at the start or end, e.g. "ASSETS/MAC") 85 | ### WIN_PLUGIN 86 | [String] - Relative Path for Win Plugin (no ```/``` at the start or end, e.g. "ASSETS/WIN") 87 | ### EXTRA 88 | [String] - Relative Path for Extra content (no ```/``` at the start or end, e.g. "ASSETS/EXTRA") - **not supported yet** 89 | ### README 90 | [String] - Relative Path for the optional Readme file (e.g. "ASSETS/Readme.txt") 91 | ### SYSTEM_INSTALL 92 | [Boolean] - false: install the Panel (if present) for all users - false: install it only for the current user. There's an open issue under Windows with Photoshop @64bit and Global installation - please set ```SYSTEM_INSTALL = false```. 93 | ### SHARED_PLUGINS 94 | [Boolean] - false: install the Filters (if present) in a shared location (for CC onwards) - false: install them on ```Photoshop /Plug-ins/```. true: installs them on ```/Library/Application Support/Adobe/Plug-Ins/CC/``` on Mac and ```\Program Files\Common Files\Adobe\Plug-Ins\CC``` on Windows. 95 | ### ENABLE_LOG 96 | [Boolean] - Enable installation logging (highly recommended) 97 | ### LOG_FILE_PATH 98 | [String] - Absolute Path for the LOG file, (no ```/``` at the start or end, e.g. "~/Desktop") 99 | ### LOG_FILE 100 | Leave it as an empty string - will be created at runtime as ```LOG_FILE_PATH/PRODUCT_NAME.log``` 101 | ### IGNORE 102 | [Array of String] RegExp for Files to ignore during installation, e.g. ```["^\\.\\w+"]``` - remember to escape ```\``` 103 | 104 | This first looked to me like a clever idea, yet I've seen it might break Signing/Timestamping. As a temporary workaround in case of such kind of errors during installation, use ```IGNORE: ["$."]``` (which basically ignores nothing - don't let it empty, the regex will match everything). 105 | 106 | --- 107 | 108 | ## Supported Versions 109 | 110 | Currently PS-Installer has been tested from Photoshop CS6 onwards (up to CC 2015), but theoretically it is backward compatible down to CS3. 111 | 112 | ## License 113 | Copyright (c) 2015 Davide Barranca, [MIT license](LICENSE). -------------------------------------------------------------------------------- /init.json: -------------------------------------------------------------------------------- 1 | { 2 | /* INFO ======================================= */ 3 | COMPANY: "CS-EXTENSIONS", 4 | CONTACT_INFO: "support@cs-extensions.com", 5 | PRODUCT_NAME: "Test product", 6 | PRODUCT_ID: "com.cs-extensions.test", 7 | PRODUCT_VERSION: "0.2.0", 8 | 9 | /* PRODUCTS =================================== */ 10 | 11 | /* Photoshop versions range */ 12 | 13 | /* Leave undefined for no limit */ 14 | MIN_VERSION: void 0, 15 | MAX_VERSION: void 0, 16 | 17 | /* Product Source Folders */ 18 | 19 | /* Leave undefined for no product to install */ 20 | 21 | /* Make sure tha paths (relative to the installer.jsx) do NOT start or end with "/" */ 22 | HTML_PANEL: "ASSETS/HTML", 23 | FLASH_PANEL: "ASSETS/FLASH", 24 | SCRIPT: "ASSETS/SCRIPT", 25 | MAC_PLUGIN: "ASSETS/MAC_PLUGIN", 26 | WIN_PLUGIN: "ASSETS/WIN_PLUGIN", 27 | EXTRA: void 0, 28 | 29 | /* If you have a readme file to display, put it here (path and filename) */ 30 | README: "ASSETS/readme.txt", 31 | 32 | /* System vs. User installation */ 33 | SYSTEM_INSTALL: false, 34 | /* Use PS shared folder for plugins */ 35 | SHARED_PLUGINS: false, 36 | 37 | /* DEBUG ===================================== */ 38 | INSTALLER_VERSION: "0.1.1", 39 | ENABLE_LOG: true, 40 | LOG_FILE_PATH: "~/Desktop", 41 | 42 | /* will be created as LOG_FILE_PATH / PRODUCT_NAME.log */ 43 | LOG_FILE: "", 44 | 45 | /* Array of RegExp for Files to be ignored (escape "\" -> "\\") 46 | Possibly a very bad idea, because the HTML Panel Signing and Timestamping 47 | may easily get corrupted if something is missing in the folder. 48 | Examples: 49 | IGNORE : [ 50 | "^\\.\\w+", // starting with . 51 | "^\\_\\w+", // starting with _ 52 | ] 53 | Leaving undefined means: don't ignore 54 | */ 55 | IGNORE: void 0, 56 | 57 | /* UTILS ===================================== */ 58 | CURRENT_PATH: File($.fileName).path, 59 | CURRENT_PS_VERSION: app.version.split('.')[0] 60 | }; --------------------------------------------------------------------------------