zapier-platform-cli
Version:
The CLI for apps in the Zapier Developer Platform.
314 lines (277 loc) • 10.4 kB
JavaScript
;
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var crypto = require('crypto');
var os = require('os');
var path = require('path');
var browserify = require('browserify');
var through = require('through2');
var _ = require('lodash');
var archiver = require('archiver');
var fs = require('fs');
var fse = require('fs-extra');
var klaw = require('klaw');
var constants = require('../constants');
var _require = require('./files'),
writeFile = _require.writeFile,
readFile = _require.readFile,
copyDir = _require.copyDir,
ensureDir = _require.ensureDir,
removeDir = _require.removeDir;
var _require2 = require('./display'),
prettyJSONstringify = _require2.prettyJSONstringify,
printStarting = _require2.printStarting,
printDone = _require2.printDone;
var _require3 = require('./api'),
checkCredentials = _require3.checkCredentials,
upload = _require3.upload,
callAPI = _require3.callAPI;
var _require4 = require('./misc'),
runCommand = _require4.runCommand,
isWindows = _require4.isWindows;
var stripPath = function stripPath(cwd, filePath) {
return filePath.split(cwd).pop();
};
// given entry points in a directory, return a list of files that uses
// could probably be done better with module-deps...
// TODO: needs to include package.json files too i think
// https://github.com/serverless/serverless-optimizer-plugin?
var requiredFiles = function requiredFiles(cwd, entryPoints) {
if (!_.endsWith(cwd, path.sep)) {
cwd += path.sep;
}
var argv = {
noParse: [undefined],
extensions: [],
ignoreTransform: [],
entries: entryPoints,
fullPaths: false,
builtins: false,
commondir: false,
bundleExternal: true,
basedir: cwd,
browserField: false,
detectGlobals: true,
insertGlobals: false,
insertGlobalVars: {
process: undefined,
global: undefined,
'Buffer.isBuffer': undefined,
Buffer: undefined
},
ignoreMissing: false,
debug: false,
standalone: undefined
};
var b = browserify(argv);
return new Promise(function (resolve, reject) {
b.on('error', reject);
var paths = [];
b.pipeline.get('deps').push(through.obj(function (row, enc, next) {
var filePath = row.file || row.id;
// why does browserify add /private + filePath?
paths.push(stripPath(cwd, filePath));
next();
}).on('end', function () {
paths.sort();
resolve(paths);
}));
b.bundle();
});
};
var listFiles = function listFiles(dir) {
return new Promise(function (resolve, reject) {
var paths = [];
var cwd = dir + path.sep;
klaw(dir).on('data', function (item) {
if (!item.stats.isDirectory()) {
paths.push(stripPath(cwd, item.path));
}
}).on('error', reject).on('end', function () {
paths.sort();
resolve(paths);
});
});
};
var forceIncludeDumbPath = function forceIncludeDumbPath(filePath /*, smartPaths*/) {
// we include smartPaths just incase you want to check the inclusion of some library
return filePath.endsWith('package.json') || filePath.endsWith('definition.json') || filePath.match(path.sep === '\\' ? /aws-sdk\\apis\\.*\.json/ : /aws-sdk\/apis\/.*\.json/) || global.argOpts['include-js-map'] && filePath.endsWith('.js.map');
};
var makeZip = function makeZip(dir, zipPath) {
var entryPoints = [path.resolve(dir, 'zapierwrapper.js'), path.resolve(dir, 'index.js')];
return requiredFiles(dir, entryPoints).then(function (smartPaths) {
return Promise.all([smartPaths, listFiles(dir)]);
}).then(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
smartPaths = _ref2[0],
dumbPaths = _ref2[1];
if (global.argOpts['disable-dependency-detection']) {
return dumbPaths;
}
var finalPaths = smartPaths.concat(dumbPaths.filter(forceIncludeDumbPath, smartPaths));
finalPaths = _.uniq(finalPaths);
finalPaths.sort();
if (global.argOpts.debug) {
console.log('\nZip files:');
finalPaths.map(function (filePath) {
return console.log(' ' + filePath);
});
console.log('');
}
return finalPaths;
}).then(function (paths) {
return new Promise(function (resolve, reject) {
var output = fs.createWriteStream(zipPath);
var zip = archiver('zip', {
store: true // Sets the compression method to STORE.
});
// listen for all archive data to be written
output.on('close', function () {
resolve();
});
zip.on('error', function (err) {
reject(err);
});
// pipe archive data to the file
zip.pipe(output);
paths.forEach(function (filePath) {
var basePath = path.dirname(filePath);
if (basePath === '.') {
basePath = undefined;
}
var name = path.join(dir, filePath);
zip.file(name, { name: filePath });
});
zip.finalize();
});
});
};
// Similar to utils.appCommand, but given a ready to go app
// with a different location and ready to go zapierwrapper.js.
var _appCommandZapierWrapper = function _appCommandZapierWrapper(dir, event) {
var app = require(dir + '/zapierwrapper.js');
event = Object.assign({}, event, {
calledFromCli: true
});
return new Promise(function (resolve, reject) {
app.handler(event, {}, function (err, resp) {
if (err) {
reject(err);
} else {
resolve(resp);
}
});
});
};
var build = function build(zipPath, wdir) {
wdir = wdir || process.cwd();
zipPath = zipPath || constants.BUILD_PATH;
var osTmpDir = fse.realpathSync(os.tmpdir());
var tmpDir = path.join(osTmpDir, 'zapier-' + crypto.randomBytes(4).toString('hex'));
return ensureDir(tmpDir).then(function () {
return ensureDir(constants.BUILD_DIR);
}).then(function () {
printStarting('Copying project to temp directory');
var filter = void 0;
if (process.env.SKIP_NPM_INSTALL) {
filter = function filter(dir) {
var isntBuild = dir.indexOf('.zip') === -1;
return isntBuild;
};
}
return copyDir(wdir, tmpDir, { filter: filter });
}).then(function () {
if (process.env.SKIP_NPM_INSTALL) {
return {};
}
printDone();
printStarting('Installing project dependencies');
return runCommand('npm', ['install', '--production'], { cwd: tmpDir });
}).then(function () {
printDone();
printStarting('Applying entry point file');
// TODO: should this routine for include exist elsewhere?
return readFile(path.join(tmpDir, 'node_modules', constants.PLATFORM_PACKAGE, 'include', 'zapierwrapper.js')).then(function (zapierWrapperBuf) {
return writeFile(tmpDir + '/zapierwrapper.js', zapierWrapperBuf.toString());
});
}).then(function () {
printDone();
printStarting('Building app definition.json');
return _appCommandZapierWrapper(tmpDir, { command: 'definition' });
}).then(function (rawDefinition) {
return Promise.all([writeFile(tmpDir + '/definition.json', prettyJSONstringify(rawDefinition.results)), Promise.resolve(rawDefinition.results)]);
}).then(function (_ref3) {
var _ref4 = _slicedToArray(_ref3, 2),
fileWriteError = _ref4[0],
rawDefinition = _ref4[1];
if (fileWriteError) {
if (global.argOpts.debug) {
console.log('\nFile Write Error:');
console.log(fileWriteError);
console.log('');
}
throw new Error('Unable to write ' + tmpDir + '/definition.json, please check file permissions!');
}
printDone();
printStarting('Validating project');
return Promise.all([_appCommandZapierWrapper(tmpDir, { command: 'validate' }), Promise.resolve(rawDefinition)]);
}).then(function (_ref5) {
var _ref6 = _slicedToArray(_ref5, 2),
validateResponse = _ref6[0],
rawDefinition = _ref6[1];
var errors = validateResponse.results;
if (errors.length) {
return Promise.resolve({
errors: {
validation: errors
}
});
}
// No need to mention specifically we're validating style checks as that's
// implied from `zapier validate`, though it happens as a separate process
return callAPI('/style-check', {
skipDeployKey: true,
method: 'POST',
body: rawDefinition
});
}).then(function (styleChecksResponse) {
var errors = styleChecksResponse.errors;
if (!_.isEmpty(errors)) {
throw new Error('We hit some validation errors, try running `zapier validate` to see them!');
}
printDone();
printStarting('Zipping project and dependencies');
return makeZip(tmpDir, wdir + path.sep + zipPath);
}).then(function () {
// tries to do a reproducible build at least
// https://blog.pivotal.io/labs/labs/barriers-deterministic-reproducible-zip-files
// https://reproducible-builds.org/tools/ or strip-nondeterminism
printDone();
printStarting('Testing build');
if (isWindows()) {
return {}; // TODO err, what should we do on windows?
}
return runCommand('find', ['.', '-exec', 'touch', '-t', '201601010000', '{}', '+'], { cwd: tmpDir });
}).then(function () {
printDone();
printStarting('Cleaning up temp directory');
return removeDir(tmpDir);
}).then(function () {
printDone();
return zipPath;
});
};
var buildAndUploadDir = function buildAndUploadDir(zipPath, appDir) {
zipPath = zipPath || constants.BUILD_PATH;
appDir = appDir || '.';
return checkCredentials().then(function () {
return build(zipPath, appDir);
}).then(function () {
return upload(zipPath, appDir);
});
};
module.exports = {
build: build,
buildAndUploadDir: buildAndUploadDir,
listFiles: listFiles,
requiredFiles: requiredFiles
};