diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..be5e4e4 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,7 @@ +{ + "rules": { + "max-depth": "off", + "no-loop-func": "off", + "promise/always-return": "off" + } +} diff --git a/.npmignore b/.npmignore index 723d63b..443d7c6 100644 --- a/.npmignore +++ b/.npmignore @@ -12,6 +12,7 @@ junit.xml node_modules npm-debug.log yarn-error.log +yarn.lock /src /test yarn.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ce5371..7a1b8b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,48 @@ +# v2.0.0 + + * BREAKING CHANGE(config): `config` command no longer returns status as apart of JSON output. + * BREAKING CHANGE(config): `config` command does not return current value when doing a `set`, + `push`, or `unshift`. + * BREAKING CHANGE(config): `config list` command no longer supports filtering, use `config get` + instead. + * BREAKING CHANGE(config): Write operations such as `set` return `"OK"` instead of `"Saved"`. + * BREAKING CHANGE: Dropped support for appcd plugin API version 1.0 and require API version 2.0, + which was initially introduced in `appcd@4.0.0`. + * feat(project): Project service with endpoints for `new`, `build`, `run`, `clean`, and info. + [(DAEMON-26)](https://jira.appcelerator.org/browse/DAEMON-26) + [(DAEMON-21)](https://jira.appcelerator.org/browse/DAEMON-21) + * feat(cli): Added `new` command. [(DAEMON-301)](https://jira.appcelerator.org/browse/DAEMON-301) + * feat(cli): Added `clean` command. + [(DAEMON-327)](https://jira.appcelerator.org/browse/DAEMON-327) + * feat(cli): Added `build` and `run` commands. + [(DAEMON-16)](https://jira.appcelerator.org/browse/DAEMON-16) + * feat(cli): Added auth commands `login`, `logout`, `whoami`, and `switch`. + [(DAEMON-300)](https://jira.appcelerator.org/browse/DAEMON-300) + * feat(legacy): Legacy Titanium CLI bootstrap for loading a Titanium SDK and running a `build` or + `clean` command. For differences between this and Titanium CLI v5, see the + [readme](https://github.com/appcelerator/appcd-plugin-titanium/blob/master/src/legacy/README.md). + * feat(legacy): Added support for remote encryption. + * feat(cli:sdk): Added aliases to sdk commands (i, ls, rm). + * feat(sdk): Added `find` endpoint to SDK service to get info about an installed Titanium SDK. + * feat(sdk): Added progress bars during SDK installation. + * feat(module): Added `/module/check-downloads` endpoint. + * feat(module): Added `/module/install` endpoint. + * feat(module): Added automatic checking of new Titanium module downloads. + * feat: Support for Titanium-specific telemetry. + * feat: Added HTTP proxy support. + * feat: Adopted appcd 4.x new `appcd.config.*`. + * feat(cli): Added `register` command to replace old --import flag. + * feat(project): Added `/project/register` endpoint. + * feat(amplify): Upgraded from AMPLIFY appcd plugin v1.x to v2.x. + * refactor: Updated to latest cli-kit with support for the new client/server architecture. + * refactor: Updated `config` command actions to be subcommands with improved help output. + * fix(legacy): Improved logging of uncaught exceptions and rejections. + * chore: Removed `source-map-support` as `appcd-plugin` already hooks it up. + * chore: Added plugin API version 2.x. + * chore: Transpile for Node 10 instead of Node 8. Not a breaking change as appcd has always + guaranteed Node 10 or newer. + * chore: Updated dependencies. + # v1.8.2 (Jan 26, 2021) * refactor: Updated to cli-kit@1.9.3 adding support for the new client/server architecture. diff --git a/README.md b/README.md index 256baa3..f1f08dc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@ Titanium SDK services for the Appc Daemon. -## Services +> This plugin requires appcd plugin API version 2.x which was introduced in appcd@4.0.0 and the +> AMPLIFY appcd plugin v2.x. + +## Service Endpoints * [SDKs](#SDKs) - [`/sdk/list/installed`](#sdklistinstalled) @@ -131,6 +134,24 @@ $ appcd exec /titanium/latest/sdk/list/releases } ``` +### `/sdk/find/:name?` + +Returns information about the specified installed SDK. + +#### CLI Usage + +```sh +$ appcd exec /titanium/latest/sdk/find +``` + +```sh +$ appcd exec /titanium/latest/sdk/find/latest +``` + +```sh +$ appcd exec /titanium/latest/sdk/find/9.0.0.GA +``` + ### `/sdk/list/branches` Returns a list of continuous integration branches and which one is the default. diff --git a/conf/config.js b/conf/config.js deleted file mode 100644 index a88ea52..0000000 --- a/conf/config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - /** - * The default Titanium SDK installation path. - * @type {Array.} - */ - searchPaths: [] -}; diff --git a/conf/config.json b/conf/config.json new file mode 100644 index 0000000..a97e7ed --- /dev/null +++ b/conf/config.json @@ -0,0 +1,7 @@ +{ + "home": "~/.axway/titanium", + "searchPaths": [], + "telemetry": { + "app": "de93eb9e-e53c-4f3a-9222-9e0d124950e5" + } +} diff --git a/package.json b/package.json index 89bed6f..3d1f90f 100644 --- a/package.json +++ b/package.json @@ -16,20 +16,35 @@ "test": "gulp test" }, "dependencies": { + "@axway/amplify-request": "^2.1.1", + "@titanium-sdk/node-is-platform-guid": "^1.0.2", + "appc-security": "^0.1.0", + "appcd-fs": "^2.0.4", + "appcd-path": "^2.0.5", + "appcd-util": "^3.1.3", "chalk": "^4.1.0", "cli-kit": "^1.9.3", + "colors": "^1.4.0", "dateformat": "^4.5.0", + "enquirer": "^2.3.6", + "figures": "^3.2.0", "filesize": "^6.1.0", + "form-data": "^3.0.0", "fs-extra": "^9.1.0", "gawk": "^5.0.0", "get-port": "^5.1.1", - "node-pty-prebuilt-multiarch": "^0.9.0", + "global-modules": "^2.0.0", + "node-forge": "^0.10.0", + "open": "^7.3.1", "pluralize": "^8.0.0", - "prompts": "^2.4.0", + "progress": "^2.0.3", + "semver": "^7.3.4", + "simple-plist": "^1.1.1", "sort-object-keys": "^1.1.3", - "source-map-support": "^0.5.19", + "tar": "^6.1.0", "titaniumlib": "^3.0.1", "tmp": "^0.2.1", + "v8-compile-cache": "^2.2.0", "yauzl": "^2.10.0" }, "devDependencies": { @@ -39,8 +54,8 @@ "bugs": "https://github.com/appcelerator/appcd-plugin-titanium/issues", "repository": "https://github.com/appcelerator/appcd-plugin-titanium", "appcd": { - "apiVersion": "1.x || 2.x", - "config": "./conf/config.js", + "apiVersion": "2.x", + "config": "./conf/config.json", "name": "titanium", "type": "external" }, diff --git a/src/cli/auth/login.js b/src/cli/auth/login.js new file mode 100644 index 0000000..60ce388 --- /dev/null +++ b/src/cli/auth/login.js @@ -0,0 +1,89 @@ +import { prompt } from '../../lib/prompt'; + +const { alert, highlight } = appcd.logger.styles; + +/** + * Performs a login. + * + * @param {Object} opts - Various options. + * @param {Array.} opts.argv - The parsed command line arguments. + * @param {Console} opts.console - The console instance to write to. + * @param {Function} opts.setExitCode - A function to set the exit code. + * @param {Terminal} opts.terminal - A cli-kit Terminal instance. + * @returns {Object} The account info. + */ +export async function login({ argv, console, setExitCode, terminal }) { + const data = { + baseUrl: argv.baseUrl, + clientId: argv.clientId, + clientSecret: argv.clientSecret, + env: argv.env, + force: argv.force, + password: argv.password, + secretFile: argv.secretFile, + username: argv.username + }; + + if (argv.username !== undefined) { + const questions = []; + + if (!argv.username || typeof argv.username !== 'string') { + questions.push({ + type: 'input', + name: 'username', + message: 'Username:', + validate(s) { + return !!s || 'Please enter your username'; + } + }); + } + + if (!argv.password || typeof argv.password !== 'string') { + questions.push({ + type: 'password', + name: 'password', + message: 'Password:', + validate(s) { + return !!s || 'Please enter your password'; + } + }); + } + + if (questions.length && argv.json) { + throw new Error('--username and --password are required when --json is set'); + } + + Object.assign(data, await prompt(questions, terminal)); + + if (!argv.json) { + // add a newline after prompting has completed + console.log(); + } + } + + try { + const { response: account } = await appcd.call('/amplify/2.x/auth/login', { data }); + try { + await appcd.call('/module/check-downloads', { data: { accountName: account.name } }); + } catch (err) { + // squelch + } + return account; + } catch (err) { + if (err.code === 'EAUTHENTICATED') { + const { account } = err; + if (argv.json) { + console.log(JSON.stringify(account, null, 2)); + } else { + console.log(`You are already logged into ${highlight(account.org.name)} as ${highlight(account.user.email || account.name)}.`); + } + } else if (err.code === 'ERR_AUTH_FAILED') { + console.error(alert(err.message)); + setExitCode(1); + } else { + throw err; + } + } +} + +export default login; diff --git a/src/cli/cli-service.js b/src/cli/cli-service.js index 75a1b73..72fd891 100644 --- a/src/cli/cli-service.js +++ b/src/cli/cli-service.js @@ -1,13 +1,16 @@ -import CLI, { snooplogg } from 'cli-kit'; +import CLI from 'cli-kit'; import Dispatcher from 'appcd-dispatcher'; import fs from 'fs'; import getPort from 'get-port'; import path from 'path'; -import { get } from 'appcd-util'; +import { isFile } from 'appcd-fs'; +import { loadOptions } from './run-legacy'; import { parseVersion } from '../lib/util'; +// import { Tiapp } from 'titaniumlib'; -const { highlight } = snooplogg.styles; +const { log } = appcd.logger('cli-service'); +const { highlight } = appcd.logger.styles; /** * Defines a service endpoint for defining, processing, and dispatching Titanium CLI commands. @@ -16,11 +19,10 @@ export default class CLIService extends Dispatcher { /** * Registers all of the endpoints. * - * @param {Object} cfg - The Appc Daemon config object. * @returns {Promise} * @access public */ - async activate(cfg) { + async activate() { const pluginVersion = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', '..', 'package.json'))).version; const cli = new CLI({ @@ -31,18 +33,61 @@ export default class CLIService extends Dispatcher { help: true, helpExitCode: 2, name: 'titanium', + options: { + '--no-prompt': 'Disable interactive prompting' + }, + serverMode: true, + styles: { + subheading(s) { + return `\n${String(s).toUpperCase()}`; + } + }, version: ({ data }) => parseVersion(data.userAgent) }); + // inject the titanium config into the command data object before parsing starts so that + // it's available to the command callback + cli.on('parse', ({ data }) => { + data.config = appcd.config.get('titanium'); + data.pluginVersion = pluginVersion; + }); + + // we need to add platform specific options for the build/run help, so first we listen for + // the help command, then we add in the options before the help is generated + cli.on('exec', async ({ cmd, contexts, data }) => { + // we need the help command, the build/run command, and a cwd containing a tiapp + cmd = data?.cwd && cmd?.name === 'help' && contexts[1]; + + if (!cmd || (cmd.name !== 'build' && cmd.name !== 'run')) { + return; + } + + const tiappFile = path.resolve(data.cwd, 'tiapp.xml'); + if (!isFile(tiappFile)) { + return; + } + + cmd.on('generateHelp', async ctx => { + const sdk = '9.0.3.GA'; // data.tiapp.get('sdk-version'); + await loadOptions({ config: data.config, ctx, sdk }); + }); + }); + + // find an available port to listen on const port = await getPort({ - port: get(cfg, 'port', 1733) + port: appcd.config.get('port', 1733) }); + // start the cli-kit server this.server = await cli.listen({ port }); - this.register('/', () => ({ - url: `ws://127.0.0.1:${port}` - })); + // register the discovery endpoint for the cli-kit server + this.register('/', () => { + log(`Returning CLI server URL: ${highlight(`ws://127.0.0.1:${port}`)}`); + return { + url: `ws://127.0.0.1:${port}` + }; + }); this.register('/schema', ({ headers }) => cli.schema({ data: { @@ -52,7 +97,7 @@ export default class CLIService extends Dispatcher { } /** - * Perform any necessary cleanup. + * Stop the CLI server. * * @returns {Promise} * @access public diff --git a/src/cli/commands/add.js b/src/cli/commands/add.js new file mode 100644 index 0000000..fe4378a --- /dev/null +++ b/src/cli/commands/add.js @@ -0,0 +1,19 @@ +import { promptLoop } from '../../lib/prompt'; + +export default { + desc: 'Add a component or service to a project', + options: { + '-d, --project-dir [path]': 'The directory containing the project; defaults to the current directory' + }, + async action(ctx) { + await promptLoop({ + ctx, + data: { + cwd: ctx.data.cwd, + ...ctx.argv + }, + path: '/project/add', + ns: 'cli:add' + }); + } +}; diff --git a/src/cli/commands/build.js b/src/cli/commands/build.js index 48a3e8e..88436fa 100644 --- a/src/cli/commands/build.js +++ b/src/cli/commands/build.js @@ -1,16 +1,15 @@ +import { callback, options, runLegacyCLI } from '../run-legacy'; + export default { + callback, desc: 'Builds a project', - options: { - '-d, --project-dir [path]': 'The directory containing the project; defaults to the current directory', - '-f, --force': 'Force a full rebuild', - '-p, --platform [name]': 'The target build platform' - }, - action({ console }) { - console.log('Building!'); - - // read the tiapp - // load the sdk - // validate - // run the build logic + options: [ + { + ...options, + '-f, --force': 'Force a full rebuild' + } + ], + async action(ctx) { + await runLegacyCLI('build', ctx); } }; diff --git a/src/cli/commands/clean.js b/src/cli/commands/clean.js index 84f4bc4..0942557 100644 --- a/src/cli/commands/clean.js +++ b/src/cli/commands/clean.js @@ -1,15 +1,15 @@ +import { runLegacyCLI } from '../run-legacy'; + export default { desc: 'Remove previous build directories', options: { '-d, --project-dir [path]': 'The directory containing the project; defaults to the current directory', - '-p, --platforms [names]': 'One or more platforms to clean or empty for all' + '-p, --platforms [names]': { + aliases: [ '--platform' ], + desc: 'A comma separated list of platforms; defaults to all platforms' + } }, - action({ console }) { - // read the tiapp - // load the sdk - // validate the platform names - // run the clean logic - - console.log('Cleaning!'); + async action(ctx) { + await runLegacyCLI('clean', ctx); } }; diff --git a/src/cli/commands/config.js b/src/cli/commands/config.js index 81345e2..a4e3a03 100644 --- a/src/cli/commands/config.js +++ b/src/cli/commands/config.js @@ -1,73 +1,55 @@ -const readActions = { - get: 'get', - ls: 'get', - list: 'get' -}; - -const writeActions = { - set: 'set', - - delete: 'delete', - rm: 'delete', - unset: 'delete', - - push: 'push', - pop: 'pop', - shift: 'shift', - unshift: 'unshift' -}; - export default { - aliases: 'conf', - args: [ - { - name: '', - desc: 'The action to run', - values: { - 'ls, list': 'Display all settings', - get: 'Display a specific setting', - set: 'Change a setting', - 'rm, delete': 'Remove a setting', - push: 'Add a value to the end of a list', - pop: 'Remove the last value in a list' - } + aliases: '!conf', + banner: false, + commands: { + '@ls, list': { + desc: 'Display all config settings', + action: ctx => runConfig('get', ctx) + }, + 'get [key]': { + desc: 'Display a specific config setting', + action: ctx => runConfig('get', ctx) + }, + 'set ': { + desc: 'Change a config setting', + action: ctx => runConfig('set', ctx) }, - { name: 'key', desc: '' }, - { name: 'value', desc: '' } - ], + '@rm, delete, !remove, !unset ': { + desc: 'Remove a config setting', + action: ctx => runConfig('delete', ctx) + }, + 'push ': { + desc: 'Add a value to the end of a list', + action: ctx => runConfig('push', ctx) + }, + 'pop ': { + desc: 'Remove the last value in a list', + action: ctx => runConfig('pop', ctx) + }, + 'shift ': { + desc: 'Remove the first value in a list', + action: ctx => runConfig('shift', ctx) + }, + 'unshift ': { + desc: 'Add a value ot the beginning of a list', + action: ctx => runConfig('unshift', ctx) + } + }, desc: 'Manage configuration options', options: { '--json': 'Outputs the config as JSON' - }, - async action({ argv, console }) { - let { action, key, value } = argv; - - if (!readActions[action] && !writeActions[action]) { - throw new Error(`Unknown action: ${action}`); - } + } +}; - let { response } = await appcd.call('/appcd/config', { - data: { - action: readActions[action] || writeActions[action] || action, - key: /^titanium\./.test(key) ? key : key ? `titanium.${key}` : 'titanium', - value - } - }); +async function runConfig(action, { argv, console, setExitCode }) { + let { json, key, value } = argv; - let result = 'Saved'; - if (response !== 'OK') { - result = response; - } else if (argv.json) { - // if a pop() or shift() returns OK, then that means there's no more items and - // thus we have to force undefined - if (/^pop|shift$/.test(action)) { - result = ''; - } - } + const print = ({ code = 0, key = null, value }) => { + setExitCode(code); - if (argv.json) { - console.log(JSON.stringify({ code: 0, result }, null, 2)); - } else if (result && typeof result === 'object') { + if (json) { + console.log(JSON.stringify(value, null, 2)); + } else if (value && typeof value === 'object') { let width = 0; const rows = []; @@ -90,7 +72,7 @@ export default { } segments.pop(); } - }(result, key ? key.split('.') : [])); + }(value, key ? key.split('.') : [])); if (rows.length) { for (const row of rows) { @@ -100,7 +82,25 @@ export default { console.log('No config settings found'); } } else { - console.log(result); + console.log(value); } + }; + + try { + const { response } = await appcd.call('/appcd/config', { + data: { + action, + key: /^titanium\./.test(key) ? key : key ? `titanium.${key}` : 'titanium', + value + } + }); + + print({ key, value: response }); + } catch (err) { + if (err.status === 404) { + return print({ code: 6, key }); + } + err.json = json; + throw err; } -}; +} diff --git a/src/cli/commands/login.js b/src/cli/commands/login.js index 297b55d..429b01c 100644 --- a/src/cli/commands/login.js +++ b/src/cli/commands/login.js @@ -1,3 +1,7 @@ +import { login } from '../auth/login'; + +const { highlight } = appcd.logger.styles; + export default { desc: 'Log in to the Axway AMPLIFY platform', options: { @@ -6,95 +10,32 @@ export default { '--env [name]': 'The environment to use', '--realm [realm]': { hidden: true }, '--force': 'Re-authenticate even if the account is already authenticated', - '--json': 'Outputs accounts as JSON', + '--json': { + callback({ ctx, value }) { + if (value) { + while (ctx.parent) { + ctx = ctx.parent; + } + ctx.banner = false; + } + }, + desc: 'Outputs accounts as JSON' + }, '-c, --client-secret [key]': 'A secret key used to authenticate', '-s, --secret-file [path]': 'Path to the PEM key used to authenticate', '-u, --username [user]': 'Username to authenticate with', '-p, --password [pass]': 'Password to authenticate with' }, - async action({ argv, console, exitCode, terminal }) { - const [ - { snooplogg }, - { prompt } - ] = await Promise.all([ - import('appcd-logger'), - import('prompts') - ]); - const { alert, highlight } = snooplogg.styles; - const data = { - baseUrl: argv.baseUrl, - clientId: argv.clientId, - clientSecret: argv.clientSecret, - env: argv.env, - force: argv.force, - password: argv.password, - secretFile: argv.secretFile, - username: argv.username - }; - - if (Object.prototype.hasOwnProperty.call(argv, 'username')) { - const questions = []; - - if (!argv.username || typeof argv.username !== 'string') { - questions.push({ - type: 'text', - name: 'username', - message: 'Username:', - stdin: terminal.stdin, - stdout: terminal.stdout, - validate(s) { - return !!s || 'Please enter your username'; - } - }); - } - - if (!argv.password || typeof argv.password !== 'string') { - questions.push({ - type: 'password', - name: 'password', - message: 'Password:', - stdin: terminal.stdin, - stdout: terminal.stdout, - validate(s) { - return !!s || 'Please enter your password'; - } - }); - } - - if (questions.length && argv.json) { - throw new Error('--username and --password are required when --json is set'); - } - - Object.assign(data, await prompt(questions)); - - if (!argv.json) { - // add a newline after prompting has completed - console.log(); - } - } - - try { - const { response: account } = await appcd.call('/amplify/1.x/auth/login', { data }); + async action(params) { + const { argv, console } = params; + const account = await login(params); + if (account) { if (argv.json) { console.log(JSON.stringify(account, null, 2)); } else { console.log(`You are logged into ${highlight(account.org.name)} as ${highlight(account.user.email || account.name)}.`); } - } catch (err) { - if (err.code === 'EAUTHENTICATED') { - const { account } = err; - if (argv.json) { - console.log(JSON.stringify(account, null, 2)); - } else { - console.log(`You are already logged into ${highlight(account.org.name)} as ${highlight(account.user.email || account.name)}.`); - } - } else if (err.code === 'ERR_AUTH_FAILED') { - console.error(alert(err.message)); - exitCode(1); - } else { - throw err; - } } } }; diff --git a/src/cli/commands/logout.js b/src/cli/commands/logout.js index c62cbf7..08c93cb 100644 --- a/src/cli/commands/logout.js +++ b/src/cli/commands/logout.js @@ -1,5 +1,5 @@ export default { - aliases: [ 'revoke' ], + aliases: '!revoke', args: [ { name: 'account', @@ -11,7 +11,7 @@ export default { '--json': 'Outputs revoked accounts as JSON' }, async action({ argv, console }) { - const { response: revoked } = await appcd.call('/amplify/1.x/auth/logout', { + const { response: revoked } = await appcd.call('/amplify/2.x/auth/logout', { data: { accountName: argv.account } @@ -24,7 +24,7 @@ export default { // pretty output if (revoked.length) { - const { highlight } = require('appcd-logger').snooplogg.styles; + const { highlight } = appcd.logger.styles; console.log('Revoked authenticated accounts:'); for (const account of revoked) { console.log(` ${highlight(account.name)}`); diff --git a/src/cli/commands/new.js b/src/cli/commands/new.js index db0aa2e..c5a3ed6 100644 --- a/src/cli/commands/new.js +++ b/src/cli/commands/new.js @@ -1,19 +1,47 @@ +import { promptLoop } from '../../lib/prompt'; + export default { aliases: '!create', desc: 'Create a new project', options: { - '-d, --workspace-dir': 'The directory to place the project in', - '-f, --force': 'Force project creation even if path already exists', - // '--id ' - // '--url ' - // '--name ' - '--template ': '?', - '-t, --type ': { - default: 'app', - desc: 'The type of project to create' - } + '-d, --workspace-dir [path]': 'The directory to place the project in', + '-f, --force': 'Force project creation even if path already exists', + '--id [id]': 'A project ID in the format \'com.companyname.appname\'', + '-n, --name [name]': 'The name of the project', + '--template [name]': 'The name of a project template, path to a local dir/zip, url, git repo, or npm package' }, - action({ console }) { - console.log('Hi from new'); + async action(ctx) { + const { blue, bold, cyan, green, note } = appcd.logger.styles; + + await promptLoop({ + ctx, + data: { + cwd: ctx.data.cwd, + force: ctx.argv.force, + id: ctx.argv.id, + name: ctx.argv.name, + template: ctx.argv.template, + workspaceDir: ctx.argv.workspaceDir + }, + header: `${bold(blue('Welcome! Let\'s create a new Titanium project!'))} +First, we need to ask you a few questions about your project: +`, + footer: proj => proj && ` +${green('Success!')} + +Next steps: + + ${cyan(`cd ${proj.name}`)} + ${cyan('ti run -p android')} + ${note(' or ')} + ${cyan('ti run -p ios')} + +For help, visit: https://docs.axway.com/category/appdev +` || ` +Failed to create project! +`, + path: '/project/new', + ns: 'cli:new' + }); } }; diff --git a/src/cli/commands/project.js b/src/cli/commands/project.js index 520b356..5034cf7 100644 --- a/src/cli/commands/project.js +++ b/src/cli/commands/project.js @@ -1,9 +1,19 @@ +import { promptLoop } from '../../lib/prompt'; + export default { desc: 'Manage project settings', options: { '-d, --project-dir [path]': 'The directory containing the project; defaults to the current directory' }, - action({ console }) { - console.log('Project info!'); + async action(ctx) { + await promptLoop({ + ctx, + data: { + cwd: ctx.data.cwd, + projectDir: ctx.argv.projectDir + }, + path: '/project', + ns: 'cli:project' + }); } }; diff --git a/src/cli/commands/register.js b/src/cli/commands/register.js new file mode 100644 index 0000000..6ef0aae --- /dev/null +++ b/src/cli/commands/register.js @@ -0,0 +1,23 @@ +import { promptLoop } from '../../lib/prompt'; + +export default { + desc: 'Registers an existing app with the Axway platform', + options: { + '-d, --project-dir [path]': 'The directory containing the project; defaults to the current directory', + '--force': 'Forces an app to be re-registered', + '--org [guid|id|name]': 'The organization to register the app with' + }, + async action(ctx) { + await promptLoop({ + ctx, + data: { + cwd: ctx.data.cwd, + force: ctx.argv.force, + org: ctx.argv.org, + projectDir: ctx.argv.projectDir + }, + path: '/project/register', + ns: 'cli:register' + }); + } +}; diff --git a/src/cli/commands/run.js b/src/cli/commands/run.js index 2d04e3a..00fac5f 100644 --- a/src/cli/commands/run.js +++ b/src/cli/commands/run.js @@ -1,18 +1,16 @@ +import { callback, options, runLegacyCLI } from '../run-legacy'; + export default { + callback, desc: 'Build and runs a project', - options: { - '--build-only': 'Builds the project without running it in the simulator/emulator or installing it on device', - '-d, --project-dir [path]': 'The directory containing the project; defaults to the current directory', - '-f, --force': 'Force a full rebuild', - '-p, --platform [name]': 'The target build platform' - }, - action({ console }) { - console.log('Building and running!'); - - // read the tiapp - // load the sdk - // validate - // run the build logic - // run the app + options: [ + { + ...options, + '--build-only': 'Builds the project without running it in the simulator/emulator or installing it on device', + '-f, --force': 'Force a full rebuild' + } + ], + async action(ctx) { + await runLegacyCLI('run', ctx); } }; diff --git a/src/cli/commands/sdk.js b/src/cli/commands/sdk.js index a9dae6c..a759407 100644 --- a/src/cli/commands/sdk.js +++ b/src/cli/commands/sdk.js @@ -4,12 +4,12 @@ import select from '../sdk/select'; import uninstall from '../sdk/uninstall'; export default { + action: list.action, commands: { install, list, select, uninstall }, - defaultCommand: 'list', desc: 'Manage Titanium SDKs.' }; diff --git a/src/cli/commands/switch.js b/src/cli/commands/switch.js index 7bf8d65..a446b54 100644 --- a/src/cli/commands/switch.js +++ b/src/cli/commands/switch.js @@ -1,3 +1,6 @@ +import { login } from '../auth/login'; +import { prompt } from '../../lib/prompt'; + export default { desc: 'Select default account and organization', options: { @@ -5,19 +8,87 @@ export default { '--json': 'Outputs accounts as JSON', '--org [guid|id|name]': 'The organization to switch to' }, - async action({ argv, console }) { - const { response: account } = await appcd.call('/amplify/1.x/auth/switch', { - data: { - accountName: argv.account, - org: argv.org + async action(params) { + const { argv, console, terminal } = params; + const { response: accounts } = await appcd.call('/amplify/2.x/auth'); + let account; + let { org } = argv; + let loggedIn = false; + let previousOrg; + + if (accounts.length) { + account = argv.account + && accounts.find(a => a.name === argv.account) + || accounts.find(a => a.active) + || accounts[0]; + } + + if (!account) { + account = await login(params); + loggedIn = true; + } + + if (!account.orgs?.length) { + console.log(`Account ${account.name} does not have any orgs.`); + return; + } + + previousOrg = account?.org.guid; + + if (account.orgs.length === 1) { + account = await appcd.call('/amplify/2.x/switch', { + accountName: account.name, + org: account.orgs[0].guid + }); + } else if (!org && argv.json) { + throw new Error('--org is required when --json is set'); + } else { + if (!org) { + let initial; + const orgs = account.orgs.sort((a, b) => a.name.localeCompare(b.name)); + + ({ org } = await prompt({ + choices: orgs + .map((org, i) => { + if (org.guid === account.org.guid) { + initial = i; + } + return { + name: org.name, + message: org.name, + value: org.guid + }; + }) + .sort((a, b) => a.message.localeCompare(b.message)), + initial, + message: 'Select an organization to switch to', + name: 'org', + type: 'select' + }, terminal)); + + console.log(); } - }); + + account = (await appcd.call('/amplify/2.x/auth/switch', { + data: { + accountName: account.name, + org + } + })).response; + } + + try { + await appcd.call('/module/check-downloads', { data: { accountName: account.name } }); + } catch (err) { + // squelch + } if (argv.json) { console.log(JSON.stringify(account, null, 2)); } else { - const { highlight } = require('appcd-logger').snooplogg.styles; - console.log(`You are logged into ${highlight(account.org.name)} as ${highlight(account.user.email || account.name)}.`); + const { highlight } = appcd.logger.styles; + const msg = loggedIn ? 'are logged into' : previousOrg !== account.org.guid ? 'have switched to' : 'are already switched to'; + console.log(`You ${msg} ${highlight(account.org.name)} as ${highlight(account.user.email || account.name)}.`); } } }; diff --git a/src/cli/commands/whoami.js b/src/cli/commands/whoami.js index 7d34833..d0b597c 100644 --- a/src/cli/commands/whoami.js +++ b/src/cli/commands/whoami.js @@ -4,7 +4,7 @@ export default { '--json': 'Outputs accounts as JSON' }, async action({ argv, console }) { - let { response: accounts } = await appcd.call('/amplify/1.x/auth'); + const { response: accounts } = await appcd.call('/amplify/2.x/auth'); if (!accounts.length) { console.log('No authenticated accounts.'); @@ -16,7 +16,7 @@ export default { if (argv.json) { console.log(JSON.stringify(account, null, 2)); } else { - const { highlight } = require('appcd-logger').snooplogg.styles; + const { highlight } = appcd.logger.styles; console.log(`You are logged into ${highlight(account.org.name)} as ${highlight(account.user.email || account.name)}.`); } } diff --git a/src/cli/info/android.js b/src/cli/info/android.js index 535a6b9..172f083 100644 --- a/src/cli/info/android.js +++ b/src/cli/info/android.js @@ -3,26 +3,26 @@ export default { return (await appcd.call('/android/2.x/info')).response; }, render(console, info) { - const { bold, cyan, gray, magenta } = require('chalk'); + const { cyan, gray, green, magenta } = require('chalk'); - console.log(bold('Android SDKs')); + console.log(magenta('Android SDKs'.toUpperCase())); if (info.sdks.length) { for (const sdk of info.sdks) { - console.log(` ${cyan(sdk.path)}${sdk.default ? gray(' (default)') : ''}`); - console.log(` ADB Executable = ${magenta(sdk.platformTools.executables.adb || 'not installed')}`); - console.log(` Build Tools = ${magenta(sdk.buildTools.length ? sdk.buildTools.map(bt => bt.version).filter(v => v).join(', ') : 'not installed')}`); - console.log(` Platform Tools = ${magenta(sdk.platformTools.version || 'n/a')}`); - console.log(` Tools = ${magenta(sdk.tools.version || 'n/a')}`); + console.log(` ${green(sdk.path)}${sdk.default ? gray(' (default)') : ''}`); + console.log(` ADB Executable = ${cyan(sdk.platformTools.executables.adb || 'not installed')}`); + console.log(` Build Tools = ${cyan(sdk.buildTools.length ? sdk.buildTools.map(bt => bt.version).filter(v => v).join(', ') : 'not installed')}`); + console.log(` Platform Tools = ${cyan(sdk.platformTools.version || 'n/a')}`); + console.log(` Tools = ${cyan(sdk.tools.version || 'n/a')}`); console.log(' Platforms:'); if (sdk.platforms.length) { for (const platform of sdk.platforms) { - console.log(` ${cyan(platform.sdk)}`); - console.log(` Name = ${magenta(platform.name)}`); - console.log(` API Level = ${magenta(platform.apiLevel)}`); - console.log(` Revision = ${magenta(platform.revision || '?')}`); - console.log(` Path = ${magenta(platform.path)}`); - console.log(` Skins = ${magenta(platform.skins.join(', '))}`); - console.log(` Architectures = ${magenta(Object.keys(platform.abis).map(name => `${name}: ${platform.abis[name].join(', ')}`))}`); + console.log(` ${green(platform.sdk)}`); + console.log(` Name = ${cyan(platform.name)}`); + console.log(` API Level = ${cyan(platform.apiLevel)}`); + console.log(` Revision = ${cyan(platform.revision || '?')}`); + console.log(` Path = ${cyan(platform.path)}`); + console.log(` Skins = ${cyan(platform.skins.join(', '))}`); + console.log(` Architectures = ${cyan(Object.keys(platform.abis).map(name => `${name}: ${platform.abis[name].join(', ')}`))}`); } } else { console.log(gray(' None')); @@ -30,16 +30,16 @@ export default { console.log(' Addons:'); if (sdk.addons.length) { for (const addon of sdk.addons) { - console.log(` ${cyan(addon.name)}`); - console.log(` Name = ${magenta(addon.name)}${addon.codename ? gray(` (${addon.codename})`) : ''}`); - console.log(` API Level = ${magenta(addon.apiLevel)}`); - console.log(` Revision = ${magenta(addon.revision || '?')}`); - console.log(` Based On = ${magenta(addon.basedOn ? `Android ${addon.basedOn.version}` : 'n/a')}`); - console.log(` Path = ${magenta(addon.path)}`); - console.log(` Vendor = ${magenta(addon.vendor)}`); - console.log(` Description = ${magenta(addon.description || 'n/a')}`); - console.log(` Skins = ${magenta(addon.skins && addon.skins.join(', ') || 'none')}`); - console.log(` Architectures = ${magenta(addon.abis && Object.keys(addon.abis).map(name => `${name}: ${addon.abis[name].join(', ')}`)) || 'none'}`); + console.log(` ${green(addon.name)}`); + console.log(` Name = ${cyan(addon.name)}${addon.codename ? gray(` (${addon.codename})`) : ''}`); + console.log(` API Level = ${cyan(addon.apiLevel)}`); + console.log(` Revision = ${cyan(addon.revision || '?')}`); + console.log(` Based On = ${cyan(addon.basedOn ? `Android ${addon.basedOn.version}` : 'n/a')}`); + console.log(` Path = ${cyan(addon.path)}`); + console.log(` Vendor = ${cyan(addon.vendor)}`); + console.log(` Description = ${cyan(addon.description || 'n/a')}`); + console.log(` Skins = ${cyan(addon.skins && addon.skins.join(', ') || 'none')}`); + console.log(` Architectures = ${cyan(addon.abis && Object.keys(addon.abis).map(name => `${name}: ${addon.abis[name].join(', ')}`)) || 'none'}`); } } else { console.log(gray(' None')); @@ -50,43 +50,43 @@ export default { } console.log(); - console.log(bold('Android NDKs')); + console.log(magenta('Android NDKs'.toUpperCase())); if (info.ndks.length) { for (const ndk of info.ndks) { - console.log(` ${cyan(ndk.path)}${ndk.default ? gray(' (default)') : ''}`); - console.log(` Name = ${magenta(ndk.name)}`); - console.log(` Version = ${magenta(ndk.version)}`); - console.log(` Architecture = ${magenta(ndk.arch)}`); - console.log(` Path = ${magenta(ndk.path)}`); + console.log(` ${green(ndk.path)}${ndk.default ? gray(' (default)') : ''}`); + console.log(` Name = ${cyan(ndk.name)}`); + console.log(` Version = ${cyan(ndk.version)}`); + console.log(` Architecture = ${cyan(ndk.arch)}`); + console.log(` Path = ${cyan(ndk.path)}`); } } else { console.log(gray(' None')); } console.log(); - console.log(bold('Android Emulators')); + console.log(magenta('Android Emulators'.toUpperCase())); if (info.emulators.length) { for (const emu of info.emulators) { - console.log(` ${cyan(emu.name)}${emu.type === 'avd' ? gray(' (AVD)') : ''}`); - console.log(` ID = ${magenta(emu.id)}`); - console.log(` Version = ${magenta(emu.target || '?')}`); - console.log(` Architecture = ${magenta(emu.abi)}`); - console.log(` Path = ${magenta(emu.path)}`); - console.log(` Google APIs = ${magenta(emu.googleApis === null ? 'Unknown' : emu.googleApis ? 'Yes' : 'No')}`); + console.log(` ${green(emu.name)}${emu.type === 'avd' ? gray(' (AVD)') : ''}`); + console.log(` ID = ${cyan(emu.id)}`); + console.log(` Version = ${cyan(emu.target || '?')}`); + console.log(` Architecture = ${cyan(emu.abi)}`); + console.log(` Path = ${cyan(emu.path)}`); + console.log(` Google APIs = ${cyan(emu.googleApis === null ? 'Unknown' : emu.googleApis ? 'Yes' : 'No')}`); } } else { console.log(gray(' None')); } console.log(); - console.log(bold('Android Devices')); + console.log(magenta('Android Devices'.toUpperCase())); if (info.devices.length) { for (const device of info.devices) { - console.log(` ${cyan(device.name)}${device.model ? gray(` (${device.model})`) : ''}`); - console.log(` ID = ${magenta(device.id)}`); - console.log(` State = ${magenta(device.state)}`); - console.log(` SDK Version = ${magenta(`${device.release || '?'}${device.sdk ? ` (android-${device.sdk})` : ''}`)}`); - console.log(` Architectures = ${magenta(Array.isArray(device.abi) ? device.abi.sort().join(', ') : '?')}`); + console.log(` ${green(device.name)}${device.model ? gray(` (${device.model})`) : ''}`); + console.log(` ID = ${cyan(device.id)}`); + console.log(` State = ${cyan(device.state)}`); + console.log(` SDK Version = ${cyan(`${device.release || '?'}${device.sdk ? ` (android-${device.sdk})` : ''}`)}`); + console.log(` Architectures = ${cyan(Array.isArray(device.abi) ? device.abi.sort().join(', ') : '?')}`); } } else { console.log(gray(' None')); diff --git a/src/cli/info/ios.js b/src/cli/info/ios.js index 73ecea5..65004d3 100644 --- a/src/cli/info/ios.js +++ b/src/cli/info/ios.js @@ -3,16 +3,16 @@ export default { render(console, info) { const dateformat = require('dateformat'); const pluralize = require('pluralize'); - const { bold, cyan, gray, magenta } = require('chalk'); + const { cyan, gray, green, magenta } = require('chalk'); - console.log(bold('Xcode')); + console.log(magenta('Xcode'.toUpperCase())); if (info.xcode) { for (const xcode of Object.values(info.xcode)) { - console.log(` ${cyan(xcode.version)} (build ${xcode.build})${xcode.default ? gray(' (default)') : ''}}`); - console.log(` App Path = ${magenta(xcode.xcodeapp)}`); - console.log(` iOS SDKs = ${magenta(xcode.sdks.ios.join(', '))}`); - console.log(` watchOS SDKs = ${magenta(xcode.sdks.watchos.join(', '))}`); - console.log(` EULA Accepted = ${magenta(xcode.eulaAccepted ? 'Yes' : 'No')}`); + console.log(` ${green(`${xcode.version} (build ${xcode.build})`)}${xcode.default ? gray(' (default)') : ''}`); + console.log(` App Path = ${cyan(xcode.xcodeapp)}`); + console.log(` iOS SDKs = ${cyan(xcode.sdks.ios.join(', '))}`); + console.log(` watchOS SDKs = ${cyan(xcode.sdks.watchos.join(', '))}`); + console.log(` EULA Accepted = ${cyan(xcode.eulaAccepted ? 'Yes' : 'No')}`); } } else { console.log(gray(' Not installed')); @@ -20,29 +20,36 @@ export default { console.log(); const pc = (title, certs) => { - console.log(` ${cyan(title)}`); + console.log(` ${green(title)}`); + const total = certs.length; certs = certs.filter(c => !c.invalid); if (certs.length) { for (const cert of certs) { console.log(` ${cert.name}`); - console.log(` Not valid before = ${magenta(cert.before ? dateformat(cert.before, 'm/d/yyyy h:MM TT') : 'unknown')}`); + console.log(` Not valid before = ${cyan(cert.before ? dateformat(cert.before, 'm/d/yyyy h:MM TT') : 'unknown')}`); if (cert.after) { const days = Math.floor((new Date(cert.after) - new Date()) / 1000 / 60 / 60 / 24); - console.log(` Not valid after = ${magenta(dateformat(cert.after, 'm/d/yyyy h:MM TT'))} ${gray(`(expires in ${pluralize('day', days, true)})`)}`); + console.log(` Not valid after = ${cyan(dateformat(cert.after, 'm/d/yyyy h:MM TT'))} ${gray(`(expires in ${pluralize('day', days, true)})`)}`); } else { - console.log(` Not valid after = ${magenta('unknown')}`); + console.log(` Not valid after = ${cyan('unknown')}`); } } + const delta = total - certs.length; + if (delta) { + console.log(gray(` (${delta} additional expired cert${delta === 1 ? '' : 's'})`)); + } + } else if (total) { + console.log(gray(` None (${total} expired cert${total === 1 ? '' : 's'})`)); } else { console.log(gray(' None')); } }; - console.log(bold('Certificates')); - console.log(cyan(' Apple WWDR Cert')); + console.log(magenta('Certificates'.toUpperCase())); + console.log(green(' Apple WWDR Cert')); if (info.certs.wwdr) { console.log(' Installed'); } else { - console.log(` Not installed, visit ${magenta('https://developer.apple.com/support/certificates/expiration/')}`); + console.log(` Not installed, visit ${cyan('https://developer.apple.com/support/certificates/expiration/')}`); } pc('Development', info.certs.developer); @@ -50,60 +57,68 @@ export default { console.log(); const pp = (title, profiles) => { - console.log(` ${cyan(title)}`); + console.log(` ${green(title)}`); + const total = profiles.length; profiles = profiles.filter(p => !p.expired && !p.managed); if (profiles.length) { for (const p of profiles) { console.log(` ${p.name}`); - console.log(` UUID = ${magenta(p.uuid)}`); - console.log(` App ID = ${magenta(p.entitlements['application-identifier'] || '?')}`); - console.log(` Date Created = ${magenta(p.creationDate ? dateformat(p.creationDate, 'm/d/yyyy h:MM TT') : 'unknown')}`); + console.log(` UUID = ${cyan(p.uuid)}`); + console.log(` App ID = ${cyan(p.entitlements['application-identifier'] || '?')}`); + console.log(` Date Created = ${cyan(p.creationDate ? dateformat(p.creationDate, 'm/d/yyyy h:MM TT') : 'unknown')}`); if (p.expirationDate) { const days = Math.floor((new Date(p.expirationDate) - new Date()) / 1000 / 60 / 60 / 24); - console.log(` Date Expires = ${magenta(dateformat(p.expirationDate, 'm/d/yyyy h:MM TT'))} ${gray(`(expires in ${pluralize('day', days, true)})`)}`); + console.log(` Date Expires = ${cyan(dateformat(p.expirationDate, 'm/d/yyyy h:MM TT'))} ${gray(`(expires in ${pluralize('day', days, true)})`)}`); } else { - console.log(` Date Expires = ${magenta('unknown')}`); + console.log(` Date Expires = ${cyan('unknown')}`); } } + const delta = total - profiles.length; + if (delta) { + console.log(gray(` (${delta} additional expired or unsupported profile${delta === 1 ? '' : 's'})`)); + } + } else if (total) { + console.log(gray(` None (${total} expired or unsupported profile${total === 1 ? '' : 's'})`)); } else { console.log(gray(' None')); } }; - console.log(bold('Provisioning Profiles')); + console.log(magenta('Provisioning Profiles'.toUpperCase())); pp('Development', info.provisioning.development); pp('App Store Distribution', info.provisioning.distribution); pp('Ad Hoc Distribution', info.provisioning.adhoc); pp('Enterprice Ad Hoc Distribution', info.provisioning.enterprise); console.log(); + const star = process.platform === 'win32' ? '*' : '★'; const ps = (title, data) => { const vers = Object.keys(data); if (vers.length) { for (const ver of vers) { - console.log(cyan(` ${title} ${ver}`)); + console.log(green(` ${title} ${ver}`)); const sims = data[ver]; for (const sim of sims) { const supportsWatch = sim.supportsWatch && Object.values(sim.supportsWatch).filter(x => x).length; - console.log(` ${supportsWatch ? gray('*') : ' '}${sim.name.substring(0, 36).padEnd(36)} = ${magenta(sim.udid)}`); + console.log(` ${supportsWatch ? gray(star) : ' '} ${sim.name.substring(0, 36).padEnd(36)} = ${cyan(sim.udid)}`); } } } else { console.log(gray(' None')); } }; - console.log(`${bold('Simulators')} ${gray('(*supports watch sim pairing)')}`); + console.log(`${magenta('Simulators'.toUpperCase())} ${gray(`(${star} supports watch sim pairing)`)}`); ps('iOS', info.simulators.ios); ps('watchOS', info.simulators.watchos); console.log(); - console.log(bold('iOS Devices')); + console.log(magenta('iOS Devices'.toUpperCase())); if (info.devices.length) { for (const device of info.devices) { - console.log(` ${cyan(device.name)}`); - console.log(` UDID = ${magenta(device.udid)}`); - console.log(` Type = ${magenta(`${device.deviceClass} (${device.deviceColor})`)}`); - console.log(` iOS Version = ${magenta(device.productVersion)}`); - console.log(` CPU Architecture = ${magenta(device.cpuArchitecture)}`); + console.log(` ${green(device.name)}`); + console.log(` UDID = ${cyan(device.udid)}`); + console.log(` Type = ${cyan(`${device.deviceClass} (${device.deviceColor})`)}`); + console.log(` iOS Version = ${cyan(device.productVersion)}`); + console.log(` CPU Architecture = ${cyan(device.cpuArchitecture)}`); } } else { console.log(gray(' None')); diff --git a/src/cli/info/jdk.js b/src/cli/info/jdk.js index 333b6e8..31aabec 100644 --- a/src/cli/info/jdk.js +++ b/src/cli/info/jdk.js @@ -3,14 +3,14 @@ export default { return (await appcd.call('/jdk/1.x/info')).response; }, render(console, info) { - const { bold, cyan, gray, magenta } = require('chalk'); + const { cyan, gray, green, magenta } = require('chalk'); - console.log(bold('Java Development Kit')); + console.log(magenta('Java Development Kit'.toUpperCase())); if (info.length) { for (const jdk of info) { - console.log(` ${cyan(`${jdk.version}:${jdk.build}`)}${jdk.default ? gray(' (default)') : ''}`); - console.log(` Architecture = ${magenta(jdk.arch)}`); - console.log(` Path = ${magenta(jdk.path)}`); + console.log(` ${green(`${jdk.version}:${jdk.build}`)}${jdk.default ? gray(' (default)') : ''}`); + console.log(` Architecture = ${cyan(jdk.arch)}`); + console.log(` Path = ${cyan(jdk.path)}`); } } else { console.log(gray(' Not installed')); diff --git a/src/cli/info/os.js b/src/cli/info/os.js index 0254409..1839e3d 100644 --- a/src/cli/info/os.js +++ b/src/cli/info/os.js @@ -1,88 +1,20 @@ -import fs from 'fs'; -import os from 'os'; - -import { arch } from 'appcd-util'; -import { isFile } from 'appcd-fs'; -import { run } from 'appcd-subprocess'; +import getOSInfo from '../../lib/os'; export default { - async fetch() { - const { platform } = process; - const info = { - platform, - name: 'Unknown', - version: '', - arch: arch(), - numcpus: os.cpus().length, - memory: os.totalmem() - }; - - switch (platform) { - case 'darwin': - { - const { stdout } = await run('sw_vers'); - let m = stdout.match(/ProductName:\s+(.+)/i); - if (m) { - info.name = m[1]; - } - m = stdout.match(/ProductVersion:\s+(.+)/i); - if (m) { - info.version = m[1]; - } - } - break; - - case 'linux': - info.name = 'GNU/Linux'; - - if (isFile('/etc/lsb-release')) { - const contents = fs.readFileSync('/etc/lsb-release', 'utf8'); - let m = contents.match(/DISTRIB_DESCRIPTION=(.+)/i); - if (m) { - info.name = m[1].replace(/"/g, ''); - } - m = contents.match(/DISTRIB_RELEASE=(.+)/i); - if (m) { - info.version = m[1].replace(/"/g, ''); - } - } else if (isFile('/etc/system-release')) { - const parts = fs.readFileSync('/etc/system-release', 'utf8').split(' '); - if (parts[0]) { - info.name = parts[0]; - } - if (parts[2]) { - info.version = parts[2]; - } - } - break; - - case 'win32': - { - const { stdout } = await run('wmic', [ 'os', 'get', 'Caption,Version' ]); - const s = stdout.split('\n')[1].split(/ {2,}/); - if (s.length > 0) { - info.name = s[0].trim() || 'Windows'; - } - if (s.length > 1) { - info.version = s[1].trim() || ''; - } - } - break; - } - - return info; + fetch() { + return getOSInfo(); }, render(console, info) { const filesize = require('filesize'); - const { bold, magenta } = require('chalk'); - - console.log(bold('Operating System')); - console.log(` Name = ${magenta(info.name)}`); - console.log(` Version = ${magenta(info.version)}`); - console.log(` Architecture = ${magenta(info.arch === 'x64' ? '64-bit' : '32-bit')}`); - console.log(` # CPUs = ${magenta(info.numcpus)}`); - console.log(` Memory = ${magenta(filesize(info.memory))}`); + const { cyan, magenta } = require('chalk'); + + console.log(magenta('Operating System'.toUpperCase())); + console.log(` Name = ${cyan(info.name)}`); + console.log(` Version = ${cyan(info.version)}`); + console.log(` Architecture = ${cyan(info.arch === 'x64' ? '64-bit' : '32-bit')}`); + console.log(` # CPUs = ${cyan(info.numcpus)}`); + console.log(` Memory = ${cyan(filesize(info.memory))}`); console.log(); } }; diff --git a/src/cli/info/titanium.js b/src/cli/info/titanium.js index 9e71570..30871de 100644 --- a/src/cli/info/titanium.js +++ b/src/cli/info/titanium.js @@ -13,21 +13,21 @@ export default { }; }, render(console, info) { - const { bold, cyan, gray, magenta } = require('chalk'); + const { cyan, gray, green, magenta } = require('chalk'); - console.log(bold('Titanium CLI')); - console.log(` CLI Version = ${magenta(info.cli.version)}`); - console.log(` Plugin Version = ${magenta(info.plugin.version)}`); + console.log(magenta('Titanium CLI'.toUpperCase())); + console.log(` CLI Version = ${cyan(info.cli.version)}`); + console.log(` Plugin Version = ${cyan(info.plugin.version)}`); console.log(); - console.log(bold('Titanium SDK')); + console.log(magenta('Titanium SDKs'.toUpperCase())); if (info.sdks.length) { for (const sdk of info.sdks) { - console.log(` ${cyan(sdk.name)}`); - console.log(` Version = ${magenta(sdk.manifest.version)}`); - console.log(` Install Location = ${magenta(sdk.path)}`); - console.log(` Platforms = ${magenta(sdk.manifest.platforms.sort().join(', '))}`); - console.log(` git Hash = ${magenta(sdk.manifest.githash || 'unknown')}`); + console.log(` ${green(sdk.name)}`); + console.log(` Version = ${cyan(sdk.manifest.version)}`); + console.log(` Install Location = ${cyan(sdk.path)}`); + console.log(` Platforms = ${cyan(sdk.manifest.platforms.sort().join(', '))}`); + console.log(` git Hash = ${cyan(sdk.manifest.githash || 'unknown')}`); } } else { console.log(gray(' None')); diff --git a/src/cli/info/windows.js b/src/cli/info/windows.js index 4d5452e..41c240c 100644 --- a/src/cli/info/windows.js +++ b/src/cli/info/windows.js @@ -1,25 +1,25 @@ export default { fetch: process.platform === 'win32' && (async () => (await appcd.call('/windows/2.x/info')).response), render(console, info) { - const { bold, cyan, gray, magenta } = require('chalk'); + const { cyan, gray, green, magenta } = require('chalk'); - console.log(bold('Visual Studio')); + console.log(magenta('Visual Studio'.toUpperCase())); if (info.visualstudio && Object.keys(info.visualstudio).length) { for (const [ ver, vs ] of Object.entries(info.visualstudio)) { - console.log(` ${cyan(ver)}`); - console.log(` Name = ${magenta(vs.name)}`); - console.log(` Path = ${magenta(vs.path)}`); + console.log(` ${green(ver)}`); + console.log(` Name = ${cyan(vs.name)}`); + console.log(` Path = ${cyan(vs.path)}`); } } else { console.log(gray(' None')); } console.log(); - console.log(bold('Windows SDKs')); + console.log(magenta('Windows SDKs'.toUpperCase())); if (info.sdks && Object.keys(info.sdks).length) { for (const [ ver, sdk ] of Object.entries(info.sdks)) { - console.log(` ${cyan(ver)}`); - console.log(` Name = ${magenta(sdk.name)}`); + console.log(` ${green(ver)}`); + console.log(` Name = ${cyan(sdk.name)}`); } } else { console.log(gray(' None')); diff --git a/src/cli/run-legacy.js b/src/cli/run-legacy.js new file mode 100644 index 0000000..bece802 --- /dev/null +++ b/src/cli/run-legacy.js @@ -0,0 +1,186 @@ +import path from 'path'; +import { capitalize } from '../lib/util'; +import { exec, spawnLegacyCLI } from '../legacy'; +import { isFile } from 'appcd-fs'; +import { prompt } from '../lib/prompt'; +// import { Tiapp } from 'titaniumlib'; + +const { log } = appcd.logger('run-legacy'); +const { highlight } = appcd.logger.styles; + +/** + * A cache of build options. The key is a combination of the platform and the Titanium SDK path. + * @type {Object} + */ +const buildOptionCache = {}; + +/** + * The build/run command callback that is executed when the parser finds the command. + * + * @param {Object} opts - Various options. + * @param {Object} opts.data - The CLI data object. + * @param {Parser} opts.parser - The cli-kit parser instance. + */ +export function callback({ data, parser }) { + parser.on('finalize', async ({ ctx }) => { + let platform; + let projectDir = data?.cwd; + + for (const arg of parser.args) { + if (arg.type === 'option') { + if (arg.option.name === 'project-dir') { + projectDir = arg.value; + } else if (arg.option.name === 'platform') { + platform = arg.value; + if (platform === 'ios') { + platform = 'iphone'; + } + } + } + } + + if (!projectDir) { + throw new Error('Expected project directory or current working directory'); + } + + const tiappFile = path.resolve(projectDir, 'tiapp.xml'); + if (!isFile(tiappFile)) { + throw new Error('Invalid project directory'); + } + + // FIX ME! + // data.tiapp = new Tiapp({ file: tiappFile }); + const sdk = '9.0.3.GA'; // data.tiapp.get('sdk-version'); + await loadOptions({ config: data.config, ctx, platform, sdk }); + }); +} + +/** + * Loads the CLI options for the given Titanium SDK into a cli-kit context. + * + * @param {Object} opts - Various options. + * @param {Context} opts.ctx - A cli-kit Context. + * @param {String} [opts.platform] - The platform name or falsey for all platforms. + * @param {String} opts.sdk - The name of the Titanium SDK. + * @returns {Promise} + */ +export async function loadOptions({ config, ctx, platform, sdk }) { + const sdkInfo = (await appcd.call('/sdk/find', { data: { name: sdk } })).response; + const cacheKey = `${platform || ''}|${sdkInfo.path}`; + let buildOptions = buildOptionCache[cacheKey]; + + if (!Array.isArray(buildOptions)) { + log(`Fetching "build" help for ${platform ? `platform "${platform}"` : 'all platforms'}: ${highlight(sdkInfo.path)}`); + + // load the Android and iOS options directly from the SDK + const buildConfig = await spawnLegacyCLI({ + data: { + command: 'build', + config, + sdkPath: sdkInfo.path, + type: 'help' + } + }); + + // copy the platform-specific options into a cli-kit friendly format + const lv = {}; + const lvRegExp = /^liveview/; + + buildOptions = []; + + for (const key of Object.keys(buildConfig)) { + if (key === 'flags' || key === 'options') { + // we skip the top level build flags/options because they are either + // already defined in the command or are unsupported + } else if (key === 'platforms') { + for (const [ platformName, conf ] of Object.entries(buildConfig[key])) { + if (platform && platform !== platformName) { + continue; + } + + const options = {}; + + for (const [ name, flag ] of Object.entries(conf.flags)) { + if (!flag.hidden) { + (lvRegExp.test(name) ? lv : options)[`--${name}`] = { desc: capitalize(flag.desc) }; + } + } + + for (const [ name, option ] of Object.entries(conf.options)) { + if (!option.hidden) { + let format = option.abbr ? `-${option.abbr}, ` : ''; + format += `--${name} [${option.hint || 'value'}]`; + (lvRegExp.test(name) ? lv : options)[format] = { + desc: capitalize(option.desc) + }; + } + } + + if (Object.keys(options).length) { + buildOptions.push(`${conf.title} build options`, options); + } + } + } else if (Array.isArray(buildConfig[key])) { + buildOptions.push.apply(buildOptions, buildConfig[key]); + } + } + + if (Object.keys(lv).length) { + buildOptions.push('LiveView Options', lv); + } + + buildOptionCache[cacheKey] = buildOptions; + } + + if (buildOptions.length) { + ctx.option(buildOptions); + } +} + +/** + * Common options for the `build` and `run` commands. + * @type {Object} + */ +export const options = { + '-d, --project-dir [path]': 'The directory containing the project; defaults to the current directory', + '-p, --platform [name]': 'The target build platform' +}; + +/** + * Runs a command in the Legacy Titanium CLI based on the cli-kit execution context. + * + * @param {String} command - The name of the command to run. + * @param {Object} ctx - A cli-kit execution context. + * @returns {Promise} + */ +export async function runLegacyCLI(command, ctx) { + const { argv, console, data, terminal } = ctx; + const { prompt: promptingEnabled } = argv; + + // remove general CLI-related values + delete argv.banner; + delete argv.color; + delete argv.help; + delete argv.prompt; + delete argv.version; + + await exec({ + argv, + command, + config: data.config, + console: console, + cwd: data.cwd, + prompt: promptingEnabled && (async question => { + return (await prompt({ + cancel() {}, // prevent '' from being thrown + validate(value) { + if (this.type === 'toggle' || !this.required || !!value) { + return true; + } + return question.validateMessage || false; + }, + ...question + }, terminal))[question.name]; + }) + }); +} diff --git a/src/cli/sdk/install.js b/src/cli/sdk/install.js index 427bea2..a12af38 100644 --- a/src/cli/sdk/install.js +++ b/src/cli/sdk/install.js @@ -1,5 +1,13 @@ +import ProgressBar from 'progress'; + +import { ansi } from 'cli-kit'; +import { arrowRight, bullet, tick } from 'figures'; + +const { log } = appcd.logger('sdk:install'); +const { alert, cyan, gray, green, highlight } = appcd.logger.styles; + export default { - async action({ console, argv }) { + async action({ argv, console, terminal }) { const { response } = await appcd.call('/sdk/install', { data: { keep: argv.keep, @@ -9,39 +17,100 @@ export default { } }); - return new Promise((resolve, reject) => { - let tasks = []; + try { + if (argv.progress) { + terminal.stderr.write(ansi.cursor.hide); + } + + await new Promise((resolve, reject) => { + let bar = null; + let tasks = []; + const tokens = {}; + + response + .on('data', evt => { + if (!evt || typeof evt !== 'object') { + return; + } + + try { + switch (evt.type) { + case 'tasks': + tasks = evt.tasks; + log('Received tasks:', tasks); + break; - response - .on('data', evt => { - switch (evt && evt.type) { - case 'tasks': - tasks = evt.tasks; - break; + case 'task-start': + { + const name = tasks[evt.task - 1] || null; + log(`Starting task: ${highlight(name)}`); - case 'task-start': - const name = tasks[evt.task - 1]; - if (name) { - console.log(`${name}...`); + if (argv.progress) { + tokens.name = name; + tokens.paddedPercent = ' 0%'; + tokens.symbol = cyan(arrowRight); + + if (evt.hasProgress) { + bar = new ProgressBar(' :symbol :name :paddedPercent [:bar]', { + clear: true, + complete: cyan('='), + incomplete: gray('.'), + stream: terminal.stderr, + total: 100, + width: 40 + }); + bar.render(tokens); + } else { + terminal.stderr.write(` ${tokens.symbol} ${name}`); + } + } else { + console.log(` ${green(bullet)} ${name}...`); + } + break; + } + + case 'task-progress': + if (bar) { + tokens.paddedPercent = (evt.progress * 100).toFixed(0).padStart(3) + '%'; + bar.update(evt.progress, tokens); + } + break; + + case 'task-end': + default: + { + if (bar || evt.type === 'task-end') { + terminal.stderr.cursorTo(0); + terminal.stderr.clearLine(); + bar = null; + } + + if (evt.type === 'task-end') { + const name = tasks[evt.task - 1] || null; + log(`Finished task: ${highlight(name)}`); + console.log(` ${green(tick)} ${name}`); + } else if (evt instanceof Error) { + console.error(`\n${alert(`Error: ${evt.message}`)}`); + } else { + console.log(`\n${evt.message || evt}`); + } + break; + } } - break; - - case 'task-progress': - // evt.progress - // console.log(evt.progress); - break; - - case 'task-end': - break; - - default: - console.log(evt.message || evt); - } - }) - .once('end', resolve) - .once('error', reject); - }); + } catch (e) { + reject(e); + } + }) + .once('end', resolve) + .once('error', reject); + }); + } finally { + if (argv.progress) { + terminal.stderr.write(ansi.cursor.show); + } + } }, + aliases: [ 'i' ], args: [ { name: 'version', diff --git a/src/cli/sdk/list.js b/src/cli/sdk/list.js index caef054..f806b7c 100644 --- a/src/cli/sdk/list.js +++ b/src/cli/sdk/list.js @@ -126,6 +126,7 @@ export default { } } }, + aliases: [ 'ls' ], desc: 'Print a list of installed SDK versions.', options: { '-b, --branches': 'Retreive and print all branches', diff --git a/src/cli/sdk/uninstall.js b/src/cli/sdk/uninstall.js index 479dac0..c5445cf 100644 --- a/src/cli/sdk/uninstall.js +++ b/src/cli/sdk/uninstall.js @@ -4,8 +4,9 @@ import { unique } from 'appcd-util'; export default { async action({ console, argv }) { try { + const { highlight } = appcd.logger.styles; const { response } = await appcd.call('/sdk/uninstall', { data: { uri: argv.version } }); - console.log(`Titanium SDK ${unique(response.map(r => r.name)).sort().join(', ')} uninstalled`); + console.log(`Titanium SDK ${unique(response.map(r => highlight(r.name))).sort().join(', ')} uninstalled`); } catch (e) { if (e.status === codes.NOT_FOUND) { console.error(e.message); @@ -14,6 +15,7 @@ export default { } } }, + aliases: [ 'rm' ], args: [ { name: 'version', diff --git a/src/index.js b/src/index.js index 93e13fd..9ec2b25 100644 --- a/src/index.js +++ b/src/index.js @@ -1,61 +1,42 @@ -// istanbul ignore if -if (!Error.prepareStackTrace) { - require('source-map-support/register'); -} - -// import fs from 'fs-extra'; -import gawk from 'gawk'; import CLIService from './cli/cli-service'; import ModuleService from './module/module-service'; +import ProjectService from './project/project-service'; import SDKService from './sdk/sdk-service'; - -import { debounce, get } from 'appcd-util'; +import { debounce } from 'appcd-util'; import { modules, options, sdk } from 'titaniumlib'; -const cliSvc = new CLIService(); -const moduleSvc = new ModuleService(); -const sdkSvc = new SDKService(); +const cliSvc = new CLIService(); +const moduleSvc = new ModuleService(); +const projectSvc = new ProjectService(); +const sdkSvc = new SDKService(); /** * Wires up plugin services. * - * @param {Object} cfg - An Appc Daemon config object * @returns {Promise} */ -export async function activate(cfg) { +export async function activate() { // set titaniumlib's network settings - const { APPCD_NETWORK_CA_FILE, APPCD_NETWORK_PROXY, APPCD_NETWORK_STRICT_SSL } = process.env; - const { network } = options; - const applySettings = () => { - Object.assign(network, cfg.network); - if (APPCD_NETWORK_CA_FILE) { - network.caFile = APPCD_NETWORK_CA_FILE; - } - if (APPCD_NETWORK_PROXY) { - network.httpProxy = network.httpsProxy = APPCD_NETWORK_PROXY; - } - if (APPCD_NETWORK_STRICT_SSL !== undefined && APPCD_NETWORK_STRICT_SSL !== 'false') { - network.strictSSL = true; - } - }; - applySettings(); - gawk.watch(cfg, [ 'network' ], debounce(applySettings)); + Object.assign(options.network, appcd.config.get('network')); + appcd.config.watch('network', debounce(network => Object.assign(options.network, network))); - options.searchPaths = get(cfg, 'titanium.searchPaths'); - - gawk.watch(cfg, [ 'titanium', 'searchPaths' ], debounce(value => { + options.searchPaths = appcd.config.get('titanium.searchPaths'); + appcd.config.watch('titanium.searchPaths', debounce(value => { options.searchPaths = value; moduleSvc.detectEngine.paths = modules.getPaths(); sdkSvc.detectEngine.paths = sdk.getPaths(); })); - await cliSvc.activate(cfg); + await cliSvc.activate(); appcd.register('/cli', cliSvc); - await moduleSvc.activate(cfg); + await moduleSvc.activate(); appcd.register([ '/module', '/modules' ], moduleSvc); - await sdkSvc.activate(cfg); + await projectSvc.activate(); + appcd.register('/project', projectSvc); + + await sdkSvc.activate(); appcd.register('/sdk', sdkSvc); } @@ -68,6 +49,7 @@ export async function deactivate() { await Promise.all([ cliSvc.deactivate(), moduleSvc.deactivate(), + projectSvc.deactivate(), sdkSvc.deactivate() ]); } diff --git a/src/legacy/README.md b/src/legacy/README.md new file mode 100644 index 0000000..8dce191 --- /dev/null +++ b/src/legacy/README.md @@ -0,0 +1,89 @@ +# Legacy Titanium CLI + +This is a stripped down version of the Titanium CLI v5. Several things such as argument parsing and +internationalization have been removed as they are not needed and speed is critical. + +The Titanium SDK contains commands such as `build` and `clean`. These commands are tightly coupled +with the Titanium CLI v5 and older. In order to be able to execute these commands, we need to shim +the bare minimum APIs and features of the Titanium CLI v5. + +While this legacy CLI attempts to maintain backwards compatibility, the following APIs and +features are no longer supported. + +#### `cli.version` + +The Titanium CLI version is set to `5.999.0` as we don't want to break compatibility with Titanium +CLI plugins, but we also want to signify that this is not the real Titanium CLI v5. + +#### CLI argument parser + +The legacy CLI no longer parses raw CLI arguments. It expects the `argv` values to be passed in as +a JSON object. + +#### i18n - Internationalization + +Locale is no longer detected and the locale is forced to `en-US`. We have very little translated +strings that users are not going to notice. + +#### Selected Titanium SDK and forking correct Titanium SDK + +The entire selected Titanium SDK system of the Titanium CLI has been removed. The `` +in the `tiapp.xml` is the source of truth. + +Because this legacy CLI will only ever be invoked with a valid Titanium SDK, various APIs are no +longer needed. See `cli.argv` below. + +#### `cli.argv._`, `cli.argv.$`, `cli.argv.$_`, `cli.argv.$0` + +`cli.argv` contained the raw CLI arguments and process name as well as the parsed arguments. While +the `cli.argv` will continue to contain the parsed arguments (passed in as a JSON object), the +raw arguments are no longer supported. These properties were intended to be internal only. + +Note that node-titanium-sdk's `validateCorrectSDK()` does reference these variables when preparing +to fork using the correct `--sdk` from the app's `tiapp.xml`, however it's a non-issue being that +the correct SDK version will always be set. + +#### `cli.on('cli:command-not-found')` + +This event was emitted when the specified command was not found so that the Titanium CLI could +check if there were any Titanium SDKs installed and if you happened to misspell the command. + +Since this legacy CLI will only ever be called with the either the `build` or `clean` command, +it is impossible for the command to not exist. + +#### #Built-in Titanium CLI hook plugins + +This legacy CLI does not contain any built-in CLI hook plugins such as `hooks/tisdk3fixes.js`. + +#### Config changes + +The configuration no longer contains settings for environment detection such as Android, iOS, +JDK, etc. Those config settings are in their respective appcd plugin config files and the Appc +Daemon user config file. + +This legacy Titanium CLI will not read or write the original Titanium CLI config file. + +#### Log changes + +The `--log-level` option has been dropped. There is no log message filtering effectively setting +the log level to `"trace"`. + +#### Prompting + +The legacy Titanium CLI does not prompt for missing or invalid values. It will throw an exception +containing the vital information required to do the prompting. + +The `build` command, and the platform-specific implementations, in the Titanium SDK define several +options that have prompt metadata, however it is unused. Depsite this, the `fields` library has +been mocked to be a noop. + +#### Android Detection + +When an Android app is being built, it needs to query the Android development environment. This +functionality is now performed by the `android` appcd plugin which does not validate the Java/JDK +and thus will not report any issues with Java being misconfigured. + +#### PAC Proxy Support + +The Titanium CLI v5 does not support PAC proxies, but the Appcelerator CLI did. This feature was +dropped. diff --git a/src/legacy/bootstrap.js b/src/legacy/bootstrap.js new file mode 100644 index 0000000..27714a1 --- /dev/null +++ b/src/legacy/bootstrap.js @@ -0,0 +1,32 @@ +/* istanbul ignore if */ +if (!Error.prepareStackTrace) { + require('source-map-support/register'); +} + +if (!process.connected) { + console.error('The Titanium SDK bootstrap cannot be directly executed.'); + process.exit(2); +} + +import 'v8-compile-cache'; +import 'colors'; +import './tunnel'; + +process.title = 'titanium-legacy-bootstrap'; + +// the Titanium SDK commands call process.exit() directly and that will interfere with the async output, so +// we need to monkey patch stdout/stderr to make sure the buffers are flushed +const { exit, stdout, stderr } = process; +process.exit = code => Promise + .all([ stdout, stderr ].map(stream => new Promise(resolve => { + if (stream._writableState && stream._writableState.needDrain) { + stream.on('drain', resolve); + } else { + resolve(); + } + }))) + .then(() => exit(code)); + +process + .on('uncaughtException', err => console.error(`Caught exception: ${err.stack || err.toString()}`)) + .on('unhandledRejection', err => console.error(`Unhandled rejection: ${err.stack || err.toString()}`)); diff --git a/src/legacy/hooks/aca.js b/src/legacy/hooks/aca.js new file mode 100644 index 0000000..d4fd66e --- /dev/null +++ b/src/legacy/hooks/aca.js @@ -0,0 +1,90 @@ +import fs from 'fs-extra'; +import isPlatformGuid from '@titanium-sdk/node-is-platform-guid'; +import path from 'path'; +import stream from 'stream'; +import tar from 'tar'; +import tunnel from '../tunnel'; +import { isDir } from 'appcd-fs'; +import { promisify } from 'util'; + +exports.init = (logger, config, cli) => { + const pipeline = promisify(stream.pipeline); + + cli.on('build.pre.compile', async ({ deployType, platformName, tiapp }) => { + if (!tiapp.modules.find(m => m.id === 'com.appcelerator.aca' && (!m.platform || m.platform === platformName || m.platform === 'ios'))) { + return; + } + + if (!isPlatformGuid(tiapp.guid)) { + throw new Error('Crash Analytics requires the application to be registered'); + } + + if (/^ios|iphone$/.test(platformName) && deployType !== 'development') { + logger.info('Authentication required, getting account...'); + const account = await tunnel.getAccount(); + if (!account) { + throw new Error('You must be authenticated to use Crash Analytics'); + } + + // we only need to upload the symbols for iOS apps + cli.on('build.post.compile', { + post: builder => uploadSymbols(builder, account), + priority: 10000 + }); + } + }); + + async function uploadSymbols({ iosBuildDir, platformName, tiapp }, account) { + const symbolsPath = path.join(iosBuildDir, `${tiapp.name}.app.dSYM`); + const symbolsTarFile = `${symbolsPath}.tar.gz`; + + if (!isDir(symbolsPath)) { + logger.error('Could not find iOS debug symbols, skipping Crash Analytics'); + return; + } + + const { api_token, limit, url } = (await tunnel.call('/amplify/2.x/ti/aca-upload-url', { + data: { + accountName: account.name, + appGuid: tiapp.guid + } + })).response; + + logger.info('Compressing debug symbols...'); + await tar.create({ + cwd: path.dirname(symbolsPath), + file: symbolsTarFile, + gzip: { level: 9 }, + portable: true + }, [ path.basename(symbolsPath) ]); + + const stat = await fs.stat(symbolsTarFile); + if (limit && stat.size > limit) { + logger.error('Symbol size exceeded max upload limit, skipping Crash Analytics'); + return; + } + + const { headers, statusCode } = await cli.got(`${url}?app=${tiapp.guid}&platform=${platformName}&version=${tiapp.version}`, { + followRedirect: false, + headers: { 'X-Auth-Token': api_token }, + retry: 0 + }); + + if (!headers.location || statusCode !== 302) { + logger.error('Failed to upload debug symbols, couldn\'t resolve upload destination'); + return; + } + + logger.info('Uploading debug symbols...'); + await pipeline( + fs.createReadStream(symbolsTarFile), + await cli.got.stream.put(headers.location, { + headers: { + 'Content-Length': stat.size + } + }) + ); + + logger.info('Symbols uploaded successfully'); + } +}; diff --git a/src/legacy/hooks/app-preview.js b/src/legacy/hooks/app-preview.js new file mode 100644 index 0000000..5689c65 --- /dev/null +++ b/src/legacy/hooks/app-preview.js @@ -0,0 +1,129 @@ +/** + * This code is based on https://github.com/jeffbonnes/appc-app-preview-cli-hook by Jeff Bonnes and + * licensed under the MIT license. + * + * Installr API docs: https://help.installrapp.com/api/ + */ + +import FormData from 'form-data'; +import fs from 'fs'; +import open from 'open'; +import tmp from 'tmp'; +import tunnel from '../tunnel'; +import { expandPath } from 'appcd-path'; + +const endpoint = 'https://appbeta.axway.com'; + +exports.init = async (logger, config, cli) => { + cli.on('build.config', data => { + data.result[1].appPreview = [ + 'App Preview Options', + { + '--app-preview': 'Deploy a distribution build to App Preview', + '--add [teams]': 'A comma-separated list of team names to add access to the App Preview build', + '--release-notes [text]': 'Release notes for the App Preview build', + '--invite [email_addresses]': 'A comma-separated list of email addresses to send the App Preview invites to', + '--notify [teams]': 'A comma-separated list of team names that have been previously invited to notify of App Preview build' + } + ]; + }); + + let { add, appPreview, invite, notify, outputDir, releaseNotes, target } = cli.argv; + + if (!appPreview) { + return; + } + + logger.info('Authentication required, getting account...'); + const account = await tunnel.getAccount(); + if (!account) { + throw new Error('You must be authenticated to use App Preview'); + } + + if (!account.org.entitlements.appPreview) { + throw new Error(`Your current organization "${account.org.name}" is not entitled to App Preview\nPlease upgrade your plan by visiting https://www.appcelerator.com/pricing/`); + } + + cli.on('build.pre.compile', async ({ platformName }) => { + if (platformName !== 'android' && platformName !== 'iphone') { + throw new Error('App Preview is only supported when building for Android or iOS'); + } + + if (!target?.startsWith('dist-')) { + throw new Error('App Preview can only be used when doing a distribution build'); + } + + if (platformName === 'iphone' && !outputDir) { + if (target === 'dist-appstore') { + logger.info('App Preview is forcing App Store build to skip Xcode archive'); + } + outputDir = cli.argv.outputDir = cli.argv['output-dir'] = tmp.tmpNameSync({ prefix: 'titanium-app-preview-' }); + } + + // TODO: prompt for releaseNotes and notify + }); + + cli.on('build.finalize', async ({ apkFile, platformName, tiapp }) => { + const file = platformName === 'android' ? apkFile : expandPath(outputDir, `${tiapp.name}.ipa`); + + const form = new FormData(); + form.append('qqfile', fs.createReadStream(file)); + form.append('releaseNotes', releaseNotes); + form.append('notify', notify); + if (add) { + form.append('add', add); + } + + logger.info('App Preview uploading build...'); + + const post = async (url, body) => { + try { + return (await cli.got(url, { + body, + headers: { + Accept: 'application/json', + Cookie: `connect.sid=${account.sid}`, + 'User-Agent': 'Titanium CLI' + }, + method: 'post', + responseType: 'json', + retry: 0 + })).body; + } catch (err) { + const msg = err.response?.body?.message || err.response?.body?.description; + err.message = `App Preview request failed: ${msg || err.message}`; + + const code = err.response?.body?.code; + if (code) { + err.code = code; + } + + throw err; + } + }; + + const { appData, message, result } = await post(`${endpoint}/apps.json`, form); + + if (result !== 'success') { + throw new Error(`App Preview failed to upload build: ${message || 'Unknown error'}`); + } + + logger.info('App Preview uploaded build successfully'); + + // check if we want to invite new testers + const emails = invite && invite.split(',').map(s => s.trim()).filter(Boolean); + if (emails?.length) { + try { + logger.info(`Adding tester${emails.length === 1 ? '' : 's'}: ${emails.join(', ')}`); + const form = new FormData(); + form.append('emails', emails.join(',')); + await post(`${endpoint}/apps/${appData.id}/builds/${appData.latestBuild.id}/team.json`, form); + logger.info(`Tester${emails.length === 1 ? '' : 's'} successfully invited`); + } catch (err) { + logger.warn(`App Preview failed to invite users: ${err.message}`); + } + } + + open(`${endpoint}/dashboard/index#/apps/${appData.id}`); + }); +}; diff --git a/src/legacy/hooks/appc.js b/src/legacy/hooks/appc.js new file mode 100644 index 0000000..89b5088 --- /dev/null +++ b/src/legacy/hooks/appc.js @@ -0,0 +1,491 @@ +import crypto from 'crypto'; +import fs from 'fs-extra'; +import isPlatformGuid from '@titanium-sdk/node-is-platform-guid'; +import path from 'path'; +import plist from 'simple-plist'; +import security from 'appc-security'; +import tunnel from '../tunnel'; +import zlib from 'zlib'; +import * as version from '../../lib/version'; + +import { expandPath } from 'appcd-path'; +import { promisify } from 'util'; +import { sha1 } from 'appcd-util'; + +/** + * Wires up hooks for platform integration. + * + * @param {Object} logger - The Titanium CLI logger. + * @param {Object} config - The Titanium CLI config object. + * @param {CLI} cli - The Titanium CLI instance. + */ +exports.init = (logger, config, cli) => { + const gzip = promisify(zlib.gzip); + const homeDir = expandPath(config.get('home')); + let account; + + /** + * Hook into the build pre-construct event to validate the encryption policy. + */ + cli.on('build.pre.construct', builder => { + switch (builder.tiapp.properties?.['appc-sourcecode-encryption-policy']?.value) { + case 'embed': + throw new Error('The source code encryption policy "embed" is no longer supported, please use the "default" or "remote" encryption policy'); + case 'remote': + // disable encryption since we'll do it ourselves + // note: this must be disabled during pre-construct before the builder's initialize() is called + builder.encryptJS = false; + } + }); + + /** + * Hook into the build pre-compile event to enforce entitlements, wire up encryption, and + * perform build verification. + */ + cli.on('build.pre.compile', { + async post(builder) { + const { deployType, platformName, projectDir, tiapp } = builder; + const policy = tiapp.properties?.['appc-sourcecode-encryption-policy']?.value; + const tiprep = { + pre: function (data) { + const orig = data.fn; + data.fn = function (...args) { + const data = args[1]; + [ null, arguments['1'].length - 1 ].forEach(function () { + if (data[arguments['0'] || arguments['1']].length === 36) { + data[arguments['0'] || arguments['1']] = data[arguments['0'] || arguments['1']].split('').reverse().join(''); + } + }); + orig.apply(this, args); + }; + }, + priority: 10000 + }; + + // step 1: production check + if (deployType === 'production') { + logger.info('Authentication required, getting account...'); + account = await tunnel.getAccount(); + if (!account) { + throw new Error('You must be authenticated to perform production builds'); + } + if (!account.org.entitlements.allowProduction) { + throw new Error(`Your current organization "${account.org.name}" is not entitled to production builds\nPlease upgrade your plan by visiting https://www.appcelerator.com/pricing/`); + } + } + + // step 2: registered check + if (isPlatformGuid(tiapp.guid)) { + // step 2.1: authenticate + if (!account) { + logger.info('Authentication required, getting account...'); + account = await tunnel.getAccount(); + if (!account) { + throw new Error('You must be authenticated to build registered applications'); + } + } + + // step 2.2: wire up post compile metadata update + cli.on('build.post.compile', { + priority: 10000, + async post() { + await tunnel.call('/amplify/2.x/ti/app/set', { + data: { + accountName: account.name, + tiapp: await fs.readFile(path.join(projectDir, 'tiapp.xml'), 'utf-8') + } + }); + logger.trace('Updated platform with tiapp metadata'); + } + }); + + // step 2.3: verify build + const buildData = await verifyBuild({ + account, + deployType, + modules: builder.modules, + projectDir, + tiapp + }); + + // step 2.4: check to see if we need to force a rebuild + if (platformName === 'android') { + try { + const applicationJava = path.join(builder.buildGenAppIdDir, `${builder.classname}Application.java`); + builder.forceRebuild = !(await fs.readFile(applicationJava, 'utf-8')).includes('new AssetCryptImpl'); + } catch (e) { + builder.forceRebuild = true; + } + } else if (platformName === 'iphone') { + try { + const src = await fs.readFile(path.join(__dirname, '..', '..', '..', 'support', 'ios', 'ApplicationRouting.m'), 'utf-8'); + const dest = await fs.readFile(path.join(builder.buildDir, 'Classes', 'ApplicationRouting.m'), 'utf-8'); + builder.forceRebuild = src !== dest; + } catch (e) { + builder.forceRebuild = true; + } + } + + // step 2.5: wire up remote encryption + if (policy === 'remote') { + if (platformName !== 'android' && platformName !== 'iphone') { + throw new Error('Remote encryption policy is only available for Android and iOS apps'); + } + + // force appcelerator.com to be injected into the ATS whitelist + // note: this must be done post-compile after the builder's initialize() is called + builder.whitelistAppceleratorDotCom = true; + + // override the default titanium prep mutator with the remote encryption logic + tiprep.pre = createRemoteHook(buildData); + } + + } else if (policy === 'remote') { + throw new Error('Remote encryption policy is only available to registered apps'); + } + + // step 3: wire up the titanium prep hook + cli.on('build.android.titaniumprep', tiprep); + cli.on('build.ios.titaniumprep', tiprep); + }, + priority: 0 + }); + + /** + * Creates the titanium prep hook that handles remote encryption. + * + * @param {Object} buildData - The build verify response. + * @returns {Function} The function hook. + */ + function createRemoteHook(buildData) { + return function (data) { + const orig = data.fn; + data.fn = async function (...args) { + const callback = args[args.length - 1]; + try { + // step 1: create a lot of variables + const forge = require('node-forge'); + const { buildDir, platformName, tiapp } = this; + const isSimBuild = this.target === 'simulator' || this.target === 'emulator'; + const debuggerDetect = tiapp.properties['appc-security-debugger-detect'] !== false; + const jailBreakDetect = !isSimBuild && tiapp.properties['appc-security-jailbreak-detect']; + const assetDir = platformName === 'android' ? this.buildBinAssetsDir : this.xcodeAppDir; + const outputDir = path.join(assetDir, sha1(tiapp.guid)); + const keys = {}; + const shasum = crypto.createHash('sha1'); + const privateKey = forge.pki.privateKeyFromPem(await fs.readFile(path.join(homeDir, `.${sha1(`${account.name}${account.org.id}`)}.pk`))); + const md = forge.md.sha256.create(); + const signature = privateKey.sign(md.update(buildData.i, 'utf8')); + const signatureBase64 = Buffer.from(forge.util.bytesToHex(signature), 'hex').toString('base64'); + const signatureShaBase64 = Buffer.from(forge.util.bytesToHex(md.digest().bytes()), 'hex').toString('base64'); + const appVerifyURL = await tunnel.call('/amplify/2.x/ti/app-verify-url'); + + // step 2: encrypt the source files and write them into the + await fs.mkdirs(outputDir); + await Promise.all(this.jsFilesToEncrypt.map(async filename => { + const from = path.join(assetDir, filename); + const to = path.join(outputDir, sha1(filename)); + + // unmark the encrypted file for deletion + if (this.buildDirFiles) { + delete this.buildDirFiles[to]; + } + + logger.trace(`Encrypting ${from} => ${to}`); + const unencrypted = await fs.readFile(from, 'utf-8'); + const encrypted = security.encrypt(unencrypted, buildData.key, buildData.pepper, buildData.hmacKey, 'base64', 128); + + // store our key by filename path + keys[filename] = encrypted.derivedKey.toString('hex'); + + // gzip the buffer contents + const compressed = await gzip(encrypted.value); + logger.trace(`Compressed ${to} ${encrypted.value.length} => ${compressed.length} bytes`); + shasum.update(sha1(compressed)); + await fs.writeFile(to, compressed); + await fs.remove(from); + })); + + const shaofshas = shasum.digest('hex'); + logger.debug(`sha of shas ${shaofshas}`); + + // step 3: notify the platform with encryption info + try { + await tunnel.call('/amplify/2.x/ti/build-update', { + data: { + buildId: buildData.i, + buildSHA: shaofshas, + keys + } + }); + } catch (err) { + // possibly offline? + logger.warn(`Failed to update build metadata: ${err.toString()}`); + return orig.apply(this, args); + } + + // step 4: copy over iOS specific files + if (platformName === 'iphone') { + let src = await fs.readFile(path.join(__dirname, '..', '..', '..', 'support', 'ios', 'ApplicationRouting.m'), 'utf-8'); + let dest = await fs.readFile(path.join(buildDir, 'Classes', 'ApplicationRouting.m'), 'utf-8'); + if (src !== dest) { + const stat = await fs.stat(src); + await fs.copy(src, dest); + await fs.utimes(dest, stat.atime, stat.mtime); + } + + // we are going to copy over the tiverify with our own implementation + src = path.join(__dirname, '..', '..', '..', 'support', 'ios', 'libappcverify.a'); + dest = path.join(buildDir, 'lib', 'libtiverify.a'); + try { + if (!fs.lstatSync(dest).isSymbolicLink()) { + throw new Error(); + } + await fs.unlink(dest); + await fs.symlink(src, dest); + } catch (err) { + await fs.copy(src, dest); + } + + if (!isSimBuild) { + // we only write main if we're not running on simulator + const mainFile = path.join(buildDir, 'main.m'); + let main = await fs.readFile(mainFile, 'utf-8'); + + // if we are re-writing the same file contents, filter out existing lines so we can + // re-write them below and not step on each other + main = main.split('\n') + .filter(line => !line.includes('TI_APPLICATION_APPC')) + .join('\n'); + + const idx = main.indexOf('int main('); + if (idx === -1) { + throw new Error('Couldn\'t find main entry point in main.m'); + } + + await fs.writeFile(`${main.substring(0, idx)} +#define TI_APPLICATION_APPC_DBG_CHECK vv9800980890v +#define TI_APPLICATION_APPC_JBK_CHECK c899089089 +#define TI_APPLICATION_APPC_VERIFY_PEPPER gggfk332944990 +#define TI_APPLICATION_APPC_VERIFY_HMAC ddkssg33jjg4jh +const bool TI_APPLICATION_APPC_DBG_CHECK = ${debuggerDetect}; +const bool TI_APPLICATION_APPC_JBK_CHECK = ${jailBreakDetect}; +NSString* const TI_APPLICATION_APPC_VERIFY_PEPPER = nil; +NSString* const TI_APPLICATION_APPC_VERIFY_HMAC = nil; +${main.substring(idx)}`, 'utf-8'); + } + } + + // step 5: prepare and write store + const store = { + build: Date.now(), + debuggerDetect, + jailBreakDetect, + policy: 'remote', + sha: shaofshas, + url: `${appVerifyURL}/${encodeURIComponent(buildData.i)}/${encodeURIComponent(signatureBase64)}/${encodeURIComponent(signatureShaBase64)}` + }; + logger.trace('Store data:', store); + for (const key of Object.keys(store)) { + store[key] = Buffer.from(String(store[key])).toString('base64'); + } + const storeData = platformName === 'android' + ? Buffer.from(JSON.stringify(store)).toString('base64') + : isSimBuild + ? plist.stringify(store) + : plist.bplistCreator(store); + const storeFile = path.join(assetDir, sha1(tiapp.id)); + await fs.writeFile(storeFile, storeData); + + // TODO: send the storeSha to platform + // const storeSha = sha1(store); + + // unmark the encrypted file for deletion + if (this.buildDirFiles) { + delete this.buildDirFiles[storeFile]; + } + + // step 6: wire up Android specific hooks to generate the Application.java file and wire up the legacy jar files + if (platformName === 'android') { + // Titanium SDK 9 switched to Gradle and no longer manually invokes aapt, javac, and the dexer, so for older + const legacy = version.lt(this.titaniumSdkVersion, '9.0.0'); + + if (legacy) { + cli.on('build.android.dexer', data => { + data.args[1].push(path.join(buildDir, 'libs', 'appcelerator-security.jar')); + data.args[1].push(path.join(buildDir, 'libs', 'appcelerator-verify.jar')); + }); + } + + cli.on('build.android.javac', async data => { + // write Application.java file + const applicationJava = path.join(this.buildGenAppIdDir, `${this.classname}Application.java`); + let contents = await fs.readFile(applicationJava, 'utf-8'); + if (contents.includes('new AssetCryptImpl')) { + contents = contents + .replace(/KrollAssetHelper\.setAssetCrypt[^;]+?;/, 'KrollAssetHelper.setAssetCrypt(new com.appcelerator.verify.AssetCryptImpl(this, appInfo));') + .replace(/(public void verifyCustomModules)/g, `\ +public void setCurrentActivity(android.app.Activity callingActivity, android.app.Activity activity) +{ +com.appcelerator.verify.AssetCryptImpl.setActivity(activity); +super.setCurrentActivity(callingActivity, activity); +} + +@Override +$1`); + await fs.writeFile(applicationJava, contents, 'utf-8'); + } + + if (legacy) { + // patch javac args + const classpathIdx = data.args.indexOf('-bootclasspath') + 1; + for (const jar of [ 'appcelerator-security.jar', 'appcelerator-verify.jar' ]) { + const src = path.join(__dirname, '..', '..', '..', 'support', 'android', jar); + const dest = path.join(buildDir, 'libs', jar); + data.args[classpathIdx] += `${path.delimiter}${dest}`; + await fs.copyFile(src, dest); + } + } + }); + } + + callback(); + } catch (err) { + callback(err); + } + }; + }; + } + + /** + * Generates the developer key pair and writes it in the home directory. + * + * @param {Object} account - The authenticated account info. + * @returns {Promise} + */ + async function generateDevCert(account) { + logger.info('Generating developer certificate and private/public keys'); + + const filename = path.join(homeDir, `.${sha1(`${account.name}${account.org.id}`)}`); + const certFile = `${filename}.pem`; + const keyFile = `${filename}.pk`; + const { pki } = require('node-forge'); + const keys = pki.rsa.generateKeyPair(1024); + const publicKey = pki.publicKeyToPem(keys.publicKey); + + logger.info('Registering developer certificate'); + const certificate = await tunnel.call('/amplify/2.x/ti/enroll', { + data: { + accountName: account.name, + fingerprint: cli.fingerprint, + publicKey + } + }); + + await fs.mkdirs(homeDir); + await fs.writeFile(certFile, certificate); + await fs.writeFile(keyFile, pki.privateKeyToPem(keys.privateKey)); + + if (process.platform === 'darwin' || process.platform === 'linux') { + await fs.chmod(certFile, '400'); + await fs.chmod(keyFile, '400'); + } + } + + /** + * Verifies the build and retrieves the build id and developer identification info. + * + * @param {Object} params - Various parameters. + * @param {Object} params.account - The authenticated account info. + * @param {String} params.deployType - The build deploy type. + * @param {Array.} params.modules - A list of module descriptors that based on the + * `tiapp.xml` that are compatible with the current platform being built for. + * @param {Object} params.tiapp - An object containing the values from the `tiapp.xml`. + * @returns {Promise} + */ + async function verifyBuild({ account, deployType, modules, projectDir, tiapp }) { + const buildFile = path.join(homeDir, 'builds', `${sha1([ + account.name, + tiapp.guid, + tiapp.id, + deployType, + cli.fingerprint, + account.org.id + ])}.json`); + + let lastBuild; + try { + lastBuild = await fs.readJson(buildFile); + } catch (e) { + await fs.remove(buildFile); + } + + // how long since we last verified this build configuration + const age = lastBuild ? (Date.now() - lastBuild.timestamp) / 3600000 : null; + + if (lastBuild && deployType !== 'production' && age < 24) { + lastBuild.skipped = true; + return lastBuild; + } + + // we are going to verify the build because it's either there was no previous build, this + // is a production build, or it is a development/test build that was last verified over + // 24 hours ago + + const verify = async data => { + try { + return await tunnel.call('/amplify/2.x/ti/build-verify', { data }); + } catch (err) { + if (err.code === 'com.appcelerator.platform.app.notregistered') { + // this is ok, just means the app isn't registered with the platform yet + } else if (/^com\.appcelerator\.platform\.developercertificate\.(notfound|invalid)$/.test(err.code)) { + logger.warn('Developer certs need to be regenerated'); + await generateDevCert(account); + + try { + // try again + return await tunnel.call('/amplify/2.x/ti/build-verify', { data }); + } catch (err2) { + if (err2.code !== 'com.appcelerator.platform.app.notregistered') { + throw err2; + } + } + } else { + throw err; + } + } + }; + + try { + logger.info('Verifying build'); + const result = await verify({ + accountName: account.name, + appGuid: tiapp.guid, + appId: tiapp.id, + deployType, + fingerprint: cli.fingerprint, + name: tiapp.name, + modules, + tiapp: await fs.readFile(path.join(projectDir, 'tiapp.xml'), 'utf-8') + }); + + // write last build + if (result) { + result.skipped = false; + result.timestamp = Date.now(); + await fs.mkdirs(path.join(homeDir, 'builds')); + await fs.writeJson(buildFile, result); + } + + return result; + } catch (err) { + // probably offline, fail + tunnel.log(`Build verify failed: ${err.toString()} (${err.code})`); + if (deployType === 'production') { + throw new Error('You must be online in order to build this application for production'); + } else { + throw new Error(`You must be online to build this application${lastBuild ? ' again' : ''}`); + } + } + } +}; diff --git a/src/legacy/index.js b/src/legacy/index.js new file mode 100644 index 0000000..af88d24 --- /dev/null +++ b/src/legacy/index.js @@ -0,0 +1,173 @@ +import path from 'path'; +import { AppcdError, codes } from 'appcd-response'; +import { Project } from 'titaniumlib'; +import { PromptError } from '../lib/prompt'; +import { spawn } from 'appcd-subprocess'; + +const logger = appcd.logger('legacy-cli'); +const { log } = logger; + +/** + * Executes the 'build' or 'clean' command from the Legacy Titanium CLI. + * + * @param {Object} opts - Various options. + * @param {Object} opts.argv - The parsed arguments. + * @param {String} opts.command - The name of the command to execute. + * @param {Object} [opts.config] - The Titanium configuration. + * @param {Console} opts.console - A console to write output to. + * @param {String} [opts.cwd] - The current working directory. Only required if `projectDir` is + * undefined or a relative path. + * @param {Function} [opts.prompt] - A function that prompts for user input. + * @returns {Promise} + */ +export async function exec({ argv, command, config, console, cwd, prompt }) { + let { projectDir } = argv; + + // step 1: validate the project directory + if (projectDir !== undefined && typeof projectDir !== 'string') { + const err = new PromptError('Invalid project directory', { + message: 'Where is the project located?', + name: 'projectDir', + type: 'text' + }); + + if (prompt) { + ({ projectDir } = await prompt(err)); + } else { + throw err; + } + } + + if (projectDir === undefined || !path.isAbsolute(projectDir)) { + if (!cwd || typeof cwd !== 'string') { + throw new AppcdError(codes.BAD_REQUEST, 'Current working directory required when project directory is relative'); + } + projectDir = path.resolve(cwd, projectDir || '.'); + } + + // step 2: init the project + const project = new Project({ + path: projectDir + }); + + // step 3: load the sdk + // FIX ME! + // const { sdk } = project.tiapp.get('sdk-version'); + const sdk = '9.0.3.GA'; + const sdkInfo = (await appcd.call('/sdk/find', { data: { name: sdk } })).response; + + const data = { + argv: { + ...argv, + projectDir, + sdk + }, + command, + config: config || {}, + cwd: projectDir, + fingerprint: (await appcd.call('/appcd/status/system/mid')).response, + network: appcd.config.get('network'), + promptingEnabled: !!prompt, + sdkPath: sdkInfo.path, + type: 'exec' + }; + + // map `build` and `run` to the correct legacy `build` command + if (command === 'build') { + data.argv.buildOnly = true; + } else if (command === 'run') { + data.command = 'build'; + } + + // step 4: spawn the legacy cli + await spawnLegacyCLI({ console, data, prompt }); +} + +/** + * Spawns the Legacy CLI and resolves once the command finishes. + * + * @param {Object} opts - Various options. + * @param {Console} [opts.console] - The console to pipe output to. + * @param {Object} [opts.data] - A data payload to send over the IPC tunnel to the Legacy Titanium + * CLI. + * @param {Function} [opts.prompt] - A function that prompts for user input. + * @returns {Promise} + */ +export async function spawnLegacyCLI({ console, data, prompt }) { + log(`Spawning legacy Titanium CLI bootstrap (console ${console ? 'enabled' : 'disabled'})`); + + const { child } = spawn({ + command: process.execPath, + args: [ path.resolve(__dirname, 'bootstrap.js') ], + options: { + cwd: data?.cwd, + env: Object.assign({ FORCE_COLOR: 1 }, process.env), + stdio: [ 'ignore', 'pipe', 'pipe', 'ipc' ] + } + }); + + const trace = logger(`${child.pid}-trace`); + + if (console) { + child.stdout.on('data', data => console._stdout.write(data.toString())); + child.stderr.on('data', data => console._stderr.write(data.toString())); + } else { + const { log } = logger(`${child.pid}-stdout`); + const { error } = logger(`${child.pid}-stderr`); + const newline = /\n$/; + child.stdout.on('data', data => log(data.toString().replace(newline, ''))); + child.stderr.on('data', data => error(data.toString().replace(newline, ''))); + } + + return await new Promise((resolve, reject) => { + child.on('close', code => { + log(`Legacy Titanium CLI bootstrap exited (code ${code || 0})`); + resolve(); + }); + + child.on('message', async msg => { + const { type } = msg; + + if (type === 'call') { + const { id, path, data } = msg; + if (id && path) { + try { + const response = await appcd.call(path, data); + child.send({ id, response, type: 'response' }); + } catch (err) { + child.send({ code: err.code, error: err.message, id, stack: err.stack, type: 'error' }); + } + } + + } else if (type === 'error') { + const err = new Error(msg.message); + reject(Object.assign(err, msg)); + + } else if (type === 'json') { + resolve(msg.data); + + } else if (type === 'log') { + trace.log(...msg.args); + + } else if (type === 'prompt') { + const { id, question } = msg; + + if (id && question) { + if (prompt) { + const answer = await prompt(question); + child.send({ answer, id, type: 'answer' }); + } else { + child.send({ error: 'Prompting is not enabled', id, type: 'error' }); + } + } + + } else if (type === 'telemetry') { + appcd.telemetry(msg.payload); + } + }); + + log('Sending data to bootstrap:'); + log(data); + child.send(data); + }); +} diff --git a/src/legacy/patch/android.js b/src/legacy/patch/android.js new file mode 100644 index 0000000..eae4917 --- /dev/null +++ b/src/legacy/patch/android.js @@ -0,0 +1,265 @@ +/* eslint-disable promise/no-callback-in-promise */ + +import tunnel from '../tunnel'; +import * as version from '../../lib/version'; +import { snooplogg } from 'cli-kit'; + +const { alert } = snooplogg.styles; + +export function patch() { + let cache; + + return { + /** + * Detects Android development environment by calling the `android` appcd plugin and translating + * the results into a Titanium SDK compatible structure. + * + * @param {Object} config - The Titanium CLI config object. + * @param {Object} opts - Various options. + * @param {Object} opts.packageJson - The Android platform-specific `package.json`. + * @param {Function} callback - A function to call with the detection results. Note that this + * function receives a single argument with the info. Any errors must be silenced. + * @returns {undefined} + */ + detect(config = {}, { packageJson } = {}, callback) { + if (cache) { + return callback(null, cache); + } + + tunnel.call('/android/2.x/info') + .then(({ response: info }) => { + const { vendorDependencies } = packageJson; + const results = { + avds: info.emulators, + devices: info.devices, + issues: [], + ndk: processNDK(info.ndks), + sdk: processSDK(info.sdks, config, vendorDependencies), + targets: {}, + vendorDependencies: vendorDependencies || {} + }; + + processTargets(results, info.sdks, vendorDependencies); + processIssues(results, config, vendorDependencies); + + cache = results; + callback(results); + }) + .catch(err => { + tunnel.log(alert(err.stack)); + callback({ + issues: [], + sdk: null + }); + }); + } + }; +} + +function processIssues(results, config, vendorDependencies) { + if (!results.ndk) { + results.issues.push({ + id: 'ANDROID_NDK_NOT_FOUND', + type: 'warning', + message: `Unable to locate an Android NDK. +Without the NDK, you will not be able to build native Android Titanium modules. +To install the Android NDK, use Android Studio's SDK Manager. +If you have already installed the Android NDK, configure the location by running: appcd config push android.ndk.searchPaths /path/to/android-ndk` + }); + } + + if (results.sdk) { + const appendInfo = msg => { + return `${msg} + +Current installed Android SDK tools: +Android SDK Tools: ${results.sdk.tools.version || 'not installed'} (Supported: ${vendorDependencies['android tools']}) +Android SDK Platform Tools: ${results.sdk.platformTools.version || 'not installed'} (Supported: ${vendorDependencies['android platform tools']}) +Android SDK Build Tools: ${results.sdk.buildTools.version || 'not installed'} (Supported: ${vendorDependencies['android build tools']}) + +Make sure you have the latest Android SDK Tools, Platform Tools, and Build Tools installed.`; + }; + + if (!results.sdk.buildTools.supported) { + results.issues.push({ + id: 'ANDROID_BUILD_TOOLS_NOT_SUPPORTED', + type: 'error', + message: appendInfo(`Android Build Tools ${results.sdk.buildTools.version} are not supported by Titanium`) + }); + } + + if (results.sdk.buildTools.notInstalled) { + const preferred = config.get('titanium.android.buildTools.selectedVersion'); + results.issues.push({ + id: 'ANDROID_BUILD_TOOLS_CONFIG_SETTING_NOT_INSTALLED', + type: 'error', + message: appendInfo(`The selected version of Android SDK Build Tools (${preferred}) are not installed. +Please either install this version of the build tools or remove this setting by running: +ti config delete android.buildTools.selectedVersion +and +appcd config delete titanium.android.buildTools.selectedVersion`) + }); + } + + // check if the sdk is missing any commands + var missing = [ 'adb', 'emulator', 'mksdcard', 'zipalign', 'aapt', 'aidl', 'dx' ].filter(cmd => !results.sdk.executables[cmd]); + if (missing.length && results.sdk.buildTools.supported) { + results.issues.push({ + id: 'ANDROID_SDK_MISSING_PROGRAMS', + type: 'error', + message: appendInfo(`Missing required Android SDK tool${missing.length !== 1 ? 's' : ''}: ${missing.join(', ')}`) + }); + } + } else { + results.issues.push({ + id: 'ANDROID_SDK_NOT_FOUND', + type: 'error', + message: `Unable to locate an Android SDK. +To install the Android SDK, use Android Studio's SDK Manager. +If you have already installed the Android SDK, configure the location by running: appcd config push android.sdk.searchPaths /path/to/android-sdk` + }); + } +} + +function processNDK(ndks) { + if (!ndks.length) { + return {}; + } + return ndks.length > 1 && ndks.find(ndk => ndk.default) || ndks[0]; +} + +function processSDK(sdks, config, vendorDependencies = {}) { + if (!sdks.length) { + return null; + } + + const sdk = sdks.length > 1 && sdks.find(sdk => sdk.default) || sdks[0]; + + const results = { + path: sdk.path, + executables: { + adb: null, + android: null, + emulator: null, + mksdcard: null, + zipalign: null, + aapt: null, + aidl: null, + dx: null, + apksigner: null + }, + dx: null, + proguard: null, + tools: { + path: null, + supported: null, + version: null + }, + platformTools: { + path: null, + supported: null, + version: null + }, + buildTools: { + maxSupported: null, + notInstalled: false, + path: null, + supported: null, + version: null + } + }; + + if (sdk.platformTools) { + results.executables.adb = sdk.platformTools.executables.adb; + } + + if (sdk.tools) { + results.executables.emulator = sdk.tools.executables.emulator; + } + + if (sdk.buildTools) { + const supportedRange = vendorDependencies['android build tools']; + const min = supportedRange && version.parseMin(supportedRange); + const preferred = config.get('titanium.android.buildTools.selectedVersion'); + const installedBuildTools = sdk.buildTools + .map(b => { + b.supported = supportedRange && version.satisfies(b.version, supportedRange) ? true : min && version.lt(b.version, min) ? false : 'maybe'; + return b; + }) + .sort((a, b) => version.compare(a.version, b.version)); + + let buildTools; + if (preferred) { + buildTools = installedBuildTools.find(bt => version.eq(bt.version, preferred)); + results.buildTools.notInstalled = !buildTools; + } else { + buildTools = installedBuildTools[0]; + results.buildTools.notInstalled = false; + } + + if (buildTools) { + results.buildTools.path = buildTools.path; + results.buildTools.version = buildTools.version; + results.buildTools.supported = buildTools.supported; + if (supportedRange) { + results.buildTools.maxSupported = version.parseMax(supportedRange); + } + results.dx = buildTools.dx; + Object.assign(results.executables, buildTools.executables); + } + } + + return results; +} + +function processTargets(results, sdks, vendorDependencies) { + let idx = 1; + let valid = 0; + + for (const sdk of sdks) { + for (const platform of sdk.platforms) { + const supported = !~~platform.apiLevel || version.satisfies(platform.apiLevel, vendorDependencies['android sdk']); + + if (supported) { + valid++; + } else { + results.issues.push({ + id: 'ANDROID_API_TOO_OLD', + type: 'warning', + message: `Android API ${platform.name} (${platform.id}) is too old. +The minimum supported Android SDK platform API level API level ${version.parseMin(vendorDependencies['android sdk'])}.` + }); + } + + results.targets[String(idx++)] = { + abis: platform.abis, + aidl: platform.aidl, + androidJar: platform.androidJar, + 'api-level': platform.apiLevel, + id: platform.sdk, + name: platform.name, + path: platform.path, + revision: platform.revision, + sdk: platform.apiLevel, + skins: platform.skins, + supported, + type: 'platform', + version: platform.version + }; + } + } + + if (idx === 1) { + results.issues.push({ + id: 'ANDROID_NO_APIS', + type: 'error', + message: 'No Android APIs found.\nRun \'Android Studio\' to install the latest Android APIs.' + }); + } else if (!valid) { + results.issues.push({ + id: 'ANDROID_NO_VALID_APIS', + type: 'warning', + message: 'No supported Android APIs found\nRun \'Android Studio\' to install the latest Android APIs.' + }); + } +} diff --git a/src/legacy/patch/fields.js b/src/legacy/patch/fields.js new file mode 100644 index 0000000..389ed8b --- /dev/null +++ b/src/legacy/patch/fields.js @@ -0,0 +1,48 @@ +/** + * Constructs a shim for the `fields` package that translates prompt settings into `enquirer` + * format. + * + * @returns {Object} + */ +export function patch() { + return { + setup() {}, + + file(opts) { + return { + initial: opts.default, + message: opts.title, + name: 'foo', + type: 'text' + }; + }, + + select(opts) { + const choices = Array.isArray(opts.options) ? opts.options : [].concat(...Object.values(opts.options)); + const formatter = opts.formatters?.option || (option => option[opts.optionLabel || 'label']); + + return { + choices: choices.map(choice => { + return { + message: formatter(choice, '', '').trim(), + name: choice[opts.optionLabel || 'label'], + value: choice[opts.optionValue || 'value'] + }; + }), + initial: opts.default, + message: opts.title, + name: 'foo', + type: 'select' + }; + }, + + text(opts) { + return { + initial: opts.default, + message: opts.title, + name: 'foo', + type: 'text' + }; + } + }; +} diff --git a/src/legacy/patch/ios.js b/src/legacy/patch/ios.js new file mode 100644 index 0000000..92137a3 --- /dev/null +++ b/src/legacy/patch/ios.js @@ -0,0 +1,559 @@ +/* eslint-disable promise/no-callback-in-promise */ + +import path from 'path'; +import tunnel from '../tunnel'; +import * as version from '../../lib/version'; +import { snooplogg } from 'cli-kit'; + +const { alert } = snooplogg.styles; + +export function patch({ load, request, parent, isMain }) { + let cache; + + const ioslib = load(request, parent, isMain); + const { SimHandle } = ioslib.simulator; + + ioslib.detect = (opts = {}, callback) => { + if (cache) { + return callback(null, cache); + } + + tunnel.call('/ios/2.x/info') + .then(({ response: info }) => { + const results = { + certs: {}, + devices: info.devices, + iosSDKtoXcode: {}, + issues: [], + provisioning: {}, + selectedXcode: null, + simulators: processSimulators(info.simulators), + teams: Object.entries(info.teams).map(([ id, name ]) => ({ id, name })), + xcode: {} + }; + + processCerts(info, results); + processProvisioning(info, results); + processXcodes(info, results, opts); + + cache = results; + callback(null, results); + }) + .catch(err => { + tunnel.log(alert(err.stack)); + callback(err); + }); + }; + + /** + * Finds a iOS Simulator and/or Watch Simulator as well as the supported Xcode based on the specified options. + * + * @param {Object} [options] - An object containing various settings. + * @param {String} [options.appBeingInstalled] - The path to the iOS app to install after launching the iOS Simulator. + * @param {Boolean} [options.bypassCache=false] - When true, re-detects Xcode and all simulators. + * @param {Function} [options.logger] - A function to log debug messages to. + * @param {String} [options.iosVersion] - The iOS version of the app so that ioslib picks the appropriate Xcode. + * @param {String} [options.minIosVersion] - The minimum iOS SDK to detect. + * @param {String} [options.minWatchosVersion] - The minimum watchOS SDK to detect. + * @param {String|Array} [options.searchPath] - One or more path to scan for Xcode installations. + * @param {String|SimHandle} [options.simHandleOrUDID] - A iOS sim handle or the UDID of the iOS Simulator to launch or null if you want ioslib to pick one. + * @param {String} [options.simType=iphone] - The type of simulator to launch. Must be either "iphone" or "ipad". Only applicable when udid is not specified. + * @param {String} [options.simVersion] - The iOS version to boot. Defaults to the most recent version. + * @param {String} [options.supportedVersions] - A string with a version number or range to check if an Xcode install is supported. + * @param {Boolean} [options.watchAppBeingInstalled] - The id of the watch app. Required in order to find a watch simulator. + * @param {String} [options.watchHandleOrUDID] - A watch sim handle or UDID of the Watch Simulator to launch or null if your app has a watch app and you want ioslib to pick one. + * @param {String} [options.watchMinOSVersion] - The min Watch OS version supported by the specified watch app id. + * @param {Function} callback(err, simHandle, watchSimHandle, selectedXcode, simInfo, xcodeInfo) - A function to call with the simulators found. + */ + ioslib.simulator.findSimulators = (options, callback) => { + if (typeof options === 'function') { + callback = options; + options = {}; + } else if (typeof options !== 'object') { + options = {}; + } + if (typeof callback !== 'function') { + callback = () => {}; + } + + const logger = typeof options.logger === 'function' ? options.logger : () => {}; + logger('Running patched legacy ioslib findSimulators()'); + + ioslib.detect(options, (err, results) => { + if (err) { + return callback(err); + } + + const simInfo = { simulators: results.simulators }; + const xcodeInfo = { xcode: results.xcode }; + + const compareXcodes = (a, b) => { + var v1 = xcodeInfo.xcode[a].version; + var v2 = xcodeInfo.xcode[b].version; + if (options.iosVersion && version.eq(options.iosVersion, v1)) { + return -1; + } + if (options.iosVersion && version.eq(options.iosVersion, v2)) { + return 1; + } + if (xcodeInfo.xcode[a].selected) { + return -1; + } + if (xcodeInfo.xcode[b].selected) { + return 1; + } + return version.gt(v1, v2) ? -1 : version.eq(v1, v2) ? 0 : 1; + }; + + const compareSims = (a, b) => { + return a.model < b.model ? -1 : a.model > b.model ? 1 : 0; + }; + + // find an Xcode installation that matches the iOS SDK or fall back to the selected Xcode or the latest + const xcodeIds = Object + .keys(xcodeInfo.xcode) + .filter(id => { + if (!xcodeInfo.xcode[id].supported) { + return false; + } + if (!options.iosVersion) { + return true; + } + return xcodeInfo.xcode[id].sdks.some(ver => version.eq(ver, options.iosVersion)); + }) + .sort(compareXcodes); + + if (!xcodeIds.length) { + if (options.iosVersion) { + return callback(new Error(`Unable to find any Xcode installations that supports iOS SDK ${options.iosVersion}.`)); + } else { + return callback(new Error('Unable to find any supported Xcode installations. Please install the latest Xcode.')); + } + } + + const xcodeId = xcodeIds[0]; + let selectedXcode = xcodeInfo.xcode[xcodeId]; + let simHandle = options.simHandleOrUDID instanceof SimHandle ? options.simHandleOrUDID : null; + let watchSimHandle = options.watchHandleOrUDID instanceof SimHandle ? options.watchHandleOrUDID : null; + const findWatchSimHandle = watchUDID => { + const sim = Object.values(simInfo.simulators.watchos).find(({ udid }) => udid === watchUDID); + if (sim) { + logger(`Found Watch Simulator UDID ${watchUDID}`); + return new SimHandle(sim); + } + }; + + if (options.simHandleOrUDID) { + // validate the udid + if (!(options.simHandleOrUDID instanceof SimHandle)) { + const vers = Object.keys(simInfo.simulators.ios); + + logger(`Validating iOS Simulator UDID ${options.simHandleOrUDID}`); + + for (let i = 0, l = vers.length; !simHandle && i < l; i++) { + const sims = simInfo.simulators.ios[vers[i]]; + for (var j = 0, k = sims.length; j < k; j++) { + if (sims[j].udid === options.simHandleOrUDID) { + logger(`Found iOS Simulator UDID ${options.simHandleOrUDID}`); + simHandle = new SimHandle(sims[j]); + break; + } + } + } + + if (!simHandle) { + return callback(new Error(`Unable to find an iOS Simulator with the UDID "${options.simHandleOrUDID}".`)); + } + } + + if (options.minIosVersion && version.lt(simHandle.version, options.minIosVersion)) { + return callback(new Error(`The selected iOS ${simHandle.version} Simulator is less than the minimum iOS version ${options.minIosVersion}.`)); + } + + if (options.watchAppBeingInstalled) { + const watchXcodeId = Object + .keys(simHandle.watchCompanion) + .filter(xcodeId => xcodeInfo.xcode[xcodeId].supported) + .sort(compareXcodes) + .pop(); + + if (!watchXcodeId) { + return callback(new Error(`Unable to find any Watch Simulators that can be paired with the specified iOS Simulator ${simHandle.udid}.`)); + } + + if (!options.watchHandleOrUDID) { + logger('Watch app present, autoselecting a Watch Simulator'); + + const companions = simHandle.watchCompanion[watchXcodeId]; + const companionUDID = Object.keys(companions) + .sort((a, b) => companions[a].model.localeCompare(companions[b].model)) + .pop(); + + watchSimHandle = new SimHandle(companions[companionUDID]); + + if (!watchSimHandle) { + return callback(new Error(`Specified iOS Simulator "${options.simHandleOrUDID}" does not support Watch apps.`)); + } + } else if (!(options.watchHandleOrUDID instanceof SimHandle)) { + logger(`Watch app present, validating Watch Simulator UDID ${options.watchHandleOrUDID}`); + watchSimHandle = findWatchSimHandle(options.watchHandleOrUDID); + if (!watchSimHandle) { + return callback(new Error(`Unable to find a Watch Simulator with the UDID "${options.watchHandleOrUDID}".`)); + } + } + + // double check + if (watchSimHandle && !simHandle.watchCompanion[watchXcodeId][watchSimHandle.udid]) { + return callback(new Error(`Specified Watch Simulator "${watchSimHandle.udid}" is not compatible with iOS Simulator "${simHandle.udid}".`)); + } + } + + if (options.watchAppBeingInstalled && !options.watchHandleOrUDID && !watchSimHandle) { + if (options.watchMinOSVersion) { + return callback(new Error(`Unable to find a Watch Simulator that supports watchOS ${options.watchMinOSVersion}.`)); + } + return callback(new Error('Unable to find a Watch Simulator.')); + } + + logger(`Selected iOS Simulator: ${simHandle.name}`); + logger(` UDID = ${simHandle.udid}`); + logger(` iOS = ${simHandle.version}`); + if (watchSimHandle) { + if (options.watchAppBeingInstalled && options.watchHandleOrUDID) { + logger(`Selected watchOS Simulator: ${watchSimHandle.name}`); + } else { + logger(`Autoselected watchOS Simulator: ${watchSimHandle.name}`); + } + logger(` UDID = ${watchSimHandle.udid}`); + logger(` watchOS = ${watchSimHandle.version}`); + } + logger(`Autoselected Xcode: ${selectedXcode.version}`); + } else { + logger('No iOS Simulator UDID specified, searching for best match'); + + if (options.watchAppBeingInstalled && options.watchHandleOrUDID) { + logger(`Validating Watch Simulator UDID ${options.watchHandleOrUDID}`); + watchSimHandle = findWatchSimHandle(options.watchHandleOrUDID); + if (!watchSimHandle) { + return callback(new Error(`Unable to find a Watch Simulator with the UDID "${options.watchHandleOrUDID}".`)); + } + } + + // pick one + logger(`Scanning Xcodes: ${xcodeIds.join(' ')}`); + + // loop through xcodes + for (let i = 0; !simHandle && i < xcodeIds.length; i++) { + const xc = xcodeInfo.xcode[xcodeIds[i]]; + const simVersMap = {}; + for (const ver of Object.keys(simInfo.simulators.ios)) { + for (const iosRange of Object.keys(xc.simDevicePairs)) { + if (version.satisfies(ver, iosRange)) { + simVersMap[ver] = xc.simDevicePairs[iosRange]; + break; + } + } + } + const simVers = Object.keys(simVersMap).sort(version.rcompare); + + logger(`Scanning Xcode ${xcodeIds[i]} sims: ${simVers.join(', ')}`); + + // loop through each xcode simulators + for (let j = 0; !simHandle && j < simVers.length; j++) { + if (!options.minIosVersion || version.gte(simVers[j], options.minIosVersion)) { + const sims = simInfo.simulators.ios[simVers[j]]; + + sims.sort(compareSims).reverse(); + + // loop through each simulator + for (let k = 0; !simHandle && k < sims.length; k++) { + if (options.simType && sims[k].family !== options.simType) { + continue; + } + + // if we're installing a watch extension, make sure we pick a simulator that supports the watch + if (options.watchAppBeingInstalled) { + if (watchSimHandle) { + for (const xcodeVer of Object.keys(sims[k].supportsWatch)) { + if (watchSimHandle.supportsXcode[xcodeVer]) { + selectedXcode = xcodeInfo.xcode[xcodeVer]; + simHandle = new SimHandle(sims[k]); + break; + } + } + } else if (sims[k].supportsWatch[xcodeIds[i]]) { + // make sure this version of Xcode has a watch simulator that supports the watch app version + for (const watchosVer of Object.keys(simInfo.simulators.watchos)) { + for (const watchosRange of Object.keys(simVersMap[simVers[j]])) { // 4.x, 5.x, etc + if (version.satisfies(watchosVer, watchosRange) && version.gte(watchosVer, options.watchMinOSVersion)) { + simHandle = new SimHandle(sims[k]); + selectedXcode = xcodeInfo.xcode[xcodeIds[i]]; + const watchSim = simInfo.simulators.watchos[watchosVer].sort(compareSims).reverse()[0]; + watchSimHandle = new SimHandle(watchSim); + break; + } + } + if (watchSimHandle) { + break; + } + } + } + } else { + // no watch app + logger('No watch app being installed, so picking first Simulator'); + simHandle = new SimHandle(sims[k]); + + // fallback to the newest supported Xcode version + for (const id of xcodeIds) { + if (simHandle.supportsXcode[id]) { + selectedXcode = xcodeInfo.xcode[id]; + break; + } + } + } + } + } + } + } + + if (!simHandle) { + // user experience! + if (options.simVersion) { + return callback(new Error(`Unable to find an iOS Simulator running iOS ${options.simVersion}`)); + } else { + return callback(new Error('Unable to find an iOS Simulator.')); + } + } else if (options.watchAppBeingInstalled && !watchSimHandle) { + return callback(new Error(`Unable to find a watchOS Simulator that supports watchOS ${options.watchMinOSVersion}.`)); + } + + logger(`Autoselected iOS Simulator: ${simHandle.name}`); + logger(` UDID = ${simHandle.udid}`); + logger(` iOS = ${simHandle.version}`); + if (watchSimHandle) { + if (options.watchAppBeingInstalled && options.watchHandleOrUDID) { + logger(`Selected watchOS Simulator: ${watchSimHandle.name}`); + } else { + logger(`Autoselected watchOS Simulator: ${watchSimHandle.name}`); + } + logger(` UDID = ${watchSimHandle.udid}`); + logger(` watchOS = ${watchSimHandle.version}`); + } + logger(`Autoselected Xcode: ${selectedXcode.version}`); + } + + callback(null, simHandle, watchSimHandle, selectedXcode, simInfo, xcodeInfo); + }); + }; + + return ioslib; +} + +function processCerts(info, results) { + const keychains = {}; + results.certs = { keychains, wwdr: info.certs.wwdr }; + + for (const type of [ 'developer', 'distribution' ]) { + for (const cert of info.certs[type]) { + if (!keychains[cert.keychain]) { + keychains[cert.keychain] = { + developer: [], + distribution: [] + }; + } + keychains[cert.keychain][type].push(cert); + cert.pem = cert.cert; + delete cert.cert; + } + } + + if (!results.certs.wwdr) { + results.issues.push({ + id: 'IOS_NO_WWDR_CERT_FOUND', + type: 'error', + message: 'Apple’s World Wide Developer Relations (WWDR) intermediate certificate is not installed.\nThis will prevent you from building apps for iOS devices or package for distribution.' + }); + } + + if (!Object.keys(keychains).length) { + results.issues.push({ + id: 'IOS_NO_KEYCHAINS_FOUND', + type: 'warning', + message: 'Unable to find any keychains found.' + }); + } + + let validDevCerts = 0; + let validDistCerts = 0; + + for (const keychain of Object.keys(keychains)) { + validDevCerts += (results.certs.keychains[keychain].developer || []).filter(c => !c.invalid).length; + validDistCerts += (results.certs.keychains[keychain].distribution || []).filter(c => !c.invalid).length; + } + + if (!validDevCerts) { + results.issues.push({ + id: 'IOS_NO_VALID_DEV_CERTS_FOUND', + type: 'warning', + message: 'Unable to find any valid iOS developer certificates.\nThis will prevent you from building apps for iOS devices.' + }); + } + + if (!validDistCerts) { + results.issues.push({ + id: 'IOS_NO_VALID_DIST_CERTS_FOUND', + type: 'warning', + message: 'Unable to find any valid iOS production distribution certificates.\nThis will prevent you from packaging apps for distribution.' + }); + } +} + +function processProvisioning(info, results) { + results.provisioning = {}; + for (const [ type, profiles ] of Object.entries(info.provisioning)) { + results.provisioning[type] = profiles.map(p => { + p.appId = (p.entitlements['application-identifier'] || '').replace(/^\w+\./, ''); + p.appPrefix = p.teamId; + p.certs = Object.values(p.certs); + p.getTaskAllow = !!p.entitlements['get-task-allow']; + p.team = p.teamIds; + return p; + }); + } + + const valid = { + development: 0, + adhoc: 0, + enterprise: 0, + distribution: 0 + }; + + for (const type of Object.keys(results.provisioning)) { + for (const profile of Object.keys(results.provisioning[type])) { + if (!profile.expired) { + valid[type]++; + } + } + } + + if (!results.provisioning.development.length || !valid.development) { + results.issues.push({ + id: 'IOS_NO_VALID_DEVELOPMENT_PROVISIONING_PROFILES', + type: 'warning', + message: 'Unable to find any valid iOS development provisioning profiles.\nThis will prevent you from building apps for testing on iOS devices.' + }); + } + + if (!results.provisioning.adhoc.length || !valid.adhoc) { + results.issues.push({ + id: 'IOS_NO_VALID_ADHOC_PROVISIONING_PROFILES', + type: 'warning', + message: 'Unable to find any valid iOS adhoc provisioning profiles.\nThis will prevent you from packaging apps for adhoc distribution.' + }); + } + + if (!results.provisioning.distribution.length || !valid.distribution) { + results.issues.push({ + id: 'IOS_NO_VALID_DISTRIBUTION_PROVISIONING_PROFILES', + type: 'warning', + message: 'Unable to find any valid iOS distribution provisioning profiles.\nThis will prevent you from packaging apps for AppStore distribution.' + }); + } +} + +function processXcodes(info, results, opts) { + results.xcode = info.xcode; + + const eulaNotAccepted = []; + const { minIosVersion, supportedVersions } = opts; + const xcodes = Object.entries(info.xcode); + let validXcodes = 0; + let sdkCounter = 0; + + results.iosSDKtoXcode = {}; + + if (xcodes.length) { + for (const [ xcodeId, xcode ] of xcodes) { + if (xcode.default) { + results.selectedXcode = xcode; + } + + xcode.supported = supportedVersions ? version.satisfies(xcode.version, supportedVersions) : true; + if (xcode.supported) { + validXcodes++; + } else { + const min = version.parseMin(supportedVersions); + results.issues.push({ + id: 'IOS_XCODE_TOO_OLD', + type: 'warning', + message: `Xcode ${xcode.version} is too old and is no longer supported.\nThe minimum supported Xcode version is Xcode ${min}.`, + xcodeVer: xcode.version, + minSupportedVer: min + }); + } + + if (!xcode.eulaAccepted) { + eulaNotAccepted.push(xcode); + } + + if (minIosVersion) { + xcode.sdks.ios = xcode.sdks.ios.filter(ver => version.gte(ver, minIosVersion)); + } + + xcode.sdks = xcode.sdks.ios; + + for (const sdk of xcode.sdks) { + if (xcode.default || !results.iosSDKtoXcode[sdk]) { + results.iosSDKtoXcode[sdk] = xcodeId; + } + } + + xcode.sims = Object.values(xcode.simRuntimes).map(r => r.version); + + sdkCounter += xcode.sdks.length; + } + + if (eulaNotAccepted.length) { + results.issues.push({ + id: 'IOS_XCODE_EULA_NOT_ACCEPTED', + type: 'warning', + message: eulaNotAccepted.length === 1 + ? 'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.' + : `Multiple Xcode versions have not had their EULA accepted:\n${eulaNotAccepted.map(xc => ` ${xc.version} (${xc.xcodeapp})`).join('\n')}\nLaunch each Xcode and accept the license.` + }); + } + + if (supportedVersions && !validXcodes) { + results.issues.push({ + id: 'IOS_NO_SUPPORTED_XCODE_FOUND', + type: 'warning', + message: 'There are no supported Xcode installations found.' + }); + } + + if (!sdkCounter) { + results.issues.push({ + id: 'IOS_NO_IOS_SDKS', + type: 'error', + message: 'There are no iOS SDKs found\nLaunch Xcode and download the mobile support packages.' + }); + } + } else { + results.issues.push({ + id: 'IOS_XCODE_NOT_INSTALLED', + type: 'error', + message: 'No Xcode installations found.\nYou can download it from the App Store or from https://developer.apple.com/xcode/.' + }); + } +} + +function processSimulators(simulators) { + for (const vers of Object.values(simulators)) { + for (const sims of Object.values(vers)) { + for (const sim of sims) { + sim.dataDir = path.join(sim.deviceDir, 'data'); + } + } + } + return simulators; +} diff --git a/src/legacy/ti/cli.js b/src/legacy/ti/cli.js new file mode 100644 index 0000000..385f2c2 --- /dev/null +++ b/src/legacy/ti/cli.js @@ -0,0 +1,766 @@ +/* eslint-disable promise/no-callback-in-promise, security/detect-non-literal-require */ + +import Context from './context'; +import fs from 'fs-extra'; +import getOSInfo from '../../lib/os'; +import Module from 'module'; +import path from 'path'; +import tunnel from '../tunnel'; +import * as version from '../../lib/version'; +import * as request from '@axway/amplify-request'; +import { CLI_VERSION } from './version'; +import { expandPath } from 'appcd-path'; +import { format } from 'util'; +import { debounce, get, mergeDeep, set, unique } from 'appcd-util'; +import { sdk } from 'titaniumlib'; +import { snooplogg } from 'cli-kit'; + +const { gray, green, highlight, magenta, note, red, yellow } = snooplogg.styles; + +/** + * The Titanium CLI v5 requires the `--sdk ` to equal the `` in the + * tiapp.xml. If they don't match, node-titanium-sdk's `ti.validateCorrectSDK()` will spawn a new + * Titanium CLI process with the correct `--sdk`. Due to the design of the Titanium CLI, this + * `GracefullyShutdown` error was thrown as an easy way to stop validating and skip executing the + * command. + * + * Since this Titanium CLI shim will ALWAYS match the `` in the tiapp.xml, this really + * isn't used, but just in case, we'll define it and set it on the `CLI` instance. + */ +class GracefulShutdown extends Error {} + +/** + * Controls the state and flow for running legacy Titanium SDK CLI commands such as `build` and + * `clean`. + */ +export default class CLI { + /** + * The hook priority used to sort hook callbacks. + * @type {Number} + */ + static HOOK_PRIORITY_DEFAULT = 1000; + + /** + * The legacy Titanium CLI version. + * @type {String} + */ + version = CLI_VERSION; + + /** + * A map of command names to command descriptors. + * @type {Object} + */ + cmds = {}; + + /** + * Export of the graceful shutdown error. + * @type {Function} + */ + GracefulShutdown = GracefulShutdown; + + /** + * The hook system state. + * @type {Object} + */ + hooks = { + erroredFilenames: [], + errors: {}, + ids: {}, + incompatibleFilenames: [], + loadedFilenames: [], + post: {}, + pre: {}, + scannedPaths: {} + }; + + /** + * The legacy Titanium CLI logger object. The original supports log levels, this one does not. + * @type {Object} + */ + logger = { + debug: (msg = '', ...args) => console.log(`${magenta('[DEBUG]')} ${format(msg, ...args)}`), + error: (msg = '', ...args) => console.error(red(`[ERROR] ${format(msg, ...args)}`)), + info: (msg = '', ...args) => console.info(`${green('[INFO]')} ${format(msg, ...args)}`), + log: (msg = '', ...args) => console.log(format(msg, ...args)), + trace: (msg = '', ...args) => console.log(`${gray('[TRACE]')} ${format(msg, ...args)}`), + warn: (msg = '', ...args) => console.warn(yellow(`[WARN] ${format(msg, ...args)}`)), + + levels: { + trace: {}, + debug: {}, + info: {}, + warn: {}, + error: {} + }, + + banner() { + // noop + }, + + getLevels() { + return Object.keys(this.levels); + }, + + setLevel() { + // noop + } + }; + + /** + * The time that executing the command starts. This value is set after validation and prompting + * has occurred. + * @type {Number} + */ + startTime = null; + + /** + * Initializes the CLI state by validating the Titanium SDK, initializing the config, and + * detecting plugins. + * + * @param {Object} opts - Various options. + * @param {Object} opts.argv - The command arguments. + * @param {String} opts.command - The name of the command to execute. + * @param {Object} [opts.config] - User-defined Titanium CLI config settings from appcd's user + * config. + * @param {Object} [opts.network] - Network configuration settings including `caFile`, + * `certFile`, `keyFile`, `proxy`, and `strictSSL`. + * @param {Boolean} [opts.promptingEnabled] - When `true`, invalid and missing values will be + * prompted for. + * @param {String} opts.sdkPath - The path to the Titanium SDK. + * @access public + */ + constructor(opts) { + if (!opts || typeof opts !== 'object') { + throw new TypeError('Expected options to be an object'); + } + + this.config = this.initConfig(opts.config); + this.fingerprint = opts.fingerprint; + this.promptingEnabled = !!opts.promptingEnabled; + this.sdk = new sdk.TitaniumSDK(opts.sdkPath); + + this.got = request.init({ defaults: opts.network }); + + // initialize the CLI argument values + this.argv = { + $command: opts.command, + }; + if (opts.argv) { + for (const [ key, value ] of Object.entries(opts.argv)) { + this.argv[key] = this.argv[key.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`)] = value; + } + } + + // initialize the legacy environment info + const installPath = path.resolve(this.sdk.path, '..', '..', '..'); + this.env = { + // used by the Titanium SDK commands to display the system info in the output before + // the command is executed + getOSInfo: cb => { + const info = getOSInfo(); + cb({ + os: info.name, + osver: info.version, + ostype: info.arch, + oscpu: info.numcpus, + memory: info.memory, + node: process.versions.node, + npm: 'n/a' // unimportant + }); + }, + + // used by node-titanium-sdk's `loadPlugins()` to scan the global Titanium SDK install + // path for Titanium CLI plugins that are explicitly enabled in the app's `tiapp.xml`, + // which is probably not used anymore + installPath, + + os: { + sdkPaths: [ installPath ] + }, + + sdks: { + [this.sdk.manifest.version]: this.sdk + } + }; + + // patch modules + const load = Module._load; + const patchDir = path.resolve(__dirname, '..', 'patch'); + const lookup = { + fields: path.join(patchDir, 'fields.js'), + ioslib: path.join(patchDir, 'ios.js'), + 'node-titanium-sdk/lib/android': path.join(patchDir, 'android.js') + }; + Module._load = (request, parent, isMain) => { + if (lookup[request] && parent && path.basename(parent.filename) === '_build.js') { + return require(lookup[request]).patch({ load, request, parent, isMain }); + } + return load(request, parent, isMain); + }; + + // initialize the commands + const cmd = opts.command; + if (cmd !== 'build' && cmd !== 'clean') { + throw new Error(`Invalid command "${cmd}"`); + } + this.command = this.cmds[cmd] = new Context({ + cli: this, + name: cmd, + path: path.join(this.sdk.path, 'cli', 'commands', `${cmd}.js`) + }); + + // initialize the hooks + unique(this.config.paths.hooks).forEach(this.scanHooks.bind(this)); + this.scanHooks(path.resolve(__dirname, '..', 'hooks')); + this.scanHooks(path.join(this.sdk.path, 'cli', 'hooks')); + } + + /** + * Logs a telemetry event. + * + * @param {String} event - The name of the event. + * @param {Object} data - An object containing any data associated with the event. + * @param {String} type - The event type, however this is only ever passed in for the + * `ti.apiusage` event, which coincidentally has had the incorrect event name since 2014. + * @access public + */ + addAnalyticsEvent(event, data, type) { + tunnel.telemetry({ event: type === 'ti.apiusage' ? type : event, ...data }); + } + + /** + * An alias for `on()`. + * + * @param {*} ...args - An event name and callback. + * @returns {CLI} + * @access public + */ + addHook(...args) { + return this.on(...args); + } + + /** + * Prompt for a question. + * + * @param {Object} question - The question parameters. + * @returns {Promise} + * @access public + */ + async ask(question) { + // copy the question and remove the error message + question = { required: true, ...question }; + delete question.error; + + if (this.promptingEnabled) { + return await tunnel.ask(question); + } + + const err = new Error(question.error); + err.prompt = question; + throw err; + } + + /** + * Defines a hook function that will emit an event before and after the hooked function is + * invoked. + * + * @param {String} name - The name of hook event. + * @param {Object} [ctx] - The `this` context to bind the callbacks to. + * @param {Function} [fn] - The function being hooked. + * @returns {Function} + * @access public + */ + createHook(name, ctx, fn) { + let dataPayload = {}; + + if (typeof ctx === 'function') { + fn = ctx; + ctx = null; + } else if (ctx && typeof ctx === 'object' && !fn) { + dataPayload = ctx; + ctx = null; + } + + return (...args) => { + let data = Object.assign({}, dataPayload, { + type: name, + args, + fn: fn, + ctx: ctx + }); + const callback = data.args.pop(); + const pres = this.hooks.pre[name] || []; + const posts = this.hooks.post[name] || []; + + (async () => { + // call all pre filters + await pres + // eslint-disable-next-line promise/no-nesting + .reduce((promise, pre) => promise.then(async () => { + if (pre.length >= 2) { + await new Promise((resolve, reject) => { + pre.call(ctx, data, (err, newData) => { + if (err) { + return reject(err); + } else if (newData) { + data = newData; + } + resolve(); + }); + }); + } else { + await pre.call(ctx, data); + } + }), Promise.resolve()); + + if (data.fn) { + data.result = await new Promise(resolve => { + // call the function + data.args.push((...args) => resolve(args)); + data.fn.apply(data.ctx, data.args); + }); + } + + // call all post filters + await posts + // eslint-disable-next-line promise/no-nesting + .reduce((promise, post) => promise.then(async () => { + if (post.length >= 2) { + await new Promise((resolve, reject) => { + post.call(ctx, data, (err, newData) => { + if (err) { + return reject(err); + } + if (newData && typeof newData === 'object' && newData.type) { + data = newData; + } + resolve(); + }); + }); + } else { + await post.call(ctx, data); + } + }), Promise.resolve()); + + if (typeof callback === 'function') { + callback.apply(data, data.result); + } + })().catch(err => { + // this is the primary error handler + if (typeof callback === 'function') { + tunnel.log(err.stack); + callback(err); + } else { + this.logger.error('Hook completion callback threw unhandled error:'); + this.logger.error(err.stack); + process.exit(1); + } + }); + }; + } + + /** + * Emits an event along with a data payload. + * + * @param {String|Array.} name - One or more events to emit. + * @param {Object} [data] - An optional data payload. + * @param {Function} [callback] A function to call once the emitting has finished. If no + * callback is specified, this function will return a promise instead. + * @returns {CLI|Promise} + * @access public + */ + emit(name, data, callback) { + if (typeof data === 'function') { + callback = data; + data = null; + } + + // create each hook and immediately fire them + const promise = unique(Array.isArray(name) ? name : [ name ]) + .reduce((promise, name) => promise.then(() => new Promise((resolve, reject) => { + const hook = this.createHook(name, data); + hook((err, result) => { + err ? reject(err) : resolve(result); + }); + })), Promise.resolve(this)); + + if (typeof callback !== 'function') { + return promise; + } + + promise + .then(result => callback(null, result)) + .catch(callback); + + return this; + } + + /** + * Executes the command's `run()` method. + * + * @returns {Promise} + * @access private + */ + async executeCommand() { + await this.emit('cli:pre-execute', { cli: this, command: this.command }); + + this.startTime = Date.now(); + + const { run } = this.command.module; + let done = 0; + + await new Promise((resolve, reject) => { + this.logger.trace(`Executing ${highlight(this.command.name)}`); + + run(this.logger, this.config, this, async (err, result) => { + if (done++) { + // guard against callback being fired more than once + return; + } + + // we need to wrap the post-execute emit in a try/catch so that any exceptions + // it throws aren't confused with command errors + try { + await this.emit('cli:post-execute', { cli: this, command: this.command, err, result }); + } catch (ex) { + return reject(ex); + } + + if (err) { + return reject(err); + } + + resolve(); + }); + + // if there's no callback in the run signature (e.g. the "clean" command), then we wait + // 100ms after the last bit of output activity through the IPC tunnel + if (run.length < 4) { + this.logger.debug(`Command "${this.command.name}" does NOT have a finished callback!`); + const fn = debounce(resolve, 100); + fn(); // start the bounce + tunnel.on('tick', fn); + } + }); + } + + /** + * An alias for `emit()`. + * + * @param {*} ...args - The hook names, data, and callback. + * @returns {CLI} + * @access public + */ + fireHook(...args) { + return this.emit(...args); + } + + /** + * The main pipeline for running the CLI. + * + * @returns {Promise} + * @access public + */ + async go() { + await this.emit('cli:go', { cli: this }); + await this.command.load(true); + await this.emit('cli:command-loaded', { cli: this, command: this.command }); + await this.validate(); + await this.executeCommand(); + } + + /** + * Creates a legacy Titanium CLI config object from the Titanium plugin config. + * + * @param {Object} config - The Titanium plugin config. + * @returns {Object} + * @access private + */ + initConfig(config) { + return Object.defineProperties( + mergeDeep({ + app: { + workspace: '' + }, + + cli: { + colors: true, + completion: false, + logLevel: 'trace', + prompt: true, + progressBars: true, + failOnWrongSDK: false, + httpProxyServer: '', + rejectUnauthorized: true, + width: 100, + ignoreDirs: '^(\\.svn|_svn|\\.git|\\.hg|\\.?[Cc][Vv][Ss]|\\.bzr|\\$RECYCLE\\.BIN)$', + ignoreFiles: '^(\\.gitignore|\\.npmignore|\\.cvsignore|\\.DS_Store|\\._.*|[Tt]humbs.db|\\.vspscc|\\.vssscc|\\.sublime-project|\\.sublime-workspace|\\.project|\\.tmproj)$' + }, + + // additional search paths for commands and hooks + paths: { + commands: [], + hooks: [], + modules: [], + plugins: [], + sdks: [], + templates: [] + }, + + user: { + locale: 'en_US' + } + }, config), + { + get: { + value(key, defaultValue) { + return get(this, key, defaultValue); + } + }, + + // called by Android build to set the `android.sdkPath` + set: { + value(key, value) { + set(this, key, value); + } + } + } + ); + } + + /** + * Registers an event callback. + * + * @param {String} name - The name of the event. + * @param {Function} callback - The listener to register. + * @returns {CLI} + * @access public + */ + on(name, callback) { + let priority = CLI.HOOK_PRIORITY_DEFAULT; + let i; + + if (typeof callback === 'function') { + callback = { post: callback }; + } else if (callback && typeof callback === 'object') { + priority = parseInt(callback.priority) || priority; + } + + if (callback.pre) { + const h = this.hooks.pre[name] || (this.hooks.pre[name] = []); + callback.pre.priority = priority; + // eslint-disable-next-line no-empty + for (i = 0; i < h.length && priority >= h[i].priority; i++) {} + h.splice(i, 0, callback.pre); + } + + if (callback.post) { + const h = this.hooks.post[name] || (this.hooks.post[name] = []); + callback.post.priority = priority; + // eslint-disable-next-line no-empty + for (i = 0; i < h.length && priority >= h[i].priority; i++) {} + h.splice(i, 0, callback.post); + } + + return this; + } + + /** + * Searches the specified directory for Titanium CLI plugin files. + * + * @param {String} dir - The directory to scan. + * @access public + */ + scanHooks(dir) { + dir = expandPath(dir); + this.logger.trace(`Scanning hooks: ${highlight(dir)}`); + + if (this.hooks.scannedPaths[dir]) { + return; + } + + try { + const jsfile = /\.js$/; + const ignore = /^[._]/; + const files = fs.statSync(dir).isDirectory() ? fs.readdirSync(dir).map(n => path.join(dir, n)) : [ dir ]; + let appc; + + for (const file of files) { + try { + if (fs.statSync(file).isFile() && jsfile.test(file) && !ignore.test(path.basename(path.dirname(file)))) { + const startTime = Date.now(); + const mod = require(file); + if (mod.id) { + if (!Array.isArray(this.hooks.ids[mod.id])) { + this.hooks.ids[mod.id] = []; + } + this.hooks.ids[mod.id].push({ + file: file, + version: mod.version || null + }); + + // don't load duplicate ids + if (this.hooks.ids[mod.id].length > 1) { + continue; + } + } + + if (!this.version || !mod.cliVersion || version.satisfies(this.version, mod.cliVersion)) { + if (!appc) { + appc = require(path.join(this.sdk.path, 'node_modules', 'node-appc')); + } + mod.init && mod.init(this.logger, this.config, this, appc); + this.hooks.loadedFilenames.push(file); + this.logger.trace(`Loaded CLI hook: ${highlight(file)} ${note(`(${Date.now() - startTime} ms)`)}`); + } else { + this.hooks.incompatibleFilenames.push(file); + } + } + } catch (ex) { + this.logger.trace(`Error loading hook: ${highlight(file)}`); + this.logger.trace(ex.stack); + this.hooks.erroredFilenames.push(file); + this.hooks.errors[file] = ex; + } + } + } catch (err) { + if (err.code !== 'ENOENT') { + this.logger.trace(`Error scanning hooks: ${highlight(dir)}`); + this.logger.trace(err.stack); + } + } + } + + /** + * Validates the arguments. First it checks against the built-in naive validation such as + * required or against a list of values. Next it calls each option's validator. After that it + * calls the command's validator. Lastly it calls each option's value callback. + * + * @returns {Promise} + * @access private + */ + async validate() { + await this.emit('cli:pre-validate', { cli: this, command: this.command }); + + // step 1: build a list of all options so we can sort them + const options = []; + for (const ctx of [ this.command, this.command?.platform ]) { + if (ctx?.conf.options) { + for (const [ name, opt ] of Object.entries(ctx.conf.options)) { + options.push({ + // this is a sacrificial wrapper that we can throw away after firing and it + // handles the boilerplate of checking the callback and result + callback(value) { + let result; + if (typeof opt.callback === 'function') { + // technically `opt.callback()` can throw a `GracefulShutdown` error + // for both `build` and `clean` commands during the `project-dir` + // callback if the `` in the tiapp.xml is not the same + // version loaded by the Titanium SDK, but luckily that will never :) + result = opt.callback(value || ''); + } + delete this.callback; + return result !== undefined ? result : value; + }, + name, + orig: opt, + values: !opt.skipValueCheck && Array.isArray(opt.values) ? opt.values : null + }); + } + } + } + + options.sort((a, b) => { + if (a.orig.order && b.orig.order) { + return a.orig.order - b.orig.order; + } + return a.orig.order ? -1 : b.orig.order ? 1 : 0; + }); + + const createQuestion = async (opt, error) => { + if (opt.values) { + return { + choices: opt.values.map(value => ({ value })), + error, + message: `Please select a valid ${opt.name}`, + name: opt.name, + type: 'select' + }; + } + + if (typeof opt.orig?.prompt === 'function') { + return await new Promise(opt.orig.prompt); + } + + return { + error, + message: `Please enter a valid ${opt.name}`, + name: opt.name, + type: 'text' + }; + }; + + // step 2: determine invalid or missing options + for (const opt of options) { + const { name, orig, values } = opt; + const value = this.argv[name]; + + if (value === undefined) { + // we need to check if the option is required + // sometimes required options such as `--device-id` allow an undefined value in the + // case when the value is derived by the config or is autoselected + if (orig.required && (typeof orig.verifyIfRequired !== 'function' || await new Promise(orig.verifyIfRequired))) { + const question = await createQuestion(opt, `Missing required option "${name}"`); + this.argv[name] = question.type === 'select' && question.choices.length === 1 ? question.choices[0].value : (await this.ask(question)); + } + } else if (values && !values.includes(value)) { + const question = await createQuestion(opt, `Invalid ${name} value "${value}"`); + this.argv[name] = question.type === 'select' && question.choices.length === 1 ? question.choices[0].value : (await this.ask(question)); + } else if (typeof orig.validate === 'function') { + this.argv[name] = await new Promise((resolve, reject) => { + orig.validate(value, async (err, adjustedValue) => { + if (err) { + this.logger.trace(`Validation failed for option ${name}: ${err.toString()}`); + try { + const question = await createQuestion(opt, `Invalid ${name} value "${value}"`); + adjustedValue = question.type === 'select' && question.choices.length === 1 ? question.choices[0].value : (await this.ask(question)); + } catch (e) { + return reject(e); + } + } + resolve(opt.callback(adjustedValue)); + }); + }); + } else { + this.argv[name] = opt.callback(value); + } + } + + // note that we don't care about missing arguments because `build` and `clean` commands + // don't have any arguments! + + // step 3: run the command's validate() function, if exists + + const { validate } = this.command.module; + if (validate && typeof validate === 'function') { + const fn = validate(this.logger, this.config, this); + + // fn should always be a function for `build` and `clean` commands + if (typeof fn === 'function') { + await new Promise(resolve => fn(resolve)); + } + } + + await this.emit('cli:post-validate', { cli: this, command: this.command }); + + // step 4: fire all option callbacks for any options we missed above + for (const opt of options) { + if (typeof opt.callback === 'function') { + const val = opt.callback(this.argv[opt.name] || ''); + if (val !== undefined) { + this.argv[opt.name] = val; + } + } + } + } +} diff --git a/src/legacy/ti/context.js b/src/legacy/ti/context.js new file mode 100644 index 0000000..8b9aab0 --- /dev/null +++ b/src/legacy/ti/context.js @@ -0,0 +1,136 @@ +import fs from 'fs-extra'; +import path from 'path'; +import * as version from '../../lib/version'; + +import { CLI_VERSION } from './version'; +import { snooplogg } from 'cli-kit'; + +const { highlight, note } = snooplogg.styles; + +/** + * Command specific data including the command module and configuration. + */ +export default class Context { + /** + * A legacy Titanium CLI version. + * @type {String} + */ + cliVersion = CLI_VERSION; + + /** + * A reference to the associated platform-specific context. This is only used for the `build` + * command. + * @type {Context} + */ + platform = null; + + /** + * Initializes the context and validates that the command JS file exists. + * + * @param {Object} opts - Various options. + * @param {CLI} opts.cli - A reference to the main CLI instance. + * @param {Object} [opts.conf] - The command's configuration. This is used when the `build` + * command initializes a platform-specific context. + * @param {String} opts.name - The command name. + * @param {Context} [opts.parent] - A reference to the parent context. This is used to + * associate a platform-specific context with the `build` command context. + * @param {String} opts.path - The path to the command JS file. + * @access public + */ + constructor({ cli, conf, name, parent, path }) { + this.cli = cli; + this.conf = conf || {}; + this.name = name; + this.parent = parent; + this.path = path; + } + + /** + * Loads a command JS file and if the command is the `build` command, it also initializes the + * platform-specific context. + * + * @param {Boolean} [checkPlatform] - When `true` and this is the `build` command, then + * validate the platform argument. + * @returns {Promise} + * @access public + */ + async load(checkPlatform) { + const startTime = Date.now(); + + // eslint-disable-next-line security/detect-non-literal-require + this.module = require(this.path); + + this.cli.logger.trace(`Loaded command file: ${highlight(this.path)} ${note(`(${Date.now() - startTime} ms)`)}`); + + if (this.module.cliVersion && !version.satisfies(this.cliVersion, this.module.cliVersion)) { + throw new Error(`Command "${this.name}" is incompatible with this version of the Titanium CLI`); + } + + if (typeof this.module.run !== 'function') { + throw new Error(`Command "${this.name}" does not contain a valid run function`); + } + + this.conf = typeof this.module.config === 'function' ? this.module.config(this.cli.logger, this.cli.config, this.cli) : {}; + if (typeof this.conf === 'function') { + this.conf = await new Promise(resolve => this.conf(r => resolve(r))); + } + + if (!checkPlatform || this.name !== 'build') { + return; + } + + // the `build` command `--platform` option was hard wired into the CLI context, so + // unfortunately we need to do a bunch of `build` specific logic to load the platform + // specific command + let { platform } = this.cli.argv; + const availablePlatforms = this.cli.sdk.manifest.platforms; + + // the platform should really be named 'ios', so just in case someday we fix that + if (platform === 'ios' && !availablePlatforms.includes('ios')) { + platform = 'iphone'; + } + + if (!platform) { + const choices = availablePlatforms.map(value => { + let message = value; + try { + const pkgJson = fs.readJsonSync(path.join(this.cli.sdk.path, value, 'package.json')); + message = pkgJson.title || value; + } catch (e) { + // squelch + } + return { message, value }; + }).sort((a, b) => a.message.localeCompare(b.message)); + + platform = await this.cli.ask({ + choices, + error: 'Missing required option "platform"', + message: 'For which platform do you want to build?', + name: 'platform', + type: 'select' + }); + } + + // `this.conf.platforms` is an object of platform names to platform-specific options + if (this.conf.platforms && Object.prototype.hasOwnProperty.call(this.conf.platforms, platform)) { + const platformConf = this.conf.platforms[platform]; + + this.platform = new Context({ + conf: platformConf, + name: platform, + parent: this, + path: path.join(this.cli.sdk.path, platform) + }); + + this.platforms = { + [this.platform.name]: this.platform + }; + + this.cli.argv.platform = this.platform.name; // I think this is to normalize `iphone` to `ios` + this.cli.argv.$platform = platform; + + // find all platform hooks + this.cli.scanHooks(path.join(this.cli.sdk.path, this.platform.name, 'cli', 'hooks')); + } + } +} diff --git a/src/legacy/ti/logger.js b/src/legacy/ti/logger.js new file mode 100644 index 0000000..e4a0b47 --- /dev/null +++ b/src/legacy/ti/logger.js @@ -0,0 +1,30 @@ +// const logger = { +// debug: console.log, +// error: console.log, +// info: console.log, +// log: console.log, +// trace: console.log, +// warn: console.log, + +// levels: { +// trace: {}, +// debug: {}, +// info: {}, +// warn: {}, +// error: {} +// }, + +// banner() { +// // noop +// }, + +// getLevels() { +// return Object.keys(this.levels); +// }, + +// setLevel() { +// // noop +// } +// }; + +// export default logger; diff --git a/src/legacy/ti/version.js b/src/legacy/ti/version.js new file mode 100644 index 0000000..4ac837d --- /dev/null +++ b/src/legacy/ti/version.js @@ -0,0 +1,5 @@ +/** + * The legacy Titanium CLI version. Since this legacy shim is intended to simulate the Titanium CLI + * v5, we keep the major as `5`, but set the minor to something high that will never exist. + */ +export const CLI_VERSION = '5.999.0'; diff --git a/src/legacy/tunnel.js b/src/legacy/tunnel.js new file mode 100644 index 0000000..9462ac9 --- /dev/null +++ b/src/legacy/tunnel.js @@ -0,0 +1,195 @@ +import CLI from './ti/cli'; +import { EventEmitter } from 'events'; +import { makeSerializable } from 'appcd-util'; +import { v4 as uuidv4 } from 'uuid'; + +/** + * A simple state manager for sending and receiving requests to the parent process. + * + * Note that this tunnel implementation does NOT support chunked/streamed responses. + */ +class Tunnel extends EventEmitter { + /** + * A map of all pending request ids and their associated promise callbacks. + * @type {Object} + */ + pending = {}; + + /** + * Wires up the IPC message handler. + * + * @access public + */ + constructor() { + super(); + process.on('message', data => this.onMessage(data)); + } + + /** + * Requests that the parent process prompts for specified question. + * + * @param {Object} question - The question to prompt for. + * @returns {Promise} + * @access public + */ + ask(question) { + return this.sendRequest({ + question, + type: 'prompt' + }); + } + + /** + * Makes a request to the parent process. + * + * @param {String} path - The path to request. + * @param {Object} [data] - An optional data payload to send with the request. + * @returns {Promise} + * @access public + */ + call(path, data) { + return this.sendRequest({ + data, + path, + type: 'call' + }); + } + + /** + * Retrieves the active account or authenticates if you're not logged in. + * + * @returns {Promise} Resolves the account info. + */ + async getAccount() { + if (!this._account) { + const { response: accounts } = await this.call('/amplify/2.x/auth'); + this._account = accounts.find(a => a.active) || accounts[0]; + } + if (!this._account) { + this._account = (await this.call('/amplify/2.x/auth/login')).response; + } + return this._account; + } + + /** + * Writes a message to the debug log. + * + * @param {...*} args - A message or data to log. + * @access public + */ + log(...args) { + this.emit('tick'); + if (process.connected) { + process.send({ + args: makeSerializable(args), + type: 'log' + }); + } + } + + /** + * Dispatches a message from the parent process. + * + * @param {Object} data - The message data. + * @access private + */ + async onMessage(data) { + const { id, type } = data; + + if (type === 'exec' || type === 'help') { + try { + const cli = new CLI(data); + + if (type === 'exec') { + await cli.go(); + } else { + await cli.command.load(); + process.send({ + data: makeSerializable(cli.command.conf), + type: 'json' + }); + } + + // the command is complete, but the IPC channel is still open, so we simply disconnect it and + // this process should exit whenever the command finishes + this.log('Disconnecting IPC tunnel from parent and letting process exit gracefully'); + process.disconnect(); + } catch (err) { + process.send(makeSerializable({ + ...err, + message: err.message || err, + stack: err.stack, + status: err.status || 500, + type: 'error' + })); + process.exit(1); + } + return; + } + + const req = id && this.pending[id]; + if (!req) { + return; + } + delete this.pending[id]; + + const { resolve, reject } = req; + + switch (type) { + case 'answer': + return resolve(data.answer); + + case 'error': + return reject(Object.defineProperties(new Error(data.error), { + code: { value: data.code }, + stack: { value: data.stack || 'Error originated in parent process and stack not available' } + })); + + case 'response': + return resolve(data.response); + } + } + + /** + * Initiates a request over the IPC tunnel to the parent. + * + * @param {Object} data - The request payload. + * @returns {Promise} + * @access private + */ + sendRequest(data) { + return new Promise((resolve, reject) => { + this.emit('tick'); + if (process.connected) { + data.id = uuidv4(); + this.pending[data.id] = { resolve, reject }; + process.send(makeSerializable(data)); + } else { + reject(new Error(`Can't send "${data.type}" message to parent because IPC channel has been closed`)); + } + }); + } + + /** + * Sends a telemetry event. + * + * @param {Object} payload - The telemetry payload including the `event` and data. + * @access public + */ + telemetry(payload) { + this.emit('tick'); + if (process.connected) { + process.send(makeSerializable({ + payload, + type: 'telemetry' + })); + } + } +} + +/** + * The global tunnel instance. It is global because `process` is global and there's no single place + * of instantiation. + * @type {Tunnel} + */ +export default new Tunnel(); diff --git a/src/lib/os.js b/src/lib/os.js new file mode 100644 index 0000000..644b754 --- /dev/null +++ b/src/lib/os.js @@ -0,0 +1,77 @@ +import fs from 'fs'; +import os from 'os'; + +import { arch } from 'appcd-util'; +import { isFile } from 'appcd-fs'; +import { spawnSync } from 'child_process'; + +/** + * Detects operating system information. + * + * @returns {Object} + */ +export default function getOSInfo() { + const info = { + platform: process.platform, + name: 'Unknown', + version: '', + arch: arch(), + numcpus: os.cpus().length, + memory: os.totalmem() + }; + + switch (process.platform) { + case 'darwin': + { + const stdout = spawnSync('sw_vers').stdout.toString(); + let m = stdout.match(/ProductName:\s+(.+)/i); + if (m) { + info.name = m[1]; + } + m = stdout.match(/ProductVersion:\s+(.+)/i); + if (m) { + info.version = m[1]; + } + } + break; + + case 'linux': + info.name = 'GNU/Linux'; + + if (isFile('/etc/lsb-release')) { + const contents = fs.readFileSync('/etc/lsb-release', 'utf8'); + let m = contents.match(/DISTRIB_DESCRIPTION=(.+)/i); + if (m) { + info.name = m[1].replace(/"/g, ''); + } + m = contents.match(/DISTRIB_RELEASE=(.+)/i); + if (m) { + info.version = m[1].replace(/"/g, ''); + } + } else if (isFile('/etc/system-release')) { + const parts = fs.readFileSync('/etc/system-release', 'utf8').split(' '); + if (parts[0]) { + info.name = parts[0]; + } + if (parts[2]) { + info.version = parts[2]; + } + } + break; + + case 'win32': + { + const stdout = spawnSync('wmic', [ 'os', 'get', 'Caption,Version' ]).stdout.toString(); + const s = stdout.split('\n')[1].split(/ {2,}/); + if (s.length > 0) { + info.name = s[0].trim() || 'Windows'; + } + if (s.length > 1) { + info.version = s[1].trim() || ''; + } + } + break; + } + + return info; +} diff --git a/src/lib/prompt.js b/src/lib/prompt.js new file mode 100644 index 0000000..2bec63d --- /dev/null +++ b/src/lib/prompt.js @@ -0,0 +1,134 @@ +import { DispatcherContext } from 'appcd-dispatcher'; +import { format } from 'util'; +import { prompt as enquire } from 'enquirer'; +import { Readable } from 'stream'; + +const { log } = appcd.logger('prompt'); +const { alert, highlight } = appcd.logger.styles; + +export class PromptError extends Error { + constructor(msg, ask) { + super(msg); + this.ask = ask; + } +} + +/** + * Prompts for a value with unified settings and improved style consistency. + * + * @param {Object|Array.} questions - A question or list of questions to prompt for. + * @param {Object} [terminal] - An object containing a `stdin` and `stdout` such as a cli-kit + * `Terminal` instance. + * @returns {Promise} + */ +export function prompt(questions, { stdin, stdout } = {}) { + if (!Array.isArray(questions)) { + questions = [ questions ]; + } + + log(`Prompting with terminal size ${highlight(stdout.columns)} x ${highlight(stdout.rows)}`); + + for (let i = 0, len = questions.length; i < len; i++) { + questions[i] = { + format: questions[i].type === 'toggle' ? undefined : function () { + // for some reason, enquirer doesn't print the selected value using the primary + // (green) color for select prompts, so we just force it for all prompts + return this.style(this.value); + }, + styles: { + em(msg) { + // stylize emphasised text with just the primary color, no underline + return this.primary(msg); + } + }, + ...questions[i], + onSubmit() { + this.cursorShow(); + }, + stdin, + stdout + }; + } + + return enquire(questions); +} + +/** + * Calls the specified appcd service and if the response returns an error with a `prompt`, then + * it will prompt for the value and retry the request. + * + * @param {Object} opts - Various options. + * @param {Object} opts.ctx - A cli-kit execution context. + * @param {Object} opts.data - The data payload to send to the appcd service. + * @param {String|Function} [opts.footer] - A message to display after the service call has completed successfully. + * @param {String|Function} [opts.header] - A message to display before the first prompt. + * @param {String} opts.ns - The debug log namespace for this prompt loop. + * @param {String} opts.path - The appcd service to call. + * @param {Function} [opts.print] - A custom print function. Defaults to `console.log()`. + * @returns {Promise} + */ +export async function promptLoop({ ctx, data, footer, header, ns, path, print }) { + const { argv, console, terminal } = ctx; + const logger = appcd.logger(ns); + + if (print === undefined) { + print = (msg, ...args) => { + if (msg && typeof msg === 'object' && msg.type === 'error') { + console.error(alert(`Error: ${msg.message}`)); + } else { + terminal.stdout.write(format(msg, ...args)); + } + }; + } + + while (true) { + try { + // ctx.data contains `cwd`, `env`, and `userAgent` via cli-kit + const { response } = await appcd.call(path, new DispatcherContext({ headers: ctx.data, request: { data } })); + if (response instanceof Readable) { + await new Promise((resolve, reject) => { + response.on('data', print); + response.on('end', resolve); + response.on('error', reject); + }); + } else if (footer) { + print(typeof footer === 'function' ? await footer(response) : footer); + } else { + print(response); + } + return; + } catch (err) { + if ((!(err instanceof PromptError) && !err.prompt) || !argv.prompt) { + throw err; + } + + if (header) { + print(typeof header === 'function' ? await header() : header); + header = null; + } + + // prompt and try again + let ask = err instanceof PromptError ? err.ask : err.prompt; + let { name } = ask; + let result; + logger.warn(`${err.toString()}, prompting for ${highlight(`"${name}"`)}`); + + while (ask) { + result = await prompt({ + validate(value) { + if (this.type === 'toggle' || !this.required || !!value) { + return true; + } + return ask.validateMessage || false; + }, + name: ask.name || name, + ...ask + }, terminal); + + ask = result?.[ask.name || name]?.prompt; + } + + Object.assign(data, result); + } + } +} diff --git a/src/lib/util.js b/src/lib/util.js index 87e0bfb..62374ce 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -1,3 +1,13 @@ +/** + * Capitalizes a string. + * + * @param {String} str - The string to capitalize. + * @returns {String} + */ +export function capitalize(str) { + return typeof str === 'string' ? `${str[0].toUpperCase()}${str.substring(1)}` : str; +} + /** * Parses the Titanium CLI version from the user agent. * diff --git a/src/lib/version.js b/src/lib/version.js index eac0a6d..f0b9589 100644 --- a/src/lib/version.js +++ b/src/lib/version.js @@ -63,3 +63,33 @@ export function satisfies(ver, str) { return range === '*' || semver.satisfies(ver, range) || (ver.indexOf('-') === -1 && semver.satisfies(ver + '-0', range)); }); } + +export function parseMax(str, allowX) { + let max, lt; + + for (const range of str.split(/\s*\|\|\s*/)) { + let x = range.split(' '); + x = x.length === 1 ? x.shift() : x.slice(1).shift(); + allowX || (x = x.replace(/.x$/i, '')); + const y = x.replace(allowX ? /[^.xX\d]/g : /[^.\d]/g, ''); + if (!max || exports.gt(y, max)) { + lt = /^<[^=]\d/.test(x); + max = y.replace(/\.$/, ''); + } + } + + return (lt ? '<' : '') + max; +} + +export function parseMin(str) { + let min; + + for (const range of str.split(/\s*\|\|\s*/)) { + const x = range.split(' ').shift().replace(/[^.\d]/g, ''); + if (!min || exports.lt(x, min)) { + min = x.replace(/\.$/, ''); + } + } + + return min; +} diff --git a/src/module/module-list-service.js b/src/module/module-list-service.js index 29552f8..a92ebdd 100644 --- a/src/module/module-list-service.js +++ b/src/module/module-list-service.js @@ -4,7 +4,7 @@ import sortObject from 'sort-object-keys'; import { compare } from '../lib/version'; import { DataServiceDispatcher } from 'appcd-dispatcher'; -import { debounce, get } from 'appcd-util'; +import { debounce } from 'appcd-util'; import { modules, options } from 'titaniumlib'; /** @@ -14,11 +14,10 @@ export default class ModuleListService extends DataServiceDispatcher { /** * Starts detecting Titanium SDKs and modules. * - * @param {Object} cfg - An Appc Daemon config object. * @returns {Promise} * @access public */ - async activate(cfg) { + async activate() { this.data = gawk({ android: {}, commonjs: {}, @@ -26,7 +25,7 @@ export default class ModuleListService extends DataServiceDispatcher { windows: {} }); - options.module.searchPaths = get(cfg, 'titanium.module.searchPaths'); + options.module.searchPaths = appcd.config.get('titanium.module.searchPaths'); this.detectEngine = new DetectEngine({ checkDir(dir) { @@ -77,7 +76,7 @@ export default class ModuleListService extends DataServiceDispatcher { gawk.set(this.data, modules); }); - gawk.watch(cfg, [ 'titanium', 'module', 'searchPaths' ], debounce(value => { + appcd.config.watch('titanium.module.searchPaths', debounce(value => { options.module.searchPaths = value; this.detectEngine.paths = modules.getPaths(); })); diff --git a/src/module/module-service.js b/src/module/module-service.js index 7183560..e3d1899 100644 --- a/src/module/module-service.js +++ b/src/module/module-service.js @@ -1,9 +1,13 @@ -import Dispatcher from 'appcd-dispatcher'; +import Dispatcher, { DispatcherError } from 'appcd-dispatcher'; import ModuleListService from './module-list-service'; +import { AppcdError } from 'appcd-response'; import { expandPath } from 'appcd-path'; -import { get, unique } from 'appcd-util'; import { modules } from 'titaniumlib'; +import { unique } from 'appcd-util'; + +const { log } = appcd.logger('module-service'); +const { highlight } = appcd.logger.styles; /** * Defines a service endpoint for listing Titanium modules. @@ -12,32 +16,104 @@ export default class ModuleService extends Dispatcher { /** * Registers all of the endpoints and initializes the installed modules detect engine. * - * @param {Object} cfg - The Appc Daemon config object. * @returns {Promise} * @access public */ - async activate(cfg) { - this.config = cfg; - + async activate() { this.installed = new ModuleListService(); - await this.installed.activate(cfg); + await this.installed.activate(); this.register('/', (ctx, next) => { ctx.path = '/list'; return next(); }) - .register('/list', this.installed) - .register('/locations', () => modules.getPaths()); + .register('/check-downloads', ctx => this.checkDownloads(ctx.request.data.accountName)) + .register('/install/:name?', ctx => this.install(ctx)) + .register('/list', this.installed) + .register('/locations', () => modules.getPaths()); + + const check = async () => { + try { + const { response: accounts } = await appcd.call('/amplify/2.x/auth'); + const account = accounts.find(a => a.active) || accounts[0]; + await this.checkDownloads(account?.name); + log('Successfully checked downloads, checking again in 1 hour'); + } catch (err) { + if (err.code === 'EAUTH') { + log('Not authenticated, checking downloads again in 1 hour'); + } else { + log(`Failed to check downloads: ${err.message}`); + log('Trying again in 1 hour'); + } + } + }; + this.checkTimer = setInterval(check, 1000 * 60 * 60); // check every hour + check(); + } + + /** + * Checks platform for available Titanium native modules and installs them if they aren't + * already installed. + * + * @param {String} accountName - The name of the account to use to verify downloads. + * @returns {Promise>} + * @access private + */ + async checkDownloads(accountName) { + if (!accountName || typeof accountName !== 'string') { + const err = new TypeError('Expected account name'); + err.code = 'EAUTH'; + throw err; + } + + const { response: downloads } = await appcd.call('/amplify/2.x/ti/downloads', { + data: { + accountName + } + }); + const installed = this.installed.data; + const result = []; + + for (const { id, versions } of downloads.modules) { + for (const { platforms = [], url, version } of versions) { + for (let platform of platforms) { + if (platform === 'iphone') { + platform = 'ios'; + } + if (platform !== 'android' && (process.platform === 'darwin' || platform !== 'ios')) { + continue; + } + if (installed[platform]?.[id]?.[version]) { + log(`${highlight(`${id}@${version}`)} (${platform}) already installed`); + } else { + log(`Installing ${highlight(`${id}@${version}`)} (${platform})...`); + const tiHome = appcd.config.get('titanium.home'); + result.push.apply(result, await modules.install({ + downloadDir: tiHome && expandPath(tiHome, 'downloads'), + uri: url + })); + break; + } + } + } + } + + return result; } /** - * Shuts down the installed SDKs detect engine. + * Shuts down the installed SDKs detect engine and stop checking for downloads. * * @returns {Promise} * @access public */ async deactivate() { await this.installed.deactivate(); + + if (this.checkTimer) { + clearInterval(this.checkTimer); + this.checkTimer = null; + } } /** @@ -48,10 +124,51 @@ export default class ModuleService extends Dispatcher { */ getInstallPaths() { const paths = modules.locations[process.platform].map(p => expandPath(p)); - const defaultPath = get(this.config, 'titanium.modules.defaultInstallLocation'); + const defaultPath = appcd.config.get('titanium.modules.defaultInstallLocation'); if (defaultPath) { paths.unshift(expandPath(defaultPath)); } return unique(paths); } + + /** + * Install module service handler. + * + * Note: This method does not return a promise because we want the response to be sent + * immediately and receive install events as they occur. It relies on the response stream to + * close. + * + * @param {Context} ctx - A request context. + * @access private + */ + install({ request, response }) { + const { data } = request; + const tiHome = appcd.config.get('titanium.home'); + + modules.install({ + downloadDir: tiHome && expandPath(tiHome, 'downloads'), + keep: data.keep, + onProgress(evt) { + if (data.progress) { + response.write(evt); + } + }, + overwrite: data.overwrite, + uri: data.uri + }).then(modules => { + response.write({ fin: true, message: 'Titanium Module installed', modules }); + response.end(); + }).catch(err => { + try { + if (err.code === 'ENOTFOUND') { + response.write(new DispatcherError(err.message)); + } else { + response.write(new AppcdError(err)); + } + response.end(); + } catch (e) { + // stream is probably closed + } + }); + } } diff --git a/src/project/project-service.js b/src/project/project-service.js new file mode 100644 index 0000000..ddd8992 --- /dev/null +++ b/src/project/project-service.js @@ -0,0 +1,236 @@ +/* eslint-disable node/prefer-global/console */ + +import Dispatcher from 'appcd-dispatcher'; +import fs from 'fs-extra'; +import path from 'path'; +import TemplateService from './templates-service'; +import { AppcdError, codes } from 'appcd-response'; +import { Console } from 'console'; +import { exec } from '../legacy'; +import { isFile } from 'appcd-fs'; +import { Project /* , Tiapp */ } from 'titaniumlib'; +import { PromptError } from '../lib/prompt'; + +const { log } = appcd.logger('project'); +const { highlight } = appcd.logger.styles; + +/** + * Service for creating and building Titanium applications. + */ +export default class ProjectService extends Dispatcher { + templateSvc = new TemplateService(); + + /** + * Registers all of the endpoints. + * + * @returns {Promise} + * @access public + */ + async activate() { + const runLegacyCLI = async (command, ctx) => { + const { cwd } = ctx.request.data; + const argv = { ...ctx.request.data }; + delete argv.cwd; + await exec({ + argv, + command, + config: appcd.config.get('titanium'), + console: new Console(ctx.response, ctx.response), + cwd + }); + ctx.response.end(); + }; + + this.register('/', ctx => { + return 'tiapp coming soon!'; + }); + + this.register('/add', ctx => { + /* + this endpoint is for adding a component to an existing app such as: + * ACA: + - Ensure logged in + - Entitlement check + - Download/install acs module + - Add per platform + * Alloy + * Apple Watch app: + - Prompt for name + - Install from template into project dir + * Hyperloop: + - Add per platform + * MBS: + - Create ACS apps + - Add acs-* properties to tiapp.xml + - Prompt if acs keys exist + - Set appc-org-id and appc-creator-user-id properites + - Add ti.cloud commonjs module + - Add ti.cloud bootstrap to app.js + */ + + const projectDir = assertProjectDir(ctx.request.data); + + ctx.response = 'Not implemented yet'; + }); + + this.register('/build', ctx => runLegacyCLI('build', ctx)); + + this.register('/clean', ctx => runLegacyCLI('clean', ctx)); + + this.register('/new', async ctx => { + try { + return await new Project({ + templates: (await this.call('/templates')).response + }).create(ctx.request.data); + } catch (err) { + if (err.prompt) { + err.telemetry = false; + } + throw err; + } + }); + + this.register('/register', async ctx => { + // step 1: make sure you're logged in + const { response: accounts } = await appcd.call('/amplify/2.x/auth'); + const account = accounts.find(a => a.active) || accounts[0]; + if (!account) { + throw new AppcdError(codes.FORBIDDEN, 'You must be authenticated to register an app'); + } + + if (!account.orgs.length) { + throw new AppcdError(codes.SERVER_ERROR, `Your account "${account.name}" has no organizations, please logout and login again`); + } + + let { force, org } = ctx.request.data; + let org_id = null; + + // step 1: check the org + if (org) { + org = String(org).toLowerCase(); + org_id = account.orgs.find(({ id, guid, name }) => String(id) === org || guid === org || name.toLowerCase() === org)?.id; + } + + if (!org_id) { + // no `org` or `org` not found + if (account.orgs.length > 1) { + throw new PromptError('Organization required to register app', { + choices: account.orgs + .map(org => ({ + name: org.name, + message: org.name, + value: org.id + })) + .sort((a, b) => a.message.localeCompare(b.message)), + message: 'Which organization should the app be registered with', + name: 'org', + type: 'select' + }); + } else { + org_id = accounts.orgs[0].id; + } + } + + // step 2: determine the project directory + const projectDir = assertProjectDir(ctx.request.data); + + // step 3: load the tiapp and get the guid + const tiappFile = path.resolve(projectDir, 'tiapp.xml'); + if (!isFile(tiappFile)) { + throw new AppcdError(codes.BAD_REQUEST, 'Invalid project directory'); + } + + log(`Loading: ${highlight(tiappFile)}`); + /* FIX ME! + const tiapp = new Tiapp({ file: tiappFile }); + const { guid } = tiapp.get('guid'); + */ + const guid = '28463e4d-0c2a-4eaf-9999-fdb4468c8778'; // already registered + // const guid = '28463e4d-0c2a-4eaf-9999-fdb4468c8779'; + + // step 4: verify that the app is registered + if (!force) { + try { + await appcd.call('/amplify/2.x/ti/app', { + data: { + accountName: account.name, + appGuid: guid + } + }); + + ctx.response = 'Application already registered\n'; + return; + } catch (err) { + if (err.code !== 404) { + throw new AppcdError(codes.SERVER_ERROR, `Failed to verify app: ${err.message}`); + } + } + } + + // step 5: register the app + log(`Registering app with org ${highlight(org_id)}`); + const { response } = await appcd.call('/amplify/2.x/ti/app/set', { + data: { + accountName: account.name, + tiapp: fs.readFileSync(tiappFile, 'utf8'), + params: { + import: true, + org_id + } + } + }); + + // step 6: update the tiapp.xml + /* FIX ME! + tiapp.set('guid', response.app_guid); + tiapp.set([ 'property', 'appc-app-id' ], { type: 'string', value: response._id }); + tiapp.save(); + */ + + ctx.response = 'Registration completed successfully!\n'; + }); + + // TODO: in the future, run will call project.build and we'll "run" it ourselves + this.register('/run', ctx => runLegacyCLI('run', ctx)); + + await this.templateSvc.activate(); + this.register('/templates', this.templateSvc); + } + + /** + * Perform any necessary cleanup. + * + * @returns {Promise} + * @access public + */ + async deactivate() { + await this.templateSvc.deactivate(); + } +} + +/** + * Checks that the project diretory is valid and resolves the absolute path. + * + * @param {Object} opts - Various options. + * @param {String} opts.cwd - The current working directory. + * @param {String} opts.projectDir - The path to the project. + * @returns {String} + */ +function assertProjectDir({ cwd, projectDir }) { + if (projectDir !== undefined && typeof projectDir !== 'string') { + throw new PromptError('Invalid project directory', { + message: 'Where is the project located?', + name: 'projectDir', + type: 'text' + }); + } + + if (projectDir === undefined || !path.isAbsolute(projectDir)) { + if (!cwd || typeof cwd !== 'string') { + throw new AppcdError(codes.BAD_REQUEST, 'Current working directory required when project directory is relative'); + } + projectDir = path.resolve(cwd, projectDir || '.'); + } + + return projectDir; +} diff --git a/src/project/templates-service.js b/src/project/templates-service.js new file mode 100644 index 0000000..920d357 --- /dev/null +++ b/src/project/templates-service.js @@ -0,0 +1,84 @@ +import DetectEngine from 'appcd-detect'; +import fs from 'fs-extra'; +import gawk from 'gawk'; +import globalModules from 'global-modules'; +import path from 'path'; +import { DataServiceDispatcher } from 'appcd-dispatcher'; +import { mergeDeep } from 'appcd-util'; +import { templates } from 'titaniumlib'; + +/** + * Detects global Titanium project templates. + */ +export default class TemplateService extends DataServiceDispatcher { + /** + * Starts detecting templates. + * + * @returns {Promise} + * @access public + */ + async activate() { + const keywordRE = /^titanium-(?:(\w*)-)?template$/; + + this.detectEngine = new DetectEngine({ + checkDir(dir) { + try { + const pkg = fs.readJsonSync(path.join(dir, 'package.json')); + for (const keyword of pkg.keywords) { + const m = keyword.match(keywordRE); + if (m) { + return { + name: pkg.name, + desc: pkg.description, + path: dir, + pkg, + type: m[1] || undefined + }; + } + } + } catch (e) { + // 'dir' is not a template + } + }, + depth: 2, // allow for scoped packages + multiple: true, + name: 'titanium:templates', + paths: [ globalModules ], + processResults(results) { + results.sort((a, b) => { + return a.name.localeCompare(b.name); + }); + }, + redetect: true, + watch: true + }); + + const format = results => { + const data = mergeDeep({}, templates); + for (const template of results) { + let type = template.type || 'other'; + if (!data[type]) { + data[type] = []; + } + data[type].push(template); + } + return data; + }; + + this.detectEngine.on('results', results => gawk.set(this.data, format(results))); + this.data = gawk(format(await this.detectEngine.start())); + } + + /** + * Stops the detect engine. + * + * @returns {Promise} + * @access public + */ + async deactivate() { + if (this.detectEngine) { + await this.detectEngine.stop(); + this.detectEngine = null; + } + } +} diff --git a/src/sdk/sdk-list-service.js b/src/sdk/sdk-list-service.js index 15a45e1..824731a 100644 --- a/src/sdk/sdk-list-service.js +++ b/src/sdk/sdk-list-service.js @@ -1,9 +1,10 @@ import DetectEngine from 'appcd-detect'; import gawk from 'gawk'; +import semver from 'semver'; import { compare } from '../lib/version'; import { DataServiceDispatcher } from 'appcd-dispatcher'; -import { debounce, get } from 'appcd-util'; +import { debounce } from 'appcd-util'; import { options, sdk } from 'titaniumlib'; /** @@ -13,14 +14,13 @@ export default class SDKListService extends DataServiceDispatcher { /** * Starts detecting Titanium SDKs. * - * @param {Object} cfg - The Appc Daemon config object. * @returns {Promise} * @access public */ - async activate(cfg) { + async activate() { this.data = gawk([]); - options.sdk.searchPaths = get(cfg, 'titanium.sdk.searchPaths'); + options.sdk.searchPaths = appcd.config.get('titanium.sdk.searchPaths'); this.detectEngine = new DetectEngine({ checkDir(dir) { @@ -36,10 +36,9 @@ export default class SDKListService extends DataServiceDispatcher { paths: sdk.getPaths(), processResults(results) { results.sort((a, b) => { - return compare( - a.manifest && a.manifest.version, - b.manifest && b.manifest.version - ); + const av = a.manifest?.version; + const bv = b.manifest?.version; + return av && bv ? compare(av, bv) : 0; }); }, recursive: true, @@ -50,7 +49,7 @@ export default class SDKListService extends DataServiceDispatcher { this.detectEngine.on('results', results => gawk.set(this.data, results)); - gawk.watch(cfg, [ 'titanium', 'sdk', 'searchPaths' ], debounce(value => { + appcd.config.watch('titanium.sdk.searchPaths', debounce(value => { options.sdk.searchPaths = value; this.detectEngine.paths = sdk.getPaths(); })); @@ -70,4 +69,29 @@ export default class SDKListService extends DataServiceDispatcher { this.detectEngine = null; } } + + /** + * Finds an SDK by name (or version) or by `latest`. + * + * @param {String} [name='latest'] - The SDK name or version to search for. + * @returns {Object} + */ + find(name) { + let result; + if (!name || name === 'latest') { + // get the latest installed + for (const sdk of this.data) { + if (!result || (sdk.manifest && result.manifest && semver.gt(sdk.manifest.version, result.manifest.version))) { + result = sdk; + } + } + } else { + result = this.data.find(s => s.name === name); + if (!result) { + // maybe name is a version? + result = this.data.find(s => s.manifest?.version === name); + } + } + return result; + } } diff --git a/src/sdk/sdk-service.js b/src/sdk/sdk-service.js index 19cf50a..e146773 100644 --- a/src/sdk/sdk-service.js +++ b/src/sdk/sdk-service.js @@ -1,6 +1,5 @@ -import Dispatcher from 'appcd-dispatcher'; +import Dispatcher, { DispatcherError } from 'appcd-dispatcher'; import SDKListService from './sdk-list-service'; - import { AppcdError, codes } from 'appcd-response'; import { expandPath } from 'appcd-path'; import { sdk } from 'titaniumlib'; @@ -12,20 +11,18 @@ export default class SDKService extends Dispatcher { /** * Registers all of the endpoints and initializes the installed SDKs detect engine. * - * @param {Object} cfg - The Appc Daemon config object. * @returns {Promise} * @access public */ - async activate(cfg) { - this.config = cfg; - + async activate() { this.installed = new SDKListService(); - await this.installed.activate(cfg); + await this.installed.activate(); this.register('/', (ctx, next) => { ctx.path = '/list'; return next(); }) + .register('/find/:name?', ctx => this.find(ctx)) .register('/list', this.installed) .register('/branches', () => sdk.getBranches()) .register('/builds/:branch?', ctx => sdk.getBuilds(ctx.request.params.branch)) @@ -46,35 +43,57 @@ export default class SDKService extends Dispatcher { } /** - * Install SDK service handler. + * Scans installed Titanium SDKs to find an SDK by name. * * @param {Context} ctx - A request context. + * @returns {Object} * @access private */ - install(ctx) { + find(ctx) { const { data, params } = ctx.request; + const name = data.name || params.name; + const result = this.installed.find(name); + if (result) { + return result; + } + throw new DispatcherError(`Titanium SDK ${name} not found`); + } + + /** + * Install SDK service handler. + * + * Note: This method does not return a promise because we want the response to be sent + * immediately and receive install events as they occur. It relies on the response stream to + * close. + * + * @param {Context} ctx - A request context. + * @access private + */ + install({ request, response }) { + const { data, params } = request; + const tiHome = appcd.config.get('titanium.home'); sdk.install({ - downloadDir: this.config.home && expandPath(this.config.home, 'downloads'), + downloadDir: tiHome && expandPath(tiHome, 'downloads'), keep: data.keep, onProgress(evt) { if (data.progress) { - ctx.response.write(evt); + response.write(evt); } }, overwrite: data.overwrite, uri: data.uri || params.name }).then(tisdk => { - ctx.response.write({ fin: true, message: `Titanium SDK ${tisdk.name} installed` }); - ctx.response.end(); - }, err => { + response.write({ fin: true, message: `Titanium SDK ${tisdk.name} installed` }); + response.end(); + }).catch(err => { try { if (err.code === 'ENOTFOUND') { - ctx.response.write(new AppcdError(codes.NOT_FOUND, err.message)); + response.write(new DispatcherError(err.message)); } else { - ctx.response.write(new AppcdError(err)); + response.write(new AppcdError(err)); } - ctx.response.end(); + response.end(); } catch (e) { // stream is probably closed } @@ -99,7 +118,7 @@ export default class SDKService extends Dispatcher { try { return await sdk.uninstall(uri); } catch (err) { - throw err.code === 'ENOTFOUND' ? new AppcdError(codes.NOT_FOUND, err) : err; + throw err.code === 'ENOTFOUND' ? new DispatcherError(err) : err; } } } diff --git a/support/android/README.txt b/support/android/README.txt new file mode 100644 index 0000000..91b41f8 --- /dev/null +++ b/support/android/README.txt @@ -0,0 +1 @@ +These files are used for the Android remote encryption policy for registered applications. diff --git a/support/android/appcelerator-security.jar b/support/android/appcelerator-security.jar new file mode 100644 index 0000000..e7626e7 Binary files /dev/null and b/support/android/appcelerator-security.jar differ diff --git a/support/android/appcelerator-verify.jar b/support/android/appcelerator-verify.jar new file mode 100644 index 0000000..29eb09d Binary files /dev/null and b/support/android/appcelerator-verify.jar differ diff --git a/support/ios/ApplicationRouting.m b/support/ios/ApplicationRouting.m new file mode 100644 index 0000000..5d4154e --- /dev/null +++ b/support/ios/ApplicationRouting.m @@ -0,0 +1,125 @@ +/** + * This code is closed source and Confidential and Proprietary to + * Appcelerator, Inc. All Rights Reserved. This code MUST not be + * modified, copied or otherwise redistributed without express + * written permission of Appcelerator. This file is licensed as + * part of the Appcelerator Platform and governed under the terms + * of the Appcelerator license agreement. + * Copyright (c) 2015 Appcelerator, Inc. All Rights Reserved. + */ +#import +#import +#import "ApplicationRouting.h" +#import + +#define initializeAppData icucfasf7797nnzz +#define filterAppData sdnmnciuuu66zzaq + +extern NSString * const TI_APPLICATION_GUID; +extern NSData * filterAppData (NSString *filename, NSData * thedata); +extern void initializeAppData (NSString * sha1); + +/** + * gunzip NSData and return as NSData + */ +static NSData* gunzip(NSData *data) { + z_stream zStream; + memset(&zStream, 0, sizeof(zStream)); + inflateInit2(&zStream, 16); + + UInt32 nUncompressedBytes = *(UInt32*)(data.bytes + data.length - 4); + NSMutableData* gunzippedData = [NSMutableData dataWithLength:nUncompressedBytes]; + + zStream.next_in = (Bytef*)data.bytes; + zStream.avail_in =(uInt) data.length; + zStream.next_out = (Bytef*)gunzippedData.bytes; + zStream.avail_out = (uInt)gunzippedData.length; + + inflate(&zStream, Z_FINISH); + inflateEnd(&zStream); + + return gunzippedData; +} + +/** + * return a char* as hex NSString* + */ +static NSString* toHexString(unsigned char* data, unsigned int length) { + NSMutableString* hash = [NSMutableString stringWithCapacity:length * 2]; + for (unsigned int i = 0; i < length; i++) { + [hash appendFormat:@"%02x", data[i]]; + data[i] = 0; + } + return hash; +} + +/** + * sha1 a NSString + */ +static NSString* sha1Str(NSString *data) { + unsigned int outputLength = CC_SHA1_DIGEST_LENGTH; + unsigned char output[outputLength]; + CC_LONG length = (CC_LONG)[data lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + CC_SHA1([data UTF8String], length, output); + return toHexString(output,outputLength); +} + +/** + * sha1 a NSData + */ +static NSData* sha1Data(NSData *data) { + unsigned int outputLength = CC_SHA1_DIGEST_LENGTH; + unsigned char output[outputLength]; + CC_SHA1(data.bytes, (unsigned int) data.length, output); + NSString *str = toHexString(output,outputLength); + return [str dataUsingEncoding:NSUTF8StringEncoding]; +} + +@implementation ApplicationRouting + ++ (void) initialize { + NSError *error = nil; + NSString *dirsha = sha1Str(TI_APPLICATION_GUID); + NSString *dirPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:dirsha]; + NSArray *directoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirPath error:&error]; +#if !TARGET_OS_IPHONE && TARGET_IPHONE_SIMULATOR + NSLog(@"[INFO] ApplicationRouting initialize, dirPath=%@",dirPath); +#endif + if (error==nil && [directoryContents count] > 0) { + // sort the files alphabetically + directoryContents = [directoryContents sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; + NSMutableData *shadata = [NSMutableData dataWithCapacity:CC_SHA1_DIGEST_LENGTH]; + for (NSString *filename in directoryContents) { + NSString *filePath = [dirPath stringByAppendingPathComponent:filename]; + NSFileHandle *aHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; + NSData *contentData = [aHandle readDataToEndOfFile]; + NSData *sha = sha1Data(contentData); + [shadata appendData:sha]; + } + NSString *sha = [[[NSString alloc] initWithData:sha1Data(shadata) encoding:NSUTF8StringEncoding] autorelease]; +#if !TARGET_OS_IPHONE && TARGET_IPHONE_SIMULATOR + NSLog(@"[DEBUG] sha of filenames = [%@]",sha); +#endif + initializeAppData(sha); + } +} + ++ (NSData *) resolveAppAsset:(NSString *)path { +#if !TARGET_OS_IPHONE && TARGET_IPHONE_SIMULATOR + NSLog(@"[INFO] resolveAppAsset path %@",path); +#endif + NSString *dirsha = sha1Str(TI_APPLICATION_GUID); + NSString *pathsha = sha1Str(path); + NSString *filePath = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:dirsha] stringByAppendingPathComponent:pathsha]; + NSFileHandle *aHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; + if (aHandle) { + NSData *contentData = [aHandle readDataToEndOfFile]; + NSData *unzipData = gunzip(contentData); + return filterAppData(path, unzipData); + } + + NSLog(@"[WARN] couldn't find file %@",path); + return nil; +} + +@end diff --git a/support/ios/libappcverify.a b/support/ios/libappcverify.a new file mode 100644 index 0000000..c7f0cb7 Binary files /dev/null and b/support/ios/libappcverify.a differ diff --git a/yarn.lock b/yarn.lock index 8bed67b..9f71dd3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,19 @@ # yarn lockfile v1 +"@axway/amplify-request@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@axway/amplify-request/-/amplify-request-2.1.1.tgz#96f9bd275509337d4cc22c7838097571ae9e8b74" + integrity sha512-9T4MJnCn67clBvRIZEvu764tpedzSaLUOifHcbC3yRVMYnzjhsJXESgevy9OaJ2CAapUiwglvZoj9otXj6X8WA== + dependencies: + appcd-util "^3.1.1" + got "^11.8.1" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + pretty-bytes "^5.5.0" + snooplogg "^3.0.2" + source-map-support "^0.5.19" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -360,6 +373,11 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== +"@sindresorhus/is@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.0.tgz#2ff674e9611b45b528896d820d3d7a812de2f0e4" + integrity sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ== + "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1": version "1.8.2" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.2.tgz#858f5c4b48d80778fde4b9d541f27edc0d56488b" @@ -388,6 +406,36 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== +"@szmarczak/http-timer@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" + integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== + dependencies: + defer-to-connect "^2.0.0" + +"@titanium-sdk/node-is-platform-guid@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@titanium-sdk/node-is-platform-guid/-/node-is-platform-guid-1.0.2.tgz#dd393ec1460525b55c22bb3498912deb28ddbcb3" + integrity sha512-K0fvIe6FY6NUxFcPeZIARQjSqTNRge2p5avkuOX8h9B1GO3mbU1cpNnQhwqZcQXOi/xBxyABzqk32dQ/t7XLMQ== + dependencies: + napi-macros "^2.0.0" + node-gyp-build "^4.2.3" + +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + +"@types/cacheable-request@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" + integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" + "@types/node" "*" + "@types/responselike" "*" + "@types/eslint-scope@^3.7.0": version "3.7.0" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86" @@ -409,6 +457,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== +"@types/http-cache-semantics@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" + integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== + "@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" @@ -419,11 +472,25 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/keyv@*": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" + integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== + dependencies: + "@types/node" "*" + "@types/node@*": version "14.14.22" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.22.tgz#0d29f382472c4ccf3bd96ff0ce47daf5b7b84b18" integrity sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw== +"@types/responselike@*", "@types/responselike@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== + dependencies: + "@types/node" "*" + "@ungap/promise-all-settled@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" @@ -597,6 +664,13 @@ acorn@^8.0.4: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.5.tgz#a3bfb872a74a6a7f661bc81b9849d9cac12601b7" integrity sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -730,6 +804,13 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +appc-security@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/appc-security/-/appc-security-0.1.0.tgz#519b08cb32fa6aec62c606dc5aedf11f1cbf9b26" + integrity sha512-UT6UzAZtfZKvbeUMgLVjA3q6ayfYpFbFDxbzZOv6ScZma4IXoTUn/glaJJyfAGyPEaBIgZbu2BHiAmriv8bhPw== + dependencies: + jsonwebtoken "^8.3.0" + appcd-fs@^1.1.10: version "1.1.10" resolved "https://registry.yarnpkg.com/appcd-fs/-/appcd-fs-1.1.10.tgz#feb17938e2e3d8a09b565a75611203e000aae861" @@ -737,6 +818,13 @@ appcd-fs@^1.1.10: dependencies: source-map-support "^0.5.16" +appcd-fs@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/appcd-fs/-/appcd-fs-2.0.4.tgz#7388af54839f4ab92fd9707c7ef57d962b5a024b" + integrity sha512-ulJwq2DB2pT4B4tNStB319n9eJzj9lPSrYD5ptF8283AYZUTo9uiL46OgLQSIwxD9wFfZTay+NfpHwNfqWXlkg== + dependencies: + source-map-support "^0.5.19" + appcd-gulp@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/appcd-gulp/-/appcd-gulp-3.1.3.tgz#5819f5f513d31be10c0649cffb432698785cb37c" @@ -790,6 +878,13 @@ appcd-path@^1.1.10: dependencies: source-map-support "^0.5.16" +appcd-path@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/appcd-path/-/appcd-path-2.0.5.tgz#48591e4bba0d16889f411f858d6b88c6cead8177" + integrity sha512-vr43I+tMjjmWWDqCIabrnoMaBGD1kKBnoLJiE7452uOKBTGt94JJ4nxpHwHaNXTvkTdXwGhdDnLUX9MXSZdi/Q== + dependencies: + source-map-support "^0.5.19" + appcd-util@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/appcd-util/-/appcd-util-2.0.2.tgz#8c524fe78ba6748f6bcf56147be21265caee8ca1" @@ -800,6 +895,17 @@ appcd-util@^2.0.2: semver "^7.1.1" source-map-support "^0.5.16" +appcd-util@^3.1.1, appcd-util@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/appcd-util/-/appcd-util-3.1.3.tgz#6080c5274487d9d8ed07e16940b119167d4ed2f3" + integrity sha512-76Go21u4TOOG9PjPTW7grnNUtRw8drWN0YZFC7mJrAUhRpamSc1AZhz8w9yhCVioUb8xvOQxPlm000AhbyMMIQ== + dependencies: + appcd-fs "^2.0.4" + lodash.get "^4.4.2" + lodash.set "^4.3.2" + semver "^7.3.4" + source-map-support "^0.5.19" + append-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" @@ -814,24 +920,11 @@ append-transform@^2.0.0: dependencies: default-require-extensions "^3.0.0" -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - archy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -1168,7 +1261,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base64-js@^1.1.2, base64-js@^1.3.1: +base64-js@^1.1.2, base64-js@^1.2.3: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -1198,6 +1291,11 @@ beeper@^1.0.0: resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" integrity sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak= +big-integer@^1.6.44: + version "1.6.48" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" + integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -1220,20 +1318,25 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bl@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" - integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +bplist-creator@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/bplist-creator/-/bplist-creator-0.0.8.tgz#56b2a6e79e9aec3fc33bf831d09347d73794e79c" + integrity sha512-Za9JKzD6fjLC16oX2wsXfc+qBEhJBJB1YPInoAQpMLhDuj5aVOv1baGeIQSq1Fr3OCqzvsoQcSBSwGId/Ja2PA== + dependencies: + stream-buffers "~2.2.0" + +bplist-parser@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" + integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== + dependencies: + big-integer "^1.6.44" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1300,6 +1403,11 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + buffer-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" @@ -1310,14 +1418,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -1333,6 +1433,24 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" + integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^2.0.0" + caching-transform@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" @@ -1527,10 +1645,10 @@ chokidar@^2.0.0: optionalDependencies: fsevents "^1.2.7" -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== chrome-trace-event@^1.0.2: version "1.0.2" @@ -1616,6 +1734,13 @@ clone-buffer@^1.0.0: resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + clone-stats@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" @@ -1711,7 +1836,12 @@ colorette@^1.2.1: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== -combined-stream@^1.0.6, combined-stream@~1.0.6: +colors@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -1748,11 +1878,6 @@ concat-stream@^1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - contains-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" @@ -1890,6 +2015,13 @@ debug@3.X: dependencies: ms "^2.1.1" +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + debug@4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" @@ -1904,13 +2036,6 @@ debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: dependencies: ms "2.0.0" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - decamelize@^1.1.1, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1926,12 +2051,12 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -decompress-response@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" - integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== dependencies: - mimic-response "^2.0.0" + mimic-response "^3.1.0" deep-eql@^3.0.1: version "3.0.1" @@ -1940,11 +2065,6 @@ deep-eql@^3.0.1: dependencies: type-detect "^4.0.0" -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -1969,6 +2089,11 @@ default-resolution@^2.0.0: resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= +defer-to-connect@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" + integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== + define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -2003,11 +2128,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - detect-file@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" @@ -2029,11 +2149,6 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" -detect-libc@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - detect-newline@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" @@ -2153,6 +2268,13 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + electron-to-chromium@^1.3.634: version "1.3.645" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.645.tgz#c0b269ae2ecece5aedc02dd4586397d8096affb1" @@ -2173,7 +2295,7 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: +end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -2188,7 +2310,7 @@ enhanced-resolve@^5.7.0: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.5: +enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== @@ -2724,11 +2846,6 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expand-template@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" - integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== - expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" @@ -2845,7 +2962,7 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -figures@^3.0.0: +figures@^3.0.0, figures@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== @@ -3065,6 +3182,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" + integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -3086,11 +3212,6 @@ fromentries@^1.2.0: resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - fs-extra@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" @@ -3128,6 +3249,13 @@ fs-extra@^9.0.1, fs-extra@^9.1.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs-mkdirp-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" @@ -3164,20 +3292,6 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - gawk@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/gawk/-/gawk-5.0.0.tgz#440f6eeeab5f894994399f311c82ffd4581ebc99" @@ -3235,6 +3349,13 @@ get-stdin@^4.0.1: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -3247,11 +3368,6 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -github-from-package@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" - integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= - glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -3322,6 +3438,13 @@ global-modules@^1.0.0: is-windows "^1.0.1" resolve-dir "^1.0.0" +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + global-prefix@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" @@ -3333,6 +3456,15 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -3357,6 +3489,23 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" +got@^11.8.1: + version "11.8.1" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.1.tgz#df04adfaf2e782babb3daabc79139feec2f7e85d" + integrity sha512-9aYdZL+6nHmvJwHALLwKSUZ0hMwGaJGYv3hoPLPgnT8BoBXm1SjnZeky+91tfwJaDzun2s4RsBRy48IEYv2q2Q== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.1" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" @@ -3555,11 +3704,6 @@ has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -3658,6 +3802,20 @@ htmlparser2@~3.8.1: entities "1.0" readable-stream "1.1" +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -3667,6 +3825,22 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.0-beta.5.2" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz#8b923deb90144aea65cf834b016a340fc98556f3" + integrity sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + ice-cap@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/ice-cap/-/ice-cap-0.0.4.tgz#8a6d31ab4cac8d4b56de4fa946df3352561b6e18" @@ -3682,11 +3856,6 @@ iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -3728,7 +3897,7 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.4, ini@~1.3.0: +ini@^1.3.4, ini@^1.3.5: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -3869,6 +4038,11 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" +is-docker@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" + integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== + is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -4034,6 +4208,13 @@ is-windows@^1.0.1, is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +is-wsl@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -4198,6 +4379,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -4265,6 +4451,22 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonwebtoken@^8.3.0: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -4285,6 +4487,30 @@ just-extend@^4.0.2: resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.1.tgz#158f1fdb01f128c411dc8b286a7b4837b3545282" integrity sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA== +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +keyv@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" + integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== + dependencies: + json-buffer "3.0.1" + kind-of@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" @@ -4321,11 +4547,6 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - last-run@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/last-run/-/last-run-1.1.1.tgz#45b96942c17b1c79c772198259ba943bebf8ca5b" @@ -4547,6 +4768,11 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -4557,6 +4783,31 @@ lodash.isarray@^3.0.0: resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + lodash.keys@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" @@ -4576,6 +4827,11 @@ lodash.merge@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + lodash.pick@^4.2.1: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" @@ -4596,6 +4852,11 @@ lodash.restparam@^3.0.0: resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= +lodash.set@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" + integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= + lodash.some@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" @@ -4643,6 +4904,11 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -4769,10 +5035,15 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-response@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" - integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== minimatch@3.0.4, minimatch@^3.0.4: version "3.0.4" @@ -4786,11 +5057,26 @@ minimist@1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: +minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minipass@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" + integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -4799,11 +5085,6 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - mkdirp@^0.5.1, mkdirp@^0.5.4: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -4811,6 +5092,11 @@ mkdirp@^0.5.1, mkdirp@^0.5.4: dependencies: minimist "^1.2.5" +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mocha-jenkins-reporter@^0.4.5: version "0.4.5" resolved "https://registry.yarnpkg.com/mocha-jenkins-reporter/-/mocha-jenkins-reporter-0.4.5.tgz#d12865e0d991afbaa1d011b966195ebb8936850f" @@ -4883,7 +5169,7 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nan@^2.12.1, nan@^2.14.0: +nan@^2.12.1: version "2.14.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== @@ -4917,10 +5203,10 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -napi-build-utils@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" - integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== +napi-macros@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b" + integrity sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg== natural-compare@^1.4.0: version "1.4.0" @@ -4958,12 +5244,15 @@ nise@^4.0.4: just-extend "^4.0.2" path-to-regexp "^1.7.0" -node-abi@^2.7.0: - version "2.19.3" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.19.3.tgz#252f5dcab12dad1b5503b2d27eddd4733930282d" - integrity sha512-9xZrlyfvKhWme2EXFKQhZRp1yNWT/uI1luYPr3sFl+H4keYY4xR+1jO7mvTTijIsHf1M+QDe9uWuKeEpLInIlg== - dependencies: - semver "^5.4.1" +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== + +node-gyp-build@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" + integrity sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg== node-modules-regexp@^1.0.0: version "1.0.0" @@ -4977,24 +5266,11 @@ node-preload@^0.2.1: dependencies: process-on-spawn "^1.0.0" -node-pty-prebuilt-multiarch@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-pty-prebuilt-multiarch/-/node-pty-prebuilt-multiarch-0.9.0.tgz#8081184198f3f1290906d656e216cae5ecc8324b" - integrity sha512-n5LkPEuBwI+S2GgDk+IEByoVZClYFQGfp1b6dbMMEln7e43ikF8LZVi3vxpLXVq1rnS8t54YOnMB7HSB4jw8Tg== - dependencies: - nan "^2.14.0" - prebuild-install "^5.2.5" - node-releases@^1.1.69: version "1.1.70" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.70.tgz#66e0ed0273aa65666d7fe78febe7634875426a08" integrity sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw== -noop-logger@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" - integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= - normalize-package-data@^2.3.2: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -5017,6 +5293,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + now-and-later@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c" @@ -5024,16 +5305,6 @@ now-and-later@^2.0.0: dependencies: once "^1.3.2" -npmlog@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - nth-check@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" @@ -5089,7 +5360,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@4.X, object-assign@^4.1.0: +object-assign@4.X: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -5192,6 +5463,14 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" +open@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/open/-/open-7.3.1.tgz#111119cb919ca1acd988f49685c4fdd0f4755356" + integrity sha512-f2wt9DCBKKjlFbjzGb8MOAW8LH8F0mrs1zc7KTjAJ9PZNQbfenzWbNP1VZJvw6ICMG9r14Ah6yfwPn7T7i646A== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + optionator@^0.8.1, optionator@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -5240,6 +5519,11 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= +p-cancelable@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" + integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== + p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -5520,6 +5804,15 @@ pkg-dir@^5.0.0: dependencies: find-up "^5.0.0" +plist@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.1.tgz#a9b931d17c304e8912ef0ba3bdd6182baf2e1f8c" + integrity sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ== + dependencies: + base64-js "^1.2.3" + xmlbuilder "^9.0.7" + xmldom "0.1.x" + plugin-error@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace" @@ -5567,27 +5860,6 @@ postcss@^7.0.16: source-map "^0.6.1" supports-color "^6.1.0" -prebuild-install@^5.2.5: - version "5.3.6" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.6.tgz#7c225568d864c71d89d07f8796042733a3f54291" - integrity sha512-s8Aai8++QQGi4sSbs/M1Qku62PFK49Jm1CbgXklGz4nmHveDq0wzJkg7Na5QbnO1uNH8K7iqx2EQ/mV0MZEmOg== - dependencies: - detect-libc "^1.0.3" - expand-template "^2.0.3" - github-from-package "0.0.0" - minimist "^1.2.3" - mkdirp-classic "^0.5.3" - napi-build-utils "^1.0.1" - node-abi "^2.7.0" - noop-logger "^0.1.1" - npmlog "^4.0.1" - pump "^3.0.0" - rc "^1.2.7" - simple-get "^3.0.3" - tar-fs "^2.0.0" - tunnel-agent "^0.6.0" - which-pm-runs "^1.0.0" - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -5598,6 +5870,11 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +pretty-bytes@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.5.0.tgz#0cecda50a74a941589498011cf23275aa82b339e" + integrity sha512-p+T744ZyjjiaFlMUZZv6YPC5JrkNj8maRmPaQCWFJFplUAzpIUTRaTcS+7wmZtUoFXHtESJb23ISliaWyz3SHA== + pretty-hrtime@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -5615,19 +5892,11 @@ process-on-spawn@^1.0.0: dependencies: fromentries "^1.2.0" -progress@^2.0.0: +progress@^2.0.0, progress@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -prompts@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7" - integrity sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -5668,6 +5937,11 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + ramda@^0.27.1: version "0.27.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" @@ -5680,16 +5954,6 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -5734,7 +5998,7 @@ readable-stream@1.1: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0: +"readable-stream@2 || 3", readable-stream@^3.1.1: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -5743,7 +6007,7 @@ readable-stream@1.1: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -5930,6 +6194,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +resolve-alpn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c" + integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA== + resolve-dir@^1.0.0, resolve-dir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" @@ -5968,6 +6237,13 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12. is-core-module "^2.1.0" path-parse "^1.0.6" +responselike@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" + integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== + dependencies: + lowercase-keys "^2.0.0" + restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -6090,7 +6366,7 @@ serialize-javascript@5.0.1, serialize-javascript@^5.0.1: dependencies: randombytes "^2.1.0" -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -6129,24 +6405,19 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== -simple-concat@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" - integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== - -simple-get@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" - integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== +simple-plist@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-1.1.1.tgz#54367ca28bc5996a982c325c1c4a4c1a05f4047c" + integrity sha512-pKMCVKvZbZTsqYR6RKgLfBHkh2cV89GXcA/0CVPje3sOiNOnXA8+rp/ciAMZ7JRaUdLzlEM6JFfUn+fS6Nt3hg== dependencies: - decompress-response "^4.2.0" - once "^1.3.1" - simple-concat "^1.0.0" + bplist-creator "0.0.8" + bplist-parser "0.2.0" + plist "^3.0.1" sinon-chai@^3.5.0: version "3.5.0" @@ -6165,11 +6436,6 @@ sinon@^9.2.4: nise "^4.0.4" supports-color "^7.1.0" -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - slice-ansi@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" @@ -6380,6 +6646,11 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" +stream-buffers@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" + integrity sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ= + stream-exhaust@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d" @@ -6524,11 +6795,6 @@ strip-json-comments@3.1.1, strip-json-comments@^3.0.1, strip-json-comments@^3.1. resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - supports-color@7.2.0, supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -6610,26 +6876,17 @@ tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== -tar-fs@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - -tar-stream@^2.1.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" +tar@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" + integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" terser-webpack-plugin@^5.1.1: version "5.1.1" @@ -7000,7 +7257,7 @@ uuid@^3.3.2, uuid@^3.3.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -v8-compile-cache@^2.0.3: +v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== @@ -7166,11 +7423,6 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which-pm-runs@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" - integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= - which@2.0.2, which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -7178,14 +7430,14 @@ which@2.0.2, which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" -which@^1.2.14, which@^1.2.9: +which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" -wide-align@1.1.3, wide-align@^1.1.0: +wide-align@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== @@ -7265,6 +7517,16 @@ xml@^1.0.1: resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= +xmlbuilder@^9.0.7: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= + +xmldom@0.1.x: + version "0.1.31" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff" + integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ== + xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"