UNPKG

nativescript

Version:

Command-line interface for building NativeScript projects

715 lines • 26.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.stripComments = stripComments; exports.doesCurrentNpmCommandMatch = doesCurrentNpmCommandMatch; exports.someWithRegExps = someWithRegExps; exports.getCurrentNpmCommandArgv = getCurrentNpmCommandArgv; exports.isInstallingNativeScriptGlobally = isInstallingNativeScriptGlobally; exports.createRegExp = createRegExp; exports.regExpEscape = regExpEscape; exports.getShortPluginName = getShortPluginName; exports.executeActionByChunks = executeActionByChunks; exports.deferPromise = deferPromise; exports.settlePromises = settlePromises; exports.getPropertyName = getPropertyName; exports.quoteString = quoteString; exports.createGUID = createGUID; exports.stringReplaceAll = stringReplaceAll; exports.isRequestSuccessful = isRequestSuccessful; exports.isResponseRedirect = isResponseRedirect; exports.formatListOfNames = formatListOfNames; exports.getRelativeToRootPath = getRelativeToRootPath; exports.setIsInteractive = setIsInteractive; exports.isInteractive = isInteractive; exports.toBoolean = toBoolean; exports.block = block; exports.isNumberWithoutExponent = isNumberWithoutExponent; exports.fromWindowsRelativePathToUnix = fromWindowsRelativePathToUnix; exports.isNullOrWhitespace = isNullOrWhitespace; exports.getCurrentEpochTime = getCurrentEpochTime; exports.sleep = sleep; exports.createTable = createTable; exports.getMessageWithBorders = getMessageWithBorders; exports.remove = remove; exports.trimSymbol = trimSymbol; exports.parseJson = parseJson; exports.getFuturesResults = getFuturesResults; exports.appendZeroesToVersion = appendZeroesToVersion; exports.decorateMethod = decorateMethod; exports.hook = hook; exports.isPromise = isPromise; exports.attachAwaitDetach = attachAwaitDetach; exports.connectEventually = connectEventually; exports.getHash = getHash; exports.connectEventuallyUntilTimeout = connectEventuallyUntilTimeout; exports.getProjectFilesConfig = getProjectFilesConfig; exports.getPidFromiOSSimulatorLogs = getPidFromiOSSimulatorLogs; exports.getValueFromNestedObject = getValueFromNestedObject; exports.getWinRegPropertyValue = getWinRegPropertyValue; exports.stringify = stringify; exports.getFixedLengthDateString = getFixedLengthDateString; exports.getFormattedDateComponent = getFormattedDateComponent; exports.getFormattedMilliseconds = getFormattedMilliseconds; exports.annotate = annotate; exports.hasValidAndroidSigning = hasValidAndroidSigning; const uuid_1 = require("uuid"); const os_1 = require("os"); const constants_1 = require("./constants"); const crypto = require("crypto"); const _ = require("lodash"); const Table = require("cli-table3"); const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm; function stripComments(content) { const newContent = content.replace(STRIP_COMMENTS, ""); return newContent; } function doesCurrentNpmCommandMatch(patterns) { const currentNpmCommandArgv = getCurrentNpmCommandArgv(); let result = false; if (currentNpmCommandArgv.length) { result = someWithRegExps(currentNpmCommandArgv, patterns); } return result; } /** * Equivalent of lodash's some, but instead of lambda, just pass array of Regular Expressions. * If any of them matches any of the given elements, true is returned. * @param {string[]} array Elements to be checked. * @param {RegExp[]} patterns Regular expressions to be tested * @returns {boolean} True in case any element of the array matches any of the patterns. False otherwise. */ function someWithRegExps(array, patterns) { return _.some(array, (item) => _.some(patterns, (pattern) => !!item.match(pattern))); } function getCurrentNpmCommandArgv() { let result = []; if (process.env && process.env.npm_config_argv) { try { const npmConfigArgv = JSON.parse(process.env.npm_config_argv); result = npmConfigArgv.original || []; } catch (error) { // ignore } } return result; } function isInstallingNativeScriptGlobally() { return (isInstallingNativeScriptGloballyWithNpm() || isInstallingNativeScriptGloballyWithYarn()); } function isInstallingNativeScriptGloballyWithNpm() { const isInstallCommand = doesCurrentNpmCommandMatch([/^install$/, /^i$/]); const isGlobalCommand = doesCurrentNpmCommandMatch([/^--global$/, /^-g$/]); const hasNativeScriptPackage = doesCurrentNpmCommandMatch([ /^nativescript(@.*)?$/, /nativescript-(.*)\.tgz?$/, ]); return isInstallCommand && isGlobalCommand && hasNativeScriptPackage; } function isInstallingNativeScriptGloballyWithYarn() { // yarn populates the same env used by npm - npm_config_argv, so check it for yarn specific command const isInstallCommand = doesCurrentNpmCommandMatch([/^add$/]); const isGlobalCommand = doesCurrentNpmCommandMatch([/^global$/]); const hasNativeScriptPackage = doesCurrentNpmCommandMatch([ /^nativescript(@.*)?$/, /nativescript-(.*)\.tgz?$/, ]); return isInstallCommand && isGlobalCommand && hasNativeScriptPackage; } /** * Creates regular expression from input string. * The method replaces all occurences of RegExp special symbols in the input string with \<symbol>. * @param {string} input The string from which a regular expression should be created. * @param {string} opts RegExp options, for example "gm" - global and multiline. * @returns {RegExp} The regular expression created from the input string. */ function createRegExp(input, opts) { if (!input || !_.isString(input)) { throw new Error("Input must be a string."); } const escapedSource = regExpEscape(input); return new RegExp(escapedSource, opts); } /** * Escapes all special symbols used in regex. * @param {string} input The string in which to replace the special regexp symbols. * @returns {string} A string in which all regex symbols are escaped. */ function regExpEscape(input) { // https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function getShortPluginName(pluginName) { return sanitizePluginName(pluginName).replace(/[\-]/g, "_"); } function sanitizePluginName(pluginName) { // avoid long plugin names, exclude the npm module scope (@scope/nativescript-plugin) from the android plugin name return pluginName.split("/").pop(); } async function executeActionByChunks(initialData, chunkSize, elementAction) { let arrayToChunk; let action; if (_.isArray(initialData)) { arrayToChunk = initialData; action = (element) => elementAction(element, initialData.indexOf(element)); } else { arrayToChunk = _.keys(initialData); action = (key) => elementAction(initialData[key], key); } const chunks = _.chunk(arrayToChunk, chunkSize); for (const chunk of chunks) { await Promise.all(_.map(chunk, (element) => action(element))); } } function deferPromise() { let resolve; let reject; let isResolved = false; let isRejected = false; let promise; let result; promise = new Promise((innerResolve, innerReject) => { resolve = (value) => { isResolved = true; result = value; return innerResolve(value); }; reject = (reason) => { isRejected = true; return innerReject(reason); }; }); return { promise, resolve, reject, isResolved: () => isResolved, isRejected: () => isRejected, isPending: () => !isResolved && !isRejected, getResult: () => result, }; } /** * Executes all promises and does not stop in case any of them throws. * Returns the results of all promises in array when all are successfully resolved. * In case any of the promises is rejected, rejects the resulted promise with all accumulated errors. * @param {Promise<T>[]} promises Promises to be resolved. * @returns {Promise<T[]>} New promise which will be resolved with the results of all promises. */ function settlePromises(promises) { return new Promise((resolve, reject) => { let settledPromisesCount = 0; const results = []; const errors = []; const length = promises.length; if (!promises.length) { resolve(null); } _.forEach(promises, (currentPromise, index) => { currentPromise .then((result) => { results[index] = result; }) .catch((err) => { // Accumulate all errors. errors.push(err); }) .then(() => { settledPromisesCount++; if (settledPromisesCount === length) { errors.length ? reject(new Error(`Multiple errors were thrown:${os_1.EOL}${errors .map((e) => e.message || e) .join(os_1.EOL)}`)) : resolve(results); } }) .catch(); }); }); } function getPropertyName(func) { if (func) { const match = func .toString() .match(/(?:return\s+?.*\.(.+);)|(?:=>\s*?.*\.(.+)\b)/); if (match) { return (match[1] || match[2]).trim(); } } return null; } function bashQuote(s) { if (s[0] === "'" && s[s.length - 1] === "'") { return s; } // replace ' with '"'"' and wrap in '' return "'" + s.replace(/'/g, "'\"'\"'") + "'"; } function cmdQuote(s) { if (s[0] === '"' && s[s.length - 1] === '"') { return s; } // replace " with \" and wrap in "" return '"' + s.replace(/"/g, '\\"') + '"'; } function quoteString(s) { if (!s) { return s; } return (0, os_1.platform)() === "win32" ? cmdQuote(s) : bashQuote(s); } function createGUID(useBraces) { let output; useBraces = useBraces === undefined ? true : useBraces; if (useBraces) { output = "{" + (0, uuid_1.v4)() + "}"; } else { output = (0, uuid_1.v4)(); } return output; } function stringReplaceAll(inputString, find, replace) { return inputString.split(find).join(replace); } function isRequestSuccessful(request) { return request.statusCode >= 200 && request.statusCode < 300; } function isResponseRedirect(response) { return _.includes([301, 302, 303, 307, 308], response.statusCode); } function formatListOfNames(names, conjunction) { conjunction = conjunction === undefined ? "or" : conjunction; if (names.length <= 1) { return names[0]; } else { return (_.initial(names).join(", ") + " " + conjunction + " " + names[names.length - 1]); } } function getRelativeToRootPath(rootPath, filePath) { const relativeToRootPath = filePath.substr(rootPath.length); return relativeToRootPath; } let customIsInteractive; function setIsInteractive(override) { customIsInteractive = override; } function isInteractive() { if (customIsInteractive) { return customIsInteractive(); } const result = isRunningInTTY() && !isCIEnvironment(); return result; } /** * Checks if current process is running in Text Terminal (TTY) */ function isRunningInTTY() { return (process.stdout && process.stdout.isTTY && process.stdin && process.stdin.isTTY); } function isCIEnvironment() { // The following CI environments set their own environment variables that we respect: // travis: "CI", // circleCI: "CI", // jenkins: "JENKINS_HOME" return !!(process.env && (process.env.CI || process.env.JENKINS_HOME)); } function toBoolean(str) { return !!(str && str.toString && str.toString().toLowerCase() === "true"); } function block(operation) { if (isInteractive()) { process.stdin.setRawMode(false); } operation(); if (isInteractive()) { process.stdin.setRawMode(true); } } function isNumberWithoutExponent(n) { const parsedNum = parseFloat(n); return (!isNaN(parsedNum) && isFinite(n) && n.toString && n.toString() === parsedNum.toString()); } function fromWindowsRelativePathToUnix(windowsRelativePath) { return windowsRelativePath.replace(/\\/g, "/"); } function isNullOrWhitespace(input) { if (!input && input !== false) { return true; } return _.isString(input) && input.replace(/\s/gi, "").length < 1; } function getCurrentEpochTime() { const dateTime = new Date(); return dateTime.getTime(); } async function sleep(ms) { return new Promise((resolve, reject) => { setTimeout(async () => resolve(), ms); }); } function createTable(headers, data) { const table = new Table({ head: headers, chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" }, }); _.forEach(data, (row) => table.push(row)); return table; } function getMessageWithBorders(message, spanLength = 3) { if (!message) { return ""; } const longestRowLength = message.split("\n").sort((a, b) => { return b.length - a.length; })[0].length; let border = "*".repeat(longestRowLength + 2 * spanLength); // * 2 for both sides if (border.length % 2 === 0) { border += "*"; // the * should always be an odd number in order to get * in each edge (we will remove the even *s below) } border = border.replace(/\*\*/g, "* "); // ***** => * * * in order to have similar padding to the side borders const formatRow = function (row) { return (_.padEnd("*", spanLength) + _.padEnd(row, border.length - 2 * spanLength) + _.padStart("*", spanLength) + os_1.EOL); }; const emptyRow = formatRow(""); const messageWithBorders = []; messageWithBorders.push(os_1.EOL, border + os_1.EOL, emptyRow, ...message.split("\n").map((row) => formatRow(row.trim())), emptyRow, border + os_1.EOL, os_1.EOL); return messageWithBorders.join(""); } function remove(array, predicate, numberOfElements) { numberOfElements = numberOfElements || 1; const index = _.findIndex(array, predicate); if (index === -1) { return new Array(); } return array.splice(index, numberOfElements); } function trimSymbol(str, symbol) { while (str.charAt(0) === symbol) { str = str.substr(1); } while (str.charAt(str.length - 1) === symbol) { str = str.substr(0, str.length - 1); } return str; } function parseJson(data) { // Replace BOM from the header of the file if it exists return JSON.parse(data.replace(/^\uFEFF/, "")); } // TODO: Use generic for predicatе predicate: (element: T|T[]) when TypeScript support this. async function getFuturesResults(promises, predicate) { const results = await Promise.all(promises); return _(results).filter(predicate).flatten().value(); } /** * Appends zeroes to a version string until it reaches a specified length. * @param {string} version The version on which to append zeroes. * @param requiredVersionLength The required length of the version string. * @returns {string} Appended version string. In case input is null, undefined or empty string, it is returned immediately without appending anything. */ function appendZeroesToVersion(version, requiredVersionLength) { if (version) { const zeroesToAppend = requiredVersionLength - version.split(".").length; for (let index = 0; index < zeroesToAppend; index++) { version += ".0"; } } return version; } function decorateMethod(before, after) { return (target, propertyKey, descriptor) => { const sink = descriptor.value; descriptor.value = async function (...args) { let newMethods = null; if (before) { newMethods = await before(sink, this, args); } let hasBeenReplaced = false; let result; if (newMethods && newMethods.length) { const replacementMethods = _.filter(newMethods, (f) => _.isFunction(f)); if (replacementMethods.length > 0) { hasBeenReplaced = true; const chainedReplacementMethod = _.reduce(replacementMethods, (prev, next) => next.bind(next, args, prev), sink.bind(this)); result = chainedReplacementMethod(); } } if (!hasBeenReplaced) { result = sink.apply(this, args); } if (after) { return await after(sink, this, result, args); } return result; }; }; } function hook(commandName) { function getHooksService(self) { let hooksService = self.$hooksService; if (!hooksService) { const injector = self.$injector; if (!injector) { throw Error("Type with hooks needs to have either $hooksService or $injector injected."); } hooksService = injector.resolve("hooksService"); } return hooksService; } function prepareArguments(method, args, hooksService) { annotate(method); const argHash = {}; for (let i = 0; i < method.$inject.args.length; ++i) { argHash[method.$inject.args[i]] = args[i]; } argHash.$arguments = args; const result = {}; result[hooksService.hookArgsName] = argHash; return result; } return decorateMethod(async (method, self, args) => { const hooksService = getHooksService(self); return hooksService.executeBeforeHooks(commandName, prepareArguments(method, args, hooksService)); }, async (method, self, resultPromise, args) => { const result = await resultPromise; const hooksService = getHooksService(self); await hooksService.executeAfterHooks(commandName, prepareArguments(method, args, hooksService)); return Promise.resolve(result); }); } function isPromise(candidateFuture) { return !!(candidateFuture && typeof candidateFuture.then === "function"); } async function attachAwaitDetach(eventName, eventEmitter, eventHandler, operation) { eventEmitter.on(eventName, eventHandler); try { await operation; } finally { eventEmitter.removeListener(eventName, eventHandler); } } async function connectEventually(factory, handler) { async function tryConnect() { const tryConnectAfterTimeout = setTimeout.bind(undefined, tryConnect, 1000); const socket = await factory(); socket.on("connect", () => { socket.removeListener("error", tryConnectAfterTimeout); handler(socket); }); socket.on("error", tryConnectAfterTimeout); } await tryConnect(); } function getHash(str, options) { return crypto .createHash((options && options.algorithm) || "sha256") .update(str) .digest((options && options.encoding) || "hex"); } async function connectEventuallyUntilTimeout(factory, timeout) { return new Promise(async (resolve, reject) => { let lastKnownError; let isResolved = false; const connectionTimer = setTimeout(function () { if (!isResolved) { isResolved = true; reject(lastKnownError || new Error(`Unable to connect for ${timeout}ms`)); } }, timeout); async function tryConnect() { const tryConnectAfterTimeout = (error) => { if (isResolved) { return; } lastKnownError = error; setTimeout(tryConnect, 1000); }; try { const socket = await factory(); socket.on("connect", () => { socket.removeListener("error", tryConnectAfterTimeout); isResolved = true; clearTimeout(connectionTimer); resolve(socket); }); socket.on("error", tryConnectAfterTimeout); } catch (e) { lastKnownError = e; tryConnectAfterTimeout(e); } } await tryConnect(); }); } function getProjectFilesConfig(opts) { const projectFilesConfig = { configuration: opts.isReleaseBuild ? constants_1.Configurations.Release.toLowerCase() : constants_1.Configurations.Debug.toLowerCase(), }; return projectFilesConfig; } /** * Tries to find the process id (PID) of the specified application identifier. * This is specific implementation for iOS Simulator, where the running applications are real processes. * Their PIDs are printed in a specific format in the the logs, once the application is started. * @param {string} applicationIdentifier Application Identifier of the app for which we try to get the PID. * @param {string} logLine Line that may contain the PID of the process. * @returns {string} The PID of the searched application identifier in case it's found in the current line, null otherwise. */ function getPidFromiOSSimulatorLogs(applicationIdentifier, logLine) { if (logLine) { const pidRegExp = new RegExp(`${applicationIdentifier}:\\s?(\\d+)`); const pidMatch = logLine.match(pidRegExp); return pidMatch ? pidMatch[1] : null; } return null; } function getValueFromNestedObject(obj, key) { function _getValueRecursive(_obj, _key) { if (_.has(_obj, _key)) { return [_obj]; } const res = []; _.forEach(_obj, (v, k) => { if (typeof v === "object" && typeof k === "string" && !_.startsWith(k, "$") && !_.endsWith(k.toLowerCase(), "service") && (v = _getValueRecursive(v, _key)).length) { res.push.apply(res, v); } }); return res; } return _.head(_getValueRecursive(obj, key)); } function getWinRegPropertyValue(key, propertyName) { return new Promise((resolve, reject) => { const Winreg = require("winreg"); const regKey = new Winreg({ hive: Winreg.HKLM, key: key, }); regKey.get(propertyName, (err, value) => { if (err) { reject(err); } else { resolve(value); } }); }); } function stringify(value, replacer, space) { return JSON.stringify(value, replacer, space || 2); } //2019-01-07 18:29:50.745 function getFixedLengthDateString() { const currentDate = new Date(); const year = currentDate.getFullYear(); const month = getFormattedDateComponent(currentDate.getMonth() + 1); const day = getFormattedDateComponent(currentDate.getDate()); const hour = getFormattedDateComponent(currentDate.getHours()); const minutes = getFormattedDateComponent(currentDate.getMinutes()); const seconds = getFormattedDateComponent(currentDate.getSeconds()); const milliseconds = getFormattedMilliseconds(currentDate); return `${[year, month, day].join("-")} ${[hour, minutes, seconds].join(":")}.${milliseconds}`; } function getFormattedDateComponent(component) { const stringComponent = component.toString(); return stringComponent.length === 1 ? `0${stringComponent}` : stringComponent; } function getFormattedMilliseconds(date) { let milliseconds = date.getMilliseconds().toString(); while (milliseconds.length < 3) { milliseconds = `0${milliseconds}`; } return milliseconds; } //--- begin part copied from AngularJS //The MIT License // //Copyright (c) 2010-2012 Google, Inc. http://angularjs.org // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in //all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN //THE SOFTWARE. const CLASS_NAME = /class\s+([A-Z].+?)(?:\s+.*?)?\{/; const CONSTRUCTOR_ARGS = /constructor\s*([^\(]*)\(\s*([^\)]*)\)/m; const FN_NAME_AND_ARGS = /^(?:function)?\s*([^\(]*)\(\s*([^\)]*)\)\s*(=>)?\s*[{_]/m; const FN_ARG_SPLIT = /,/; const FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; function annotate(fn) { let $inject, fnText, argDecl; if (typeof fn === "function") { if (!($inject = fn.$inject) || $inject.name !== fn.name) { $inject = { args: [], name: "" }; fnText = fn.toString().replace(STRIP_COMMENTS, ""); let nameMatch = fnText.match(CLASS_NAME); if (nameMatch) { argDecl = fnText.match(CONSTRUCTOR_ARGS); } else { nameMatch = argDecl = fnText.match(FN_NAME_AND_ARGS); } $inject.name = nameMatch && nameMatch[1]; if (argDecl && fnText.length) { argDecl[2].split(FN_ARG_SPLIT).forEach((arg) => { arg.replace(FN_ARG, (all, underscore, name) => $inject.args.push(name)); }); } fn.$inject = $inject; } } return $inject; } /** * Returns true if all Android signing options are provided, false otherwise. * @param {IAndroidSigningData} signingData The signing data to be validated. * @return {void} */ function hasValidAndroidSigning(signingData) { const isValid = signingData && signingData.keyStorePath && signingData.keyStorePassword && signingData.keyStoreAlias && signingData.keyStoreAliasPassword; return !!isValid; } //--- end part copied from AngularJS //# sourceMappingURL=helpers.js.map