cepy
Version:
An utility that helps debugging and packaging HTML5-based extensions for Adobe Creative Cloud applications.
304 lines (265 loc) • 11.2 kB
JavaScript
/**
* Copyright 2016-2017 Francesco Camarlinghi
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
;
const path = require('path'),
Promise = require('bluebird'),
chalk = require('chalk'),
log = require('debug')('cepy'),
_ = require('lodash');
const mkdirp = Promise.promisify(require('mkdirp')),
fs_readFile = Promise.promisify(require('fs').readFile),
fs_writeFile = Promise.promisify(require('fs').writeFile);
const hosts = require('./hosts.js');
// Custom delimiters that play nice with XML templates
const templateOptions = {
interpolate: /{%=([\s\S]+?)%}/g,
evaluate: /{%([\s\S]+?)%}/g,
escape: /{%-([\s\S]+?)%}/g,
};
/**
* Creates a .debug file from template.
*/
const generateDotDebug = function (outputPath, build)
{
log(`Generating ${chalk.cyan('.debug')} file...`);
const debugConfig = build.bundle.debug;
if (typeof debugConfig.port !== 'number' || debugConfig.port < 0)
{
throw new Error(`Invalid host debug port ${chalk.cyan(debugConfig.port)}.`);
}
// Process template
let templatePath = debugConfig.template;
if (typeof templatePath !== 'string' || templatePath.length === 0)
{
// Fall back to a default template if none is specified in extension properties
templatePath = path.resolve(__dirname, '../res/.debug');
}
return Promise
.resolve(fs_readFile(templatePath))
.then(template =>
{
const dotdebug = _.template(template, templateOptions)(build);
return fs_writeFile(path.join(outputPath, '.debug'), dotdebug)
.catch(error => { throw new Error(`Could not write .debug file: ${error}.`); });
});
};
/**
* Creates a CSXS manifest file from template.
*/
const generateBundleManifest = function (outputPath, build)
{
let hostList = [],
extensionList = [],
dispatchInfoList = [];
log('Generating bundle manifest...');
return Promise.resolve()
// Generate manifest data for each extension
.then(() =>
{
return Promise.map(build.extensions, extension =>
{
// Add this extension to bundle extension list
extensionList.push(`<Extension Id="${extension.id}" Version="${extension.version}" />`);
// Build manifest data for this extension and add it to DispatchInfoList
let templatePath = extension.manifest;
if (typeof templatePath !== 'string' || !templatePath.length)
{
// Fall back to a default template if none is specified in extension properties
templatePath = path.join(__dirname, '../res/manifest.extension.xml');
}
return fs_readFile(templatePath, 'utf8')
.then(template =>
{
const extensionManifest = _.template(template, templateOptions)(extension);
dispatchInfoList.push(extensionManifest)
})
.catch(() => { throw new Error(`Unable to produce manifest data for extension "${chalk.cyan(extension.id)}".`) });
});
})
// Generate bundle manifest
.then(() =>
{
// <Host> list
if (Array.isArray(build.products))
{
for (const product of build.products)
{
if (typeof build.families === 'string')
{
// Minimum required family
const hostInfo = hosts.getProduct(product, build.families);
for (const hostId of hostInfo.ids)
{
hostList.push(`<Host Name="${hostId}" Version="${hostInfo.version.min.toFixed(1)}" />`);
}
}
else
{
// Family range
const hostInfo = hosts.getProduct(product, build.families[0]),
hostVersionRange = hosts.getVersionRange(product, build.families);
for (const hostId of hostInfo.ids)
{
hostList.push(`<Host Name="${hostId}" Version="[${hostVersionRange.min.toFixed(1)},${hostVersionRange.max.toFixed(1)}]" />`);
}
}
}
}
// Process template
let templatePath = build.bundle.manifest;
if (typeof templatePath !== 'string' || templatePath.length === 0)
{
// Fall back to a default template if none is specified in bundle properties
// As families are sorted alphabetically (@see build.js), the first one
// is guaranteed to be the lowest we need to support
const familyName = (typeof build.families === 'string') ? build.families : build.families[0];
templatePath = path.resolve(__dirname, `../res/manifest.bundle.${familyName}.xml`);
}
return fs_readFile(templatePath, 'utf8')
.then(template =>
{
const data = {
bundle: build.bundle,
hostList,
extensionList,
dispatchInfoList,
};
const bundleManifest = _.template(template, templateOptions)(data);
// Make sure CSXS folder exists in output folder
return mkdirp(path.join(outputPath, 'CSXS'))
.then(() => fs_writeFile(path.join(outputPath, 'CSXS/manifest.xml'), bundleManifest));
})
.catch(error => { throw new Error(`Unable to produce bundle manifest: ${error}`); });
});
};
/**
* Populates extension MXI file template.
*/
const generateMXI = function (outputPath, builds, packaging)
{
// Use bundle information from the first build
const mxiFilename = `${builds[0].baseName}.mxi`;
log(`Generating ${mxiFilename} file from template...`);
let targets = {},
fileList = [],
productList = [];
// Get the required info about the products targeted by the bundles in the package
for (let build of builds)
{
const supportsFamilyRange = Array.isArray(build.families),
targetFamilies = supportsFamilyRange ? build.families : [build.families];
for (let product of build.products)
{
if (!targets.hasOwnProperty(product))
{
targets[product] = {
minFamily: targetFamilies[0],
version: {
min: null,
max: null,
},
};
}
// If current range values are higher or lower than the stored ones, save them
const range = hosts.getVersionRange(product, targetFamilies);
let version = targets[product].version;
if (version.min === null || range.min < version.min)
{
version.min = range.min;
}
if (supportsFamilyRange)
{
// Only save the maximum targeted version if we actually have a range of supported families
if (version.max === null || range.max > version.max)
{
version.max = range.max;
}
}
}
}
// Create <FileList> list
for (let build of builds)
{
for (let product of build.products)
{
const target = targets[product],
version = target.version;
if (version.max === null)
{
fileList.push(`<file products="${hosts.mapToFamilyName(product)}" minVersion="${version.min.toFixed(1)}" source="${build.outputFile}" destination="" file-type="CSXS" />`);
}
else
{
fileList.push(`<file products="${hosts.mapToFamilyName(product)}" minVersion="${version.min.toFixed(1)}" maxVersion="${version.max.toFixed(1)}" source="${build.outputFile}" destination="" file-type="CSXS" />`);
}
}
}
// Create <product> list
// NOTE: only some products support the "familyname" attribute so we should filter them out
// (see https://helpx.adobe.com/extension-manager/kb/general-mxi-elements.html#id_64891)
const familyNameProducts = ['illustrator', 'incopy', 'indesign', 'photoshop'];
for (let product in targets)
{
const target = targets[product],
version = target.version;
if (version.max === null)
{
if (familyNameProducts.indexOf(product) > -1)
{
productList.push(`<product familyname="${hosts.getProduct(product, target.minFamily).familyname}" version="${version.min.toFixed(1)}" primary="true" />`);
}
else
{
productList.push(`<product name="${hosts.getProduct(product, target.minFamily).familyname}" version="${version.min.toFixed(1)}" primary="true" />`);
}
}
else
{
if (familyNameProducts.indexOf(product) > -1)
{
productList.push(`<product familyname="${hosts.getProduct(product, target.minFamily).familyname}" version="${version.min.toFixed(1)}" maxversion="${version.max.toFixed(1)}" primary="true" />`);
}
else
{
productList.push(`<product name="${hosts.getProduct(product, target.minFamily).familyname}" version="${version.min.toFixed(1)}" maxversion="${version.max.toFixed(1)}" primary="true" />`);
}
}
}
// Process template
let templatePath = packaging.mxi;
if (typeof templatePath !== 'string' || templatePath.length === 0)
{
// Fall back to a default template if none is specified in packaging properties
templatePath = path.resolve(__dirname, '../res/manifest.mxi.xml');
}
return fs_readFile(templatePath, 'utf8')
.then(template =>
{
const data = {
bundle: builds[0].bundle,
productList,
fileList,
};
const mxiManifest = _.template(template, templateOptions)(data);
return fs_writeFile(path.join(outputPath, mxiFilename), mxiManifest);
})
.catch(error => { throw new Error(`Unable to produce MXI manifest: ${error}`); });
};
module.exports = {
generateDotDebug,
generateBundleManifest,
generateMXI,
};