UNPKG

zapier-platform-cli

Version:

The CLI for apps in the Zapier Developer Platform.

314 lines (277 loc) 10.4 kB
'use strict'; 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 };