From 862cd76787513ba77dd0f4f0d711cc8eb458c74a Mon Sep 17 00:00:00 2001 From: Anuj Badhwar Date: Thu, 3 Jun 2021 13:26:54 +0530 Subject: [PATCH 01/18] chore: Refactor profiles usage to projects (#257) --- src/commands/profiles/list.js | 14 +++++++------- src/commands/profiles/remove.js | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/commands/profiles/list.js b/src/commands/profiles/list.js index 9c7e3c139..bcc83599e 100644 --- a/src/commands/profiles/list.js +++ b/src/commands/profiles/list.js @@ -5,7 +5,7 @@ class ProfilesList extends BaseCommand { async run() { await super.run(); const envProfile = this.userConfig.getProfileFromEnvironment(); - // If environment profile exists, add required details to userConfig.profiles, and mark as active. + // If environment profile exists, add required details to userConfig.projects, and mark as active. if (envProfile) { const { accountSid: ENVIRONMENT_ACCOUNT_SID, region: ENVIRONMENT_REGION } = envProfile; const strippedEnvProfile = { @@ -13,19 +13,19 @@ class ProfilesList extends BaseCommand { accountSid: ENVIRONMENT_ACCOUNT_SID, region: ENVIRONMENT_REGION, }; - this.userConfig.profiles.unshift(strippedEnvProfile); + this.userConfig.projects.unshift(strippedEnvProfile); this.userConfig.setActiveProfile(strippedEnvProfile.id); } - if (this.userConfig.profiles.length > 0) { + if (this.userConfig.projects.length > 0) { // If none of the profiles have a region, delete it from all of them so it doesn't show up in the output. - if (!this.userConfig.profiles.some((p) => p.region)) { - this.userConfig.profiles.forEach((p) => delete p.region); + if (!this.userConfig.projects.some((p) => p.region)) { + this.userConfig.projects.forEach((p) => delete p.region); } const activeProfile = this.userConfig.getActiveProfile(); - this.userConfig.profiles.forEach((p) => { + this.userConfig.projects.forEach((p) => { p.active = p.id === activeProfile.id; }); - this.output(this.userConfig.profiles); + this.output(this.userConfig.projects); } else { this.logger.warn(`No profiles have been configured. Run ${chalk.bold('twilio profiles:create')} to create one!`); } diff --git a/src/commands/profiles/remove.js b/src/commands/profiles/remove.js index 4c72972d3..0908a6525 100644 --- a/src/commands/profiles/remove.js +++ b/src/commands/profiles/remove.js @@ -33,7 +33,7 @@ class ProfilesRemove extends TwilioClientCommand { 'Are you sure you want to remove the active profile? Run "twilio profiles:use" to set another profile as active.', ); } - if (this.userConfig.profiles.length === 1) { + if (this.userConfig.projects.length === 1) { this.logger.warn( 'Are you sure you want to remove the last profile? Run "twilio profiles:create" to create another profile.', ); From c679ed82708be2efc70ac1937ce8a385760689f0 Mon Sep 17 00:00:00 2001 From: Anuj Badhwar Date: Mon, 21 Jun 2021 20:26:23 +0530 Subject: [PATCH 02/18] chore: Store API Keys in Config File (#259) * Save to config and not keytar * Remove secure storage references * Remove keytar load * Remove unnecessary ref to secureStorage * Correct logging * Remove dependent test failures * Add todos --- src/commands/profiles/create.js | 21 +--- test/commands/profiles/create.test.js | 13 +-- test/commands/profiles/list.test.js | 152 +++++++++++++------------- test/commands/profiles/remove.test.js | 39 ++++--- 4 files changed, 105 insertions(+), 120 deletions(-) diff --git a/src/commands/profiles/create.js b/src/commands/profiles/create.js index 4e2b6f0d7..16ce12aaa 100644 --- a/src/commands/profiles/create.js +++ b/src/commands/profiles/create.js @@ -4,21 +4,14 @@ const { flags } = require('@oclif/command'); const { BaseCommand, TwilioClientCommand } = require('@twilio/cli-core').baseCommands; const { CliRequestClient } = require('@twilio/cli-core').services; const { TwilioCliError } = require('@twilio/cli-core').services.error; -const { STORAGE_LOCATIONS } = require('@twilio/cli-core').services.secureStorage; const helpMessages = require('../../services/messaging/help-messages'); -const FRIENDLY_STORAGE_LOCATIONS = { - [STORAGE_LOCATIONS.KEYCHAIN]: 'in your keychain', - [STORAGE_LOCATIONS.WIN_CRED_VAULT]: 'in the Windows credential vault', - [STORAGE_LOCATIONS.LIBSECRET]: 'using libsecret', -}; - const SKIP_VALIDATION = 'skip-parameter-validation'; class ProfilesCreate extends BaseCommand { - constructor(argv, config, secureStorage) { - super(argv, config, secureStorage); + constructor(argv, config) { + super(argv, config); this.accountSid = undefined; this.authToken = undefined; @@ -30,9 +23,6 @@ class ProfilesCreate extends BaseCommand { async run() { await super.run(); - // Eagerly load up the credential store. No need to proceed if this fails. - await this.secureStorage.loadKeytar(); - this.loadArguments(); this.loadAccountSid(); @@ -241,14 +231,11 @@ class ProfilesCreate extends BaseCommand { throw new TwilioCliError('Could not create an API Key.'); } - this.userConfig.addProfile(this.profileId, this.accountSid, this.region); - await this.secureStorage.saveCredentials(this.profileId, apiKey.sid, apiKey.secret); + this.userConfig.addProfile(this.profileId, this.accountSid, this.region, apiKey.sid, apiKey.secret); const configSavedMessage = await this.configFile.save(this.userConfig); this.logger.info( - `Created API Key ${apiKey.sid} and stored the secret ${ - FRIENDLY_STORAGE_LOCATIONS[this.secureStorage.storageLocation] - }. See: https://www.twilio.com/console/runtime/api-keys/${apiKey.sid}`, + `Created API Key ${apiKey.sid} and stored the secret in Config. See: https://www.twilio.com/console/runtime/api-keys/${apiKey.sid}`, ); this.logger.info(configSavedMessage); } diff --git a/test/commands/profiles/create.test.js b/test/commands/profiles/create.test.js index ce306b97b..3c7d22127 100644 --- a/test/commands/profiles/create.test.js +++ b/test/commands/profiles/create.test.js @@ -35,7 +35,6 @@ describe('commands', () => { overwrite: true, }); ctx.testCmd.inquirer.prompt = fakePrompt; - ctx.testCmd.secureStorage.loadKeytar = sinon.fake.resolves(true); }); const mockSuccess = (api) => { @@ -60,9 +59,7 @@ describe('commands', () => { expect(ctx.stderr).to.contain(helpMessages.AUTH_TOKEN_NOT_SAVED); expect(ctx.stderr).to.contain('Saved default.'); expect(ctx.stderr).to.contain('configuration saved'); - expect(ctx.stderr).to.contain( - `Created API Key ${constants.FAKE_API_KEY} and stored the secret using libsecret`, - ); + expect(ctx.stderr).to.contain(`Created API Key ${constants.FAKE_API_KEY} and stored the secret in Config.`); expect(ctx.stderr).to.contain( `See: https://www.twilio.com/console/runtime/api-keys/${constants.FAKE_API_KEY}`, ); @@ -179,14 +176,6 @@ describe('commands', () => { .catch(/Could not create an API Key/) .it('fails to create an API key'); - createTest() - .do((ctx) => { - ctx.testCmd.secureStorage.loadKeytar = sinon.fake.rejects('ugh'); - }) - .do((ctx) => ctx.testCmd.run()) - .catch(/ugh/) - .it('fails early if keytar cannot be loaded'); - createTest(['--region', 'dev']) .nock('https://api.dev.twilio.com', mockSuccess) .do(async (ctx) => ctx.testCmd.run()) diff --git a/test/commands/profiles/list.test.js b/test/commands/profiles/list.test.js index e97510c20..a6383b2b4 100644 --- a/test/commands/profiles/list.test.js +++ b/test/commands/profiles/list.test.js @@ -16,22 +16,25 @@ describe('commands', () => { expect(ctx.stderr).to.contain('No profiles have been configured'); }); - test - .do((ctx) => { - ctx.userConfig = new ConfigData(); - ctx.userConfig.addProfile('profile1', constants.FAKE_ACCOUNT_SID); - }) - .twilioCliEnv(Config) - .stdout() - .stderr() - .twilioCommand(ProfilesList, []) - .it('runs profiles:list with 1 profile', (ctx) => { - expect(ctx.stdout).to.contain('profile1'); - expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); - expect(ctx.stdout).to.not.contain('Region'); - expect(ctx.stdout.match(/true/g)).to.have.length(1); - expect(ctx.stderr).to.equal(''); - }); + /* + * TODO: To be fixed with profiles:list changes + * test + * .do((ctx) => { + * ctx.userConfig = new ConfigData(); + * ctx.userConfig.addProfile('profile1', constants.FAKE_ACCOUNT_SID); + * }) + * .twilioCliEnv(Config) + * .stdout() + * .stderr() + * .twilioCommand(ProfilesList, []) + * .it('runs profiles:list with 1 profile', (ctx) => { + * expect(ctx.stdout).to.contain('profile1'); + * expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); + * expect(ctx.stdout).to.not.contain('Region'); + * expect(ctx.stdout.match(/true/g)).to.have.length(1); + * expect(ctx.stderr).to.equal(''); + * }); + */ test .do(() => { @@ -50,63 +53,66 @@ describe('commands', () => { expect(ctx.stderr).to.equal(''); }); - test - .do((ctx) => { - ctx.userConfig = new ConfigData(); - ctx.userConfig.addProfile('profile1', constants.FAKE_ACCOUNT_SID); - ctx.userConfig.addProfile('profile2', constants.FAKE_ACCOUNT_SID); - }) - .twilioCliEnv(Config) - .stdout() - .stderr() - .twilioCommand(ProfilesList, []) - .it('runs profiles:list with multiple profiles', (ctx) => { - expect(ctx.stdout).to.contain('profile1'); - expect(ctx.stdout).to.contain('profile2'); - expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); - expect(ctx.stdout).to.not.contain('Region'); - expect(ctx.stdout.match(/true/g)).to.have.length(1); - expect(ctx.stderr).to.equal(''); - }); - - test - .do((ctx) => { - ctx.userConfig = new ConfigData(); - ctx.userConfig.addProfile('profile1', constants.FAKE_ACCOUNT_SID); - ctx.userConfig.addProfile('profile2', constants.FAKE_ACCOUNT_SID); - ctx.userConfig.activeProfile = 'profile1'; - }) - .twilioCliEnv(Config) - .stdout() - .stderr() - .twilioCommand(ProfilesList, []) - .it('when the active profile is set', (ctx) => { - expect(ctx.stdout).to.contain('profile1'); - expect(ctx.stdout).to.contain('profile2'); - expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); - expect(ctx.stdout).to.not.contain('Region'); - expect(ctx.stdout.match(/true/g)).to.have.length(1); - expect(ctx.stdout).to.match(/profile1.*true/); - expect(ctx.stderr).to.equal(''); - }); - - test - .do((ctx) => { - ctx.userConfig = new ConfigData(); - ctx.userConfig.addProfile('default', constants.FAKE_ACCOUNT_SID, 'dev'); - }) - .twilioCliEnv(Config) - .stdout() - .stderr() - .twilioCommand(ProfilesList, []) - .it('runs profiles:list with 1 regional profile', (ctx) => { - expect(ctx.stdout).to.contain('default'); - expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); - expect(ctx.stdout).to.contain('dev'); - expect(ctx.stdout).to.contain('Region'); - expect(ctx.stdout.match(/true/g)).to.have.length(1); - expect(ctx.stderr).to.equal(''); - }); + /* + * TODO: To be fixed with profiles:list changes + * test + * .do((ctx) => { + * ctx.userConfig = new ConfigData(); + * ctx.userConfig.addProfile('profile1', constants.FAKE_ACCOUNT_SID); + * ctx.userConfig.addProfile('profile2', constants.FAKE_ACCOUNT_SID); + * }) + * .twilioCliEnv(Config) + * .stdout() + * .stderr() + * .twilioCommand(ProfilesList, []) + * .it('runs profiles:list with multiple profiles', (ctx) => { + * expect(ctx.stdout).to.contain('profile1'); + * expect(ctx.stdout).to.contain('profile2'); + * expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); + * expect(ctx.stdout).to.not.contain('Region'); + * expect(ctx.stdout.match(/true/g)).to.have.length(1); + * expect(ctx.stderr).to.equal(''); + * }); + * + * test + * .do((ctx) => { + * ctx.userConfig = new ConfigData(); + * ctx.userConfig.addProfile('profile1', constants.FAKE_ACCOUNT_SID); + * ctx.userConfig.addProfile('profile2', constants.FAKE_ACCOUNT_SID); + * ctx.userConfig.activeProfile = 'profile1'; + * }) + * .twilioCliEnv(Config) + * .stdout() + * .stderr() + * .twilioCommand(ProfilesList, []) + * .it('when the active profile is set', (ctx) => { + * expect(ctx.stdout).to.contain('profile1'); + * expect(ctx.stdout).to.contain('profile2'); + * expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); + * expect(ctx.stdout).to.not.contain('Region'); + * expect(ctx.stdout.match(/true/g)).to.have.length(1); + * expect(ctx.stdout).to.match(/profile1.*true/); + * expect(ctx.stderr).to.equal(''); + * }); + * + * test + * .do((ctx) => { + * ctx.userConfig = new ConfigData(); + * ctx.userConfig.addProfile('default', constants.FAKE_ACCOUNT_SID, 'dev'); + * }) + * .twilioCliEnv(Config) + * .stdout() + * .stderr() + * .twilioCommand(ProfilesList, []) + * .it('runs profiles:list with 1 regional profile', (ctx) => { + * expect(ctx.stdout).to.contain('default'); + * expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); + * expect(ctx.stdout).to.contain('dev'); + * expect(ctx.stdout).to.contain('Region'); + * expect(ctx.stdout.match(/true/g)).to.have.length(1); + * expect(ctx.stderr).to.equal(''); + * }); + */ }); }); }); diff --git a/test/commands/profiles/remove.test.js b/test/commands/profiles/remove.test.js index f101618d1..3173e0fc8 100644 --- a/test/commands/profiles/remove.test.js +++ b/test/commands/profiles/remove.test.js @@ -91,24 +91,27 @@ describe('commands', () => { .catch(/Cancelled/) .it('run profiles:remove with a profile and decide not to remove profile'); - setup(['profile1'], { addProfiles: 1 }) - .nock('https://api.twilio.com', (api) => { - api - .delete(`/2010-04-01/Accounts/${constants.FAKE_ACCOUNT_SID}/Keys/${constants.FAKE_API_KEY}.json`) - .reply(200, { - sid: constants.FAKE_API_KEY, - secret: constants.FAKE_API_SECRET, - }); - }) - .do((ctx) => ctx.testCmd.run()) - .it('run profiles:remove with the last configured profile and delete all keys', (ctx) => { - expect(ctx.stderr).to.contain('remove the active profile'); - expect(ctx.stderr).to.contain('remove the last profile'); - expect(ctx.stderr).to.contain('Deleted local key.'); - expect(ctx.stderr).to.contain('The API Key has been deleted from The Twilio console'); - expect(ctx.stderr).to.contain('Deleted profile1'); - expect(ctx.stderr).to.contain('configuration saved'); - }); + /* + * TODO: To be fixed with profiles:remove functionality + * setup(['profile1'], { addProfiles: 1 }) + * .nock('https://api.twilio.com', (api) => { + * api + * .delete(`/2010-04-01/Accounts/${constants.FAKE_ACCOUNT_SID}/Keys/${constants.FAKE_API_KEY}.json`) + * .reply(200, { + * sid: constants.FAKE_API_KEY, + * secret: constants.FAKE_API_SECRET, + * }); + * }) + * .do((ctx) => ctx.testCmd.run()) + * .it('run profiles:remove with the last configured profile and delete all keys', (ctx) => { + * expect(ctx.stderr).to.contain('remove the active profile'); + * expect(ctx.stderr).to.contain('remove the last profile'); + * expect(ctx.stderr).to.contain('Deleted local key.'); + * expect(ctx.stderr).to.contain('The API Key has been deleted from The Twilio console'); + * expect(ctx.stderr).to.contain('Deleted profile1'); + * expect(ctx.stderr).to.contain('configuration saved'); + * }); + */ setup(['invalidProfile']) .do((ctx) => ctx.testCmd.run()) From 981cad50a6553c06c35a909eb555f9229e149116 Mon Sep 17 00:00:00 2001 From: ravali-rimmalapudi <83863595+ravali-rimmalapudi@users.noreply.github.com> Date: Tue, 22 Jun 2021 21:25:12 +0530 Subject: [PATCH 03/18] chore: CLI Profile Remove - Check and Use config file before checking system keychain. (#262) --- src/commands/profiles/remove.js | 10 ++- test/commands/profiles/remove.test.js | 100 +++++++++++++++++++------- 2 files changed, 83 insertions(+), 27 deletions(-) diff --git a/src/commands/profiles/remove.js b/src/commands/profiles/remove.js index 0908a6525..b33e5514c 100644 --- a/src/commands/profiles/remove.js +++ b/src/commands/profiles/remove.js @@ -10,7 +10,10 @@ class ProfilesRemove extends TwilioClientCommand { const verdict = await this.confirmRemoveProfile(); if (verdict === true) { const keyVerdict = await this.confirmRemoveKey(); - const credentials = await this.deleteLocalKey(deleteProfile); + let credentials; + if (!this.userConfig.profiles[deleteProfile.id]) { + credentials = await this.deleteLocalKey(deleteProfile); + } await this.deleteRemoteKey(deleteProfile, keyVerdict, credentials); this.logger.info(`Deleted ${deleteProfile.id} profile.`); this.userConfig.removeProfile(deleteProfile); @@ -33,7 +36,7 @@ class ProfilesRemove extends TwilioClientCommand { 'Are you sure you want to remove the active profile? Run "twilio profiles:use" to set another profile as active.', ); } - if (this.userConfig.projects.length === 1) { + if (this.userConfig.projects.length + Object.keys(this.userConfig.profiles).length === 1) { this.logger.warn( 'Are you sure you want to remove the last profile? Run "twilio profiles:create" to create another profile.', ); @@ -54,7 +57,8 @@ class ProfilesRemove extends TwilioClientCommand { async deleteRemoteKey(profileDelete, keyVerdict, credentials) { if (keyVerdict === true) { try { - await this.twilioClient.api.keys(credentials.apiKey).remove(); + const apiKey = profileDelete.apiKey || credentials.apiKey; + await this.twilioClient.api.keys(apiKey).remove(); this.logger.info('The API Key has been deleted from The Twilio console.'); } catch (error) { this.logger.error( diff --git a/test/commands/profiles/remove.test.js b/test/commands/profiles/remove.test.js index 3173e0fc8..0a8177fb8 100644 --- a/test/commands/profiles/remove.test.js +++ b/test/commands/profiles/remove.test.js @@ -9,13 +9,22 @@ describe('commands', () => { describe('remove', () => { const setup = ( commandArgs = [], - { addProfiles = 4, deleteProfile = true, deleteKey = true, removeCred = true } = {}, + { addProjects = 4, addProfiles = 3, deleteProfile = true, deleteKey = true, removeCred = true } = {}, ) => test .do((ctx) => { ctx.userConfig = new ConfigData(); + for (let i = 1; i <= addProjects; i++) { + ctx.userConfig.addProject(`profile${i}`, constants.FAKE_ACCOUNT_SID); + } for (let i = 1; i <= addProfiles; i++) { - ctx.userConfig.addProfile(`profile${i}`, constants.FAKE_ACCOUNT_SID); + ctx.userConfig.addProfile( + `configProfile${i}`, + constants.FAKE_ACCOUNT_SID, + '', + `${constants.FAKE_API_KEY}configProfile${i}`, + constants.FAKE_API_SECRET, + ); } }) .twilioCliEnv(Config) @@ -43,12 +52,19 @@ describe('commands', () => { setup(['profile2'], { deleteKey: false }) .do((ctx) => ctx.testCmd.run()) - .it('run profiles:remove with a profile and delets local key but keep remote key', (ctx) => { + .it('run profiles:remove with a profile in keytar and deletes local key but keep remote key', (ctx) => { expect(ctx.stderr).to.contain('Deleted local key.'); expect(ctx.stderr).to.contain('The API Key for profile2 profile still exists in The Twilio console'); expect(ctx.stderr).to.contain('Deleted profile2'); expect(ctx.stderr).to.contain('configuration saved'); }); + setup(['configProfile2'], { deleteKey: false }) + .do((ctx) => ctx.testCmd.run()) + .it('run profiles:remove with a profile in config file but keep remote key', (ctx) => { + expect(ctx.stderr).to.contain('The API Key for configProfile2 profile still exists in The Twilio console'); + expect(ctx.stderr).to.contain('Deleted configProfile2'); + expect(ctx.stderr).to.contain('configuration saved'); + }); setup(['profile3'], { removeCred: false, deleteKey: false }) .do((ctx) => ctx.testCmd.run()) @@ -77,6 +93,25 @@ describe('commands', () => { expect(ctx.stderr).to.contain('configuration saved'); }); + setup(['configProfile1'], { addProjects: 0 }) + .nock('https://api.twilio.com', (api) => { + api + .delete( + `/2010-04-01/Accounts/${constants.FAKE_ACCOUNT_SID}/Keys/${constants.FAKE_API_KEY}configProfile1.json`, + ) + .reply(200, { + sid: constants.FAKE_API_KEY, + secret: constants.FAKE_API_SECRET, + }); + }) + .do((ctx) => ctx.testCmd.run()) + .it('run profiles:remove with the active profile and deletes both remote keys and config profile', (ctx) => { + expect(ctx.stderr).to.contain('remove the active profile'); + expect(ctx.stderr).to.contain('The API Key has been deleted from The Twilio console'); + expect(ctx.stderr).to.contain('Deleted configProfile1'); + expect(ctx.stderr).to.contain('configuration saved'); + }); + setup(['profile2']) .do((ctx) => ctx.testCmd.run()) .it('run profiles:remove with profile and deletes local key but can not delete remote key', (ctx) => { @@ -91,27 +126,44 @@ describe('commands', () => { .catch(/Cancelled/) .it('run profiles:remove with a profile and decide not to remove profile'); - /* - * TODO: To be fixed with profiles:remove functionality - * setup(['profile1'], { addProfiles: 1 }) - * .nock('https://api.twilio.com', (api) => { - * api - * .delete(`/2010-04-01/Accounts/${constants.FAKE_ACCOUNT_SID}/Keys/${constants.FAKE_API_KEY}.json`) - * .reply(200, { - * sid: constants.FAKE_API_KEY, - * secret: constants.FAKE_API_SECRET, - * }); - * }) - * .do((ctx) => ctx.testCmd.run()) - * .it('run profiles:remove with the last configured profile and delete all keys', (ctx) => { - * expect(ctx.stderr).to.contain('remove the active profile'); - * expect(ctx.stderr).to.contain('remove the last profile'); - * expect(ctx.stderr).to.contain('Deleted local key.'); - * expect(ctx.stderr).to.contain('The API Key has been deleted from The Twilio console'); - * expect(ctx.stderr).to.contain('Deleted profile1'); - * expect(ctx.stderr).to.contain('configuration saved'); - * }); - */ + setup(['profile1'], { addProjects: 1, addProfiles: 0 }) + .nock('https://api.twilio.com', (api) => { + api + .delete(`/2010-04-01/Accounts/${constants.FAKE_ACCOUNT_SID}/Keys/${constants.FAKE_API_KEY}.json`) + .reply(200, { + sid: constants.FAKE_API_KEY, + secret: constants.FAKE_API_SECRET, + }); + }) + .do((ctx) => ctx.testCmd.run()) + .it('run profiles:remove with the last configured profile in keytar and delete all keys', (ctx) => { + expect(ctx.stderr).to.contain('remove the active profile'); + expect(ctx.stderr).to.contain('remove the last profile'); + expect(ctx.stderr).to.contain('Deleted local key.'); + expect(ctx.stderr).to.contain('The API Key has been deleted from The Twilio console'); + expect(ctx.stderr).to.contain('Deleted profile1'); + expect(ctx.stderr).to.contain('configuration saved'); + }); + + setup(['configProfile1'], { addProjects: 0, addProfiles: 1 }) + .nock('https://api.twilio.com', (api) => { + api + .delete( + `/2010-04-01/Accounts/${constants.FAKE_ACCOUNT_SID}/Keys/${constants.FAKE_API_KEY}configProfile1.json`, + ) + .reply(200, { + sid: constants.FAKE_API_KEY, + secret: constants.FAKE_API_SECRET, + }); + }) + .do((ctx) => ctx.testCmd.run()) + .it('run profiles:remove with the last configured profile in config file and delete all keys', (ctx) => { + expect(ctx.stderr).to.contain('remove the active profile'); + expect(ctx.stderr).to.contain('remove the last profile'); + expect(ctx.stderr).to.contain('The API Key has been deleted from The Twilio console'); + expect(ctx.stderr).to.contain('Deleted configProfile1'); + expect(ctx.stderr).to.contain('configuration saved'); + }); setup(['invalidProfile']) .do((ctx) => ctx.testCmd.run()) From b71fb5e4c34a7a74f8ef2de4a9aaa5ef86d7d1d2 Mon Sep 17 00:00:00 2001 From: ravali-rimmalapudi <83863595+ravali-rimmalapudi@users.noreply.github.com> Date: Mon, 28 Jun 2021 20:30:01 +0530 Subject: [PATCH 04/18] chore: CLI Profile Update - Using config file instead of the system keychain (#264) * chore: CLI Profile Update - Using config file instead of the system keychain * Added the test cases. * Addressed the review comments * Added the missing alignment. * Addressed the review comments --- src/commands/profiles/create.js | 13 ++++++++- test/commands/profiles/create.test.js | 41 ++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/commands/profiles/create.js b/src/commands/profiles/create.js index 16ce12aaa..03663d801 100644 --- a/src/commands/profiles/create.js +++ b/src/commands/profiles/create.js @@ -230,7 +230,7 @@ class ProfilesCreate extends BaseCommand { this.logger.debug(error); throw new TwilioCliError('Could not create an API Key.'); } - + await this.removeKeytarKeysByProfileId(this.profileId); this.userConfig.addProfile(this.profileId, this.accountSid, this.region, apiKey.sid, apiKey.secret); const configSavedMessage = await this.configFile.save(this.userConfig); @@ -239,6 +239,17 @@ class ProfilesCreate extends BaseCommand { ); this.logger.info(configSavedMessage); } + + async removeKeytarKeysByProfileId(profileId) { + if (this.userConfig.projects.find((p) => p.id === profileId)) { + const removed = await this.secureStorage.removeCredentials(profileId); + if (removed === true) { + this.logger.info('Deleted key from keytar.'); + } else { + this.logger.warn(`Could not delete ${profileId} key from keytar.`); + } + } + } } ProfilesCreate.aliases = ['profiles:add', 'login']; diff --git a/test/commands/profiles/create.test.js b/test/commands/profiles/create.test.js index 3c7d22127..33142d9ad 100644 --- a/test/commands/profiles/create.test.js +++ b/test/commands/profiles/create.test.js @@ -11,9 +11,13 @@ const helpMessages = require('../../../src/services/messaging/help-messages'); describe('commands', () => { describe('profiles', () => { describe('create', () => { - const createTest = (commandArgs = [], profileId = 'default') => + const createTest = (commandArgs = [], { profileId = 'default', addProjects = [], removeCred = true } = {}) => test .twilioFakeProfile(ConfigData) + .do((ctx) => { + ctx.userConfig = new ConfigData(); + addProjects.forEach((project) => ctx.userConfig.addProject(project, constants.FAKE_ACCOUNT_SID)); + }) .twilioCliEnv(Config) .twilioCreateCommand(ProfilesCreate, commandArgs) .stdout() @@ -35,6 +39,11 @@ describe('commands', () => { overwrite: true, }); ctx.testCmd.inquirer.prompt = fakePrompt; + }) + .do((ctx) => { + ctx.testCmd.secureStorage.removeCredentials = () => { + return removeCred; + }; }); const mockSuccess = (api) => { @@ -65,6 +74,36 @@ describe('commands', () => { ); }); + createTest([], { profileId: 'profile1', addProjects: ['profile1'] }) + .nock('https://api.twilio.com', mockSuccess) + .do((ctx) => ctx.testCmd.run()) + .it('runs profiles:create with existing profile in Projects', (ctx) => { + expect(ctx.stdout).to.equal(''); + expect(ctx.stderr).to.contain(helpMessages.AUTH_TOKEN_NOT_SAVED); + expect(ctx.stderr).to.contain('Saved profile1.'); + expect(ctx.stderr).to.contain('Deleted key from keytar.'); + expect(ctx.stderr).to.contain('configuration saved'); + expect(ctx.stderr).to.contain(`Created API Key ${constants.FAKE_API_KEY} and stored the secret in Config.`); + expect(ctx.stderr).to.contain( + `See: https://www.twilio.com/console/runtime/api-keys/${constants.FAKE_API_KEY}`, + ); + }); + + createTest([], { profileId: 'profile1', addProjects: ['profile1'], removeCred: false }) + .nock('https://api.twilio.com', mockSuccess) + .do((ctx) => ctx.testCmd.run()) + .it('runs profiles:create with existing profile in Projects with Keytar remove failed', (ctx) => { + expect(ctx.stdout).to.equal(''); + expect(ctx.stderr).to.contain(helpMessages.AUTH_TOKEN_NOT_SAVED); + expect(ctx.stderr).to.contain('Saved profile1.'); + expect(ctx.stderr).to.contain('Could not delete profile1 key from keytar.'); + expect(ctx.stderr).to.contain('configuration saved'); + expect(ctx.stderr).to.contain(`Created API Key ${constants.FAKE_API_KEY} and stored the secret in Config.`); + expect(ctx.stderr).to.contain( + `See: https://www.twilio.com/console/runtime/api-keys/${constants.FAKE_API_KEY}`, + ); + }); + createTest() .do((ctx) => { sinon.stub(os, 'hostname').returns('some_super_long_fake_hostname'); From fb98a5d0e0091ed400212afb8674da3fcb6a5340 Mon Sep 17 00:00:00 2001 From: ravali-rimmalapudi <83863595+ravali-rimmalapudi@users.noreply.github.com> Date: Thu, 1 Jul 2021 23:41:26 +0530 Subject: [PATCH 05/18] chore: Update profiles list (#265) * chore: Update profiles list * Addressed the comments --- src/commands/profiles/list.js | 18 ++- test/commands/profiles/list.test.js | 236 ++++++++++++++++++---------- 2 files changed, 170 insertions(+), 84 deletions(-) diff --git a/src/commands/profiles/list.js b/src/commands/profiles/list.js index bcc83599e..9bdd9839d 100644 --- a/src/commands/profiles/list.js +++ b/src/commands/profiles/list.js @@ -16,16 +16,24 @@ class ProfilesList extends BaseCommand { this.userConfig.projects.unshift(strippedEnvProfile); this.userConfig.setActiveProfile(strippedEnvProfile.id); } - if (this.userConfig.projects.length > 0) { + const profiles = this.userConfig.projects; + Object.keys(this.userConfig.profiles).forEach((id) => + profiles.push({ + id, + accountSid: this.userConfig.profiles[id].accountSid, + region: this.userConfig.profiles[id].region, + }), + ); + if (profiles.length > 0) { // If none of the profiles have a region, delete it from all of them so it doesn't show up in the output. - if (!this.userConfig.projects.some((p) => p.region)) { - this.userConfig.projects.forEach((p) => delete p.region); + if (!profiles.some((p) => p.region)) { + profiles.forEach((p) => delete p.region); } const activeProfile = this.userConfig.getActiveProfile(); - this.userConfig.projects.forEach((p) => { + profiles.forEach((p) => { p.active = p.id === activeProfile.id; }); - this.output(this.userConfig.projects); + this.output(profiles); } else { this.logger.warn(`No profiles have been configured. Run ${chalk.bold('twilio profiles:create')} to create one!`); } diff --git a/test/commands/profiles/list.test.js b/test/commands/profiles/list.test.js index a6383b2b4..2695d5327 100644 --- a/test/commands/profiles/list.test.js +++ b/test/commands/profiles/list.test.js @@ -16,25 +16,28 @@ describe('commands', () => { expect(ctx.stderr).to.contain('No profiles have been configured'); }); - /* - * TODO: To be fixed with profiles:list changes - * test - * .do((ctx) => { - * ctx.userConfig = new ConfigData(); - * ctx.userConfig.addProfile('profile1', constants.FAKE_ACCOUNT_SID); - * }) - * .twilioCliEnv(Config) - * .stdout() - * .stderr() - * .twilioCommand(ProfilesList, []) - * .it('runs profiles:list with 1 profile', (ctx) => { - * expect(ctx.stdout).to.contain('profile1'); - * expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); - * expect(ctx.stdout).to.not.contain('Region'); - * expect(ctx.stdout.match(/true/g)).to.have.length(1); - * expect(ctx.stderr).to.equal(''); - * }); - */ + test + .do((ctx) => { + ctx.userConfig = new ConfigData(); + ctx.userConfig.addProfile( + 'profile1', + constants.FAKE_ACCOUNT_SID, + '', + constants.FAKE_API_KEY, + constants.FAKE_API_SECRET, + ); + }) + .twilioCliEnv(Config) + .stdout() + .stderr() + .twilioCommand(ProfilesList, []) + .it('runs profiles:list with 1 profile', (ctx) => { + expect(ctx.stdout).to.contain('profile1'); + expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); + expect(ctx.stdout).to.not.contain('Region'); + expect(ctx.stdout.match(/true/g)).to.have.length(1); + expect(ctx.stderr).to.equal(''); + }); test .do(() => { @@ -53,66 +56,141 @@ describe('commands', () => { expect(ctx.stderr).to.equal(''); }); - /* - * TODO: To be fixed with profiles:list changes - * test - * .do((ctx) => { - * ctx.userConfig = new ConfigData(); - * ctx.userConfig.addProfile('profile1', constants.FAKE_ACCOUNT_SID); - * ctx.userConfig.addProfile('profile2', constants.FAKE_ACCOUNT_SID); - * }) - * .twilioCliEnv(Config) - * .stdout() - * .stderr() - * .twilioCommand(ProfilesList, []) - * .it('runs profiles:list with multiple profiles', (ctx) => { - * expect(ctx.stdout).to.contain('profile1'); - * expect(ctx.stdout).to.contain('profile2'); - * expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); - * expect(ctx.stdout).to.not.contain('Region'); - * expect(ctx.stdout.match(/true/g)).to.have.length(1); - * expect(ctx.stderr).to.equal(''); - * }); - * - * test - * .do((ctx) => { - * ctx.userConfig = new ConfigData(); - * ctx.userConfig.addProfile('profile1', constants.FAKE_ACCOUNT_SID); - * ctx.userConfig.addProfile('profile2', constants.FAKE_ACCOUNT_SID); - * ctx.userConfig.activeProfile = 'profile1'; - * }) - * .twilioCliEnv(Config) - * .stdout() - * .stderr() - * .twilioCommand(ProfilesList, []) - * .it('when the active profile is set', (ctx) => { - * expect(ctx.stdout).to.contain('profile1'); - * expect(ctx.stdout).to.contain('profile2'); - * expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); - * expect(ctx.stdout).to.not.contain('Region'); - * expect(ctx.stdout.match(/true/g)).to.have.length(1); - * expect(ctx.stdout).to.match(/profile1.*true/); - * expect(ctx.stderr).to.equal(''); - * }); - * - * test - * .do((ctx) => { - * ctx.userConfig = new ConfigData(); - * ctx.userConfig.addProfile('default', constants.FAKE_ACCOUNT_SID, 'dev'); - * }) - * .twilioCliEnv(Config) - * .stdout() - * .stderr() - * .twilioCommand(ProfilesList, []) - * .it('runs profiles:list with 1 regional profile', (ctx) => { - * expect(ctx.stdout).to.contain('default'); - * expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); - * expect(ctx.stdout).to.contain('dev'); - * expect(ctx.stdout).to.contain('Region'); - * expect(ctx.stdout.match(/true/g)).to.have.length(1); - * expect(ctx.stderr).to.equal(''); - * }); - */ + test + .do((ctx) => { + ctx.userConfig = new ConfigData(); + ctx.userConfig.addProfile( + 'profile1', + constants.FAKE_ACCOUNT_SID, + '', + constants.FAKE_API_KEY, + constants.FAKE_API_SECRET, + ); + ctx.userConfig.addProfile( + 'profile2', + constants.FAKE_ACCOUNT_SID, + '', + constants.FAKE_API_KEY, + constants.FAKE_API_SECRET, + ); + }) + .twilioCliEnv(Config) + .stdout() + .stderr() + .twilioCommand(ProfilesList, []) + .it('runs profiles:list with multiple profiles', (ctx) => { + expect(ctx.stdout).to.contain('profile1'); + expect(ctx.stdout).to.contain('profile2'); + expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); + expect(ctx.stdout).to.not.contain('Region'); + expect(ctx.stdout.match(/true/g)).to.have.length(1); + expect(ctx.stderr).to.equal(''); + }); + + test + .do((ctx) => { + ctx.userConfig = new ConfigData(); + ctx.userConfig.addProject('profile1', constants.FAKE_ACCOUNT_SID); + ctx.userConfig.addProject('profile2', constants.FAKE_ACCOUNT_SID); + }) + .twilioCliEnv(Config) + .stdout() + .stderr() + .twilioCommand(ProfilesList, []) + .it('runs profiles:list with multiple profiles from projects', (ctx) => { + expect(ctx.stdout).to.contain('profile1'); + expect(ctx.stdout).to.contain('profile2'); + expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); + expect(ctx.stdout).to.not.contain('Region'); + expect(ctx.stdout.match(/true/g)).to.have.length(1); + expect(ctx.stderr).to.equal(''); + }); + + test + .do((ctx) => { + ctx.userConfig = new ConfigData(); + ctx.userConfig.addProfile( + 'profile1', + constants.FAKE_ACCOUNT_SID, + '', + constants.FAKE_API_KEY, + constants.FAKE_API_SECRET, + ); + ctx.userConfig.addProject('profile2', constants.FAKE_ACCOUNT_SID); + ctx.userConfig.activeProfile = 'profile1'; + }) + .twilioCliEnv(Config) + .stdout() + .stderr() + .twilioCommand(ProfilesList, []) + .it('when the active profile is set from profiles', (ctx) => { + expect(ctx.stdout).to.contain('profile1'); + expect(ctx.stdout).to.contain('profile2'); + expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); + expect(ctx.stdout).to.not.contain('Region'); + expect(ctx.stdout.match(/true/g)).to.have.length(1); + expect(ctx.stdout).to.match(/profile1.*true/); + expect(ctx.stderr).to.equal(''); + }); + + test + .do((ctx) => { + ctx.userConfig = new ConfigData(); + ctx.userConfig.addProfile( + 'profile1', + constants.FAKE_ACCOUNT_SID, + '', + constants.FAKE_API_KEY, + constants.FAKE_API_SECRET, + ); + ctx.userConfig.addProject('profile2', constants.FAKE_ACCOUNT_SID); + ctx.userConfig.activeProfile = 'profile2'; + }) + .twilioCliEnv(Config) + .stdout() + .stderr() + .twilioCommand(ProfilesList, []) + .it('when the active profile is set from projects', (ctx) => { + expect(ctx.stdout).to.contain('profile1'); + expect(ctx.stdout).to.contain('profile2'); + expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); + expect(ctx.stdout).to.not.contain('Region'); + expect(ctx.stdout.match(/true/g)).to.have.length(1); + expect(ctx.stdout).to.match(/profile2.*true/); + expect(ctx.stderr).to.equal(''); + }); + + test + .do((ctx) => { + ctx.userConfig = new ConfigData(); + ctx.userConfig.addProfile( + 'default', + constants.FAKE_ACCOUNT_SID, + 'dev', + constants.FAKE_API_KEY, + constants.FAKE_API_SECRET, + ); + ctx.userConfig.addProfile( + 'profile1', + constants.FAKE_ACCOUNT_SID, + '', + constants.FAKE_API_KEY, + constants.FAKE_API_SECRET, + ); + ctx.userConfig.addProject('profile2', constants.FAKE_ACCOUNT_SID); + }) + .twilioCliEnv(Config) + .stdout() + .stderr() + .twilioCommand(ProfilesList, []) + .it('runs profiles:list with 1 regional profile set and with multiple profiles', (ctx) => { + expect(ctx.stdout).to.contain('default'); + expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); + expect(ctx.stdout).to.contain('dev'); + expect(ctx.stdout).to.contain('Region'); + expect(ctx.stdout.match(/true/g)).to.have.length(1); + expect(ctx.stderr).to.equal(''); + }); }); }); }); From 2ca36028025f062a79b76099846fdbe1821b94d9 Mon Sep 17 00:00:00 2001 From: Anuj Badhwar Date: Tue, 6 Jul 2021 21:17:51 +0530 Subject: [PATCH 06/18] chore: Add profiles:port (#266) * Add profiles:port * Eslint fix * Minor edit * update test * Address review comments * Change nomenclature from porting profiles to porting keys * add space --- package.json | 1 + src/commands/profiles/port.js | 85 ++++++++++++++++++ test/commands/profiles/port.test.js | 128 ++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 src/commands/profiles/port.js create mode 100644 test/commands/profiles/port.test.js diff --git a/package.json b/package.json index 0a2d7f9d7..85c93d138 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@sendgrid/mail": "^7.2.1", "@twilio/cli-core": "^5.9.1", "chalk": "^4.1.0", + "cli-ux": "^5.6.2", "file-type": "^14.6.2", "inquirer": "^7.3.0", "ngrok": "^3.2.7", diff --git a/src/commands/profiles/port.js b/src/commands/profiles/port.js new file mode 100644 index 000000000..f13dc48b2 --- /dev/null +++ b/src/commands/profiles/port.js @@ -0,0 +1,85 @@ +const { BaseCommand } = require('@twilio/cli-core').baseCommands; +const { cli } = require('cli-ux'); + +class ProfilesPort extends BaseCommand { + async run() { + await super.run(); + + const query = await this.inquirer.prompt([ + { + type: 'confirm', + name: 'confirmation', + message: + `This command will port API keys from keytar` + + ` to config file at location ${this.configFile.filePath}. Continue?`, + default: false, + }, + ]); + + if (!query.confirmation) { + this.logger.warn('Abort!'); + return; + } + + const { projects } = this.userConfig; + + if (this.args.profile) { + const sanitizedProfileId = this.args.profile.trim(); + const project = projects.find((p) => p.id === sanitizedProfileId); + + if (!project) { + this.logger.error( + `Unable to port keys for profile ${sanitizedProfileId} since it doesn't exist or has already been ported!`, + ); + return; + } + + await this.portProfile(project); + } else { + if (projects.length === 0) { + this.logger.info('No profiles have keys in keytar. Nothing to do.'); + return; + } + + for (const project of projects) { + await this.portProfile(project); + } + + this.logger.info('Profiles port process complete!'); + } + } + + async portProfile(project) { + cli.action.start(`Porting ${project.id}`); + + const creds = await this.secureStorage.getCredentials(project.id); + if (creds.apiKey === 'error') { + cli.action.stop('Failed: Unable to retrieve credentials from keytar.'); + return; + } + + // Also removes from `projects` along with adding to `profiles` + this.userConfig.addProfile(project.id, project.accountSid, project.region, creds.apiKey, creds.apiSecret); + await this.configFile.save(this.userConfig); + const removed = await this.secureStorage.removeCredentials(project.id); + if (!removed) { + this.logger.warn(`Unable to clean-up credentials from keytar for profile ${project.id}`); + // But still mark the action as Done! + } + + cli.action.stop('Done!'); + } +} + +ProfilesPort.description = + 'Port API keys from keytar to config file. This command ports ALL keys by default,' + + ' although to only port a specific key append the profile-id as additional argument.'; +ProfilesPort.args = [ + { + name: 'profile', + description: 'Profile-id for porting keys standalone', + }, +]; +ProfilesPort.flags = BaseCommand.flags; + +module.exports = ProfilesPort; diff --git a/test/commands/profiles/port.test.js b/test/commands/profiles/port.test.js new file mode 100644 index 000000000..a2e0fccb6 --- /dev/null +++ b/test/commands/profiles/port.test.js @@ -0,0 +1,128 @@ +const sinon = require('sinon'); +const { expect, test, constants } = require('@twilio/cli-test'); +const { Config, ConfigData } = require('@twilio/cli-core').services.config; + +const ProfilesPort = require('../../../src/commands/profiles/port'); + +describe('commands', () => { + describe('profiles', () => { + describe('port', () => { + const setup = ( + commandArgs = [], + { addProjects = 4, addProfiles = 3, portProfiles = true, removeCred = true, getCreds = true } = {}, + ) => + test + .do((ctx) => { + ctx.userConfig = new ConfigData(); + for (let i = 1; i <= addProjects; i++) { + ctx.userConfig.addProject(`profile${i}`, constants.FAKE_ACCOUNT_SID); + } + for (let i = 1; i <= addProfiles; i++) { + ctx.userConfig.addProfile( + `configProfile${i}`, + constants.FAKE_ACCOUNT_SID, + '', + `${constants.FAKE_API_KEY}configProfile${i}`, + constants.FAKE_API_SECRET, + ); + } + }) + .twilioCliEnv(Config) + .twilioCreateCommand(ProfilesPort, commandArgs) + .stdout() + .stderr() + .do((ctx) => { + const fakePrompt = sinon.stub(); + fakePrompt.onFirstCall().resolves({ + confirmation: portProfiles, + }); + + ctx.testCmd.inquirer.prompt = fakePrompt; + }) + .do((ctx) => { + ctx.testCmd.secureStorage.removeCredentials = () => { + return removeCred; + }; + }) + .do((ctx) => { + ctx.testCmd.secureStorage.getCredentials = (projectId) => { + return { + apiKey: getCreds === true ? constants.FAKE_API_KEY : 'error', + apiSecret: getCreds === true ? constants.FAKE_API_SECRET : 'error_message', + }; + }; + }); + + setup([], { portProfiles: true, removeCred: true }) + .do((ctx) => ctx.testCmd.run()) + .it('should port all projects to profiles in config', (ctx) => { + expect(ctx.testCmd.userConfig.projects).to.have.length(0); + expect(Object.keys(ctx.testCmd.userConfig.profiles)).to.have.length(7); + expect(ctx.stderr).to.contain('Profiles port process complete!'); + expect(ctx.stderr).to.not.contain('All profiles already ported to config file!'); + expect(ctx.stderr).to.not.contain('Failed: Unable to retrieve credentials from Keytar.'); + }); + + setup(['profile3'], { portProfiles: true, removeCred: true }) + .do((ctx) => ctx.testCmd.run()) + .it('Should only port profile3 to profiles', (ctx) => { + expect(ctx.testCmd.userConfig.projects).to.have.length(3); + expect(Object.keys(ctx.testCmd.userConfig.profiles)).to.have.length(4); + expect(ctx.stderr).to.not.contain('All profiles already ported to config file!'); + expect(ctx.stderr).to.not.contain('Failed: Unable to retrieve credentials from Keytar.'); + expect(ctx.testCmd.userConfig.profiles.profile3.accountSid).to.equal(constants.FAKE_ACCOUNT_SID); + expect(ctx.testCmd.userConfig.profiles.profile3.region).to.be.undefined; + expect(ctx.testCmd.userConfig.profiles.profile3.apiKey).to.equal(constants.FAKE_API_KEY); + expect(ctx.testCmd.userConfig.profiles.profile3.apiSecret).to.equal(constants.FAKE_API_SECRET); + }); + + setup(['profileNotExisting'], { portProfiles: true, removeCred: true }) + .do((ctx) => ctx.testCmd.run()) + .it('should not port any non-existing profile', (ctx) => { + expect(ctx.testCmd.userConfig.projects).to.have.length(4); + expect(Object.keys(ctx.testCmd.userConfig.profiles)).to.have.length(3); + expect(ctx.stderr).to.contain( + "Unable to port keys for profile profileNotExisting since it doesn't exist or has already been ported!", + ); + }); + + setup(['profile1'], { portProfiles: true, removeCred: false }) + .do((ctx) => ctx.testCmd.run()) + .it('should ignore keytar profile removal', (ctx) => { + expect(ctx.stderr).to.contain('Unable to clean-up credentials from keytar for profile profile1'); + expect(ctx.stderr).to.contain('Porting profile1... Done!'); + expect(ctx.testCmd.userConfig.projects).to.have.length(3); + expect(Object.keys(ctx.testCmd.userConfig.profiles)).to.have.length(4); + expect(ctx.testCmd.userConfig.profiles.profile1.accountSid).to.equal(constants.FAKE_ACCOUNT_SID); + expect(ctx.testCmd.userConfig.profiles.profile1.region).to.be.undefined; + expect(ctx.testCmd.userConfig.profiles.profile1.apiKey).to.equal(constants.FAKE_API_KEY); + expect(ctx.testCmd.userConfig.profiles.profile1.apiSecret).to.equal(constants.FAKE_API_SECRET); + }); + + setup([], { portProfiles: false }) + .do((ctx) => ctx.testCmd.run()) + .it('should not not initiate port process if not confirmed by user', (ctx) => { + expect(ctx.stderr).to.contain('Abort!'); + expect(ctx.stderr).to.not.contain('Profiles port process complete!'); + expect(ctx.testCmd.userConfig.projects).to.have.length(4); + expect(Object.keys(ctx.testCmd.userConfig.profiles)).to.have.length(3); + }); + + setup([], { addProjects: 0, portProfiles: true, removeCred: true }) + .do((ctx) => ctx.testCmd.run()) + .it('should inform in case of no profiles to port', (ctx) => { + expect(ctx.testCmd.userConfig.projects).to.have.length(0); + expect(Object.keys(ctx.testCmd.userConfig.profiles)).to.have.length(3); + expect(ctx.stderr).to.contain('No profiles have keys in keytar. Nothing to do.'); + }); + + setup(['profile2'], { portProfiles: true, removeCred: true, getCreds: false }) + .do((ctx) => ctx.testCmd.run()) + .it('Should not port profile in case credentials can not be fetched from keytar', (ctx) => { + expect(ctx.testCmd.userConfig.projects).to.have.length(4); + expect(Object.keys(ctx.testCmd.userConfig.profiles)).to.have.length(3); + expect(ctx.stderr).to.contain('Porting profile2... Failed: Unable to retrieve credentials from keytar.'); + }); + }); + }); +}); From ceb894c8a603f1b861e355ef3305c37bd3679b7c Mon Sep 17 00:00:00 2001 From: ravali-rimmalapudi <83863595+ravali-rimmalapudi@users.noreply.github.com> Date: Wed, 7 Jul 2021 01:57:11 +0530 Subject: [PATCH 07/18] chore: Throw alert when active profile is not set (#267) * Fetching profile by order if active profile is not set 1. Added the warning when active profile is NULL and no environment variable is set in case of profile create/update. 2. Added/updated the corresponding test cases. * Addressed the comments --- src/commands/profiles/create.js | 3 ++ src/commands/profiles/list.js | 5 +- test/commands/phone-numbers/update.test.js | 1 + test/commands/profiles/list.test.js | 58 +++++++++++++++++++++- test/commands/profiles/remove.test.js | 18 +++++-- 5 files changed, 79 insertions(+), 6 deletions(-) diff --git a/src/commands/profiles/create.js b/src/commands/profiles/create.js index 03663d801..5da59a9f0 100644 --- a/src/commands/profiles/create.js +++ b/src/commands/profiles/create.js @@ -33,6 +33,9 @@ class ProfilesCreate extends BaseCommand { await this.loadProfileId(); await this.saveCredentials(); this.logger.info(`Saved ${this.profileId}.`); + if (!this.userConfig.getActiveProfile()) { + this.logger.warn(`You don't have any active profile set, run "twilio profiles:use" to set a profile as active`); + } } else { this.cancel(); } diff --git a/src/commands/profiles/list.js b/src/commands/profiles/list.js index 9bdd9839d..7ed23c28f 100644 --- a/src/commands/profiles/list.js +++ b/src/commands/profiles/list.js @@ -16,7 +16,7 @@ class ProfilesList extends BaseCommand { this.userConfig.projects.unshift(strippedEnvProfile); this.userConfig.setActiveProfile(strippedEnvProfile.id); } - const profiles = this.userConfig.projects; + const profiles = this.userConfig.projects.slice(0); Object.keys(this.userConfig.profiles).forEach((id) => profiles.push({ id, @@ -31,8 +31,9 @@ class ProfilesList extends BaseCommand { } const activeProfile = this.userConfig.getActiveProfile(); profiles.forEach((p) => { - p.active = p.id === activeProfile.id; + p.active = activeProfile ? p.id === activeProfile.id : false; }); + this.output(profiles); } else { this.logger.warn(`No profiles have been configured. Run ${chalk.bold('twilio profiles:create')} to create one!`); diff --git a/test/commands/phone-numbers/update.test.js b/test/commands/phone-numbers/update.test.js index 54369e50f..94beb0a5d 100644 --- a/test/commands/phone-numbers/update.test.js +++ b/test/commands/phone-numbers/update.test.js @@ -38,6 +38,7 @@ describe('commands', () => { .do((ctx) => { ctx.userConfig = new ConfigData(); ctx.userConfig.addProfile('default', constants.FAKE_ACCOUNT_SID); + ctx.userConfig.setActiveProfile('default'); if (promptAcked) { ctx.userConfig.ackPrompt('ngrok-warning'); diff --git a/test/commands/profiles/list.test.js b/test/commands/profiles/list.test.js index 2695d5327..4b53b25f1 100644 --- a/test/commands/profiles/list.test.js +++ b/test/commands/profiles/list.test.js @@ -26,6 +26,7 @@ describe('commands', () => { constants.FAKE_API_KEY, constants.FAKE_API_SECRET, ); + ctx.userConfig.setActiveProfile('profile1'); }) .twilioCliEnv(Config) .stdout() @@ -39,6 +40,29 @@ describe('commands', () => { expect(ctx.stderr).to.equal(''); }); + test + .do((ctx) => { + ctx.userConfig = new ConfigData(); + ctx.userConfig.addProfile( + 'profile1', + constants.FAKE_ACCOUNT_SID, + '', + constants.FAKE_API_KEY, + constants.FAKE_API_SECRET, + ); + }) + .twilioCliEnv(Config) + .stdout() + .stderr() + .twilioCommand(ProfilesList, []) + .it('runs profiles:list with 1 profile without active profile', (ctx) => { + expect(ctx.stdout).to.contain('profile1'); + expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); + expect(ctx.stdout).to.not.contain('Region'); + expect(ctx.stdout.match(/false/g)).to.have.length(1); + expect(ctx.stderr).to.equal(''); + }); + test .do(() => { process.env.TWILIO_ACCOUNT_SID = constants.FAKE_ACCOUNT_SID; @@ -73,12 +97,13 @@ describe('commands', () => { constants.FAKE_API_KEY, constants.FAKE_API_SECRET, ); + ctx.userConfig.setActiveProfile('profile1'); }) .twilioCliEnv(Config) .stdout() .stderr() .twilioCommand(ProfilesList, []) - .it('runs profiles:list with multiple profiles', (ctx) => { + .it('runs profiles:list with multiple profiles and with active profile', (ctx) => { expect(ctx.stdout).to.contain('profile1'); expect(ctx.stdout).to.contain('profile2'); expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); @@ -87,6 +112,37 @@ describe('commands', () => { expect(ctx.stderr).to.equal(''); }); + test + .do((ctx) => { + ctx.userConfig = new ConfigData(); + ctx.userConfig.addProfile( + 'profile1', + constants.FAKE_ACCOUNT_SID, + '', + constants.FAKE_API_KEY, + constants.FAKE_API_SECRET, + ); + ctx.userConfig.addProfile( + 'profile2', + constants.FAKE_ACCOUNT_SID, + '', + constants.FAKE_API_KEY, + constants.FAKE_API_SECRET, + ); + }) + .twilioCliEnv(Config) + .stdout() + .stderr() + .twilioCommand(ProfilesList, []) + .it('runs profiles:list with multiple profiles without a active profile', (ctx) => { + expect(ctx.stdout).to.contain('profile1'); + expect(ctx.stdout).to.contain('profile2'); + expect(ctx.stdout).to.contain(constants.FAKE_ACCOUNT_SID); + expect(ctx.stdout).to.not.contain('Region'); + expect(ctx.stdout.match(/false/g)).to.have.length(2); + expect(ctx.stderr).to.equal(''); + }); + test .do((ctx) => { ctx.userConfig = new ConfigData(); diff --git a/test/commands/profiles/remove.test.js b/test/commands/profiles/remove.test.js index 0a8177fb8..1971076fb 100644 --- a/test/commands/profiles/remove.test.js +++ b/test/commands/profiles/remove.test.js @@ -9,7 +9,14 @@ describe('commands', () => { describe('remove', () => { const setup = ( commandArgs = [], - { addProjects = 4, addProfiles = 3, deleteProfile = true, deleteKey = true, removeCred = true } = {}, + { + addProjects = 4, + addProfiles = 3, + deleteProfile = true, + deleteKey = true, + removeCred = true, + setActiveProfile = '', + } = {}, ) => test .do((ctx) => { @@ -27,6 +34,11 @@ describe('commands', () => { ); } }) + .do((ctx) => { + if (setActiveProfile) { + ctx.userConfig.setActiveProfile(setActiveProfile); + } + }) .twilioCliEnv(Config) .twilioCreateCommand(ProfilesRemove, commandArgs) .stdout() @@ -93,7 +105,7 @@ describe('commands', () => { expect(ctx.stderr).to.contain('configuration saved'); }); - setup(['configProfile1'], { addProjects: 0 }) + setup(['configProfile1'], { addProjects: 0, setActiveProfile: 'configProfile1' }) .nock('https://api.twilio.com', (api) => { api .delete( @@ -145,7 +157,7 @@ describe('commands', () => { expect(ctx.stderr).to.contain('configuration saved'); }); - setup(['configProfile1'], { addProjects: 0, addProfiles: 1 }) + setup(['configProfile1'], { addProjects: 0, addProfiles: 1, setActiveProfile: 'configProfile1' }) .nock('https://api.twilio.com', (api) => { api .delete( From 5e564e729cc6b51afbeef1ff57ef89756ebc1751 Mon Sep 17 00:00:00 2001 From: Anuj Badhwar Date: Mon, 19 Jul 2021 10:55:01 +0530 Subject: [PATCH 08/18] chore: change post install messaging behaviour (#269) --- src/services/post-install-utility.js | 58 ++++++++++++++++++ test/services/post-install-utility.test.js | 70 ++++++++++++++++++++++ welcome.js | 17 +----- 3 files changed, 131 insertions(+), 14 deletions(-) create mode 100644 src/services/post-install-utility.js create mode 100644 test/services/post-install-utility.test.js diff --git a/src/services/post-install-utility.js b/src/services/post-install-utility.js new file mode 100644 index 000000000..277708cbf --- /dev/null +++ b/src/services/post-install-utility.js @@ -0,0 +1,58 @@ +/* eslint-disable no-console */ +const chalk = require('chalk'); +const { Config } = require('@twilio/cli-core').services.config; +const { configureEnv } = require('@twilio/cli-core'); + +const PORT_WARNING = `Profiles exist with API keys in system keychain. Please run ${chalk.bold( + 'twilio profiles:port', +)} to port all keys to the config file`; + +class PostInstallDisplayManager { + constructor(configDir, userConfig) { + configureEnv(); + this.configDir = configDir || process.env.TWILIO_CONFIG_DIR; + this.configFile = new Config(this.configDir); + this.userConfig = userConfig; + } + + hasProjects() { + return this.userConfig.projects && this.userConfig.projects.length > 0; + } + + hasPreConfiguredProfiles() { + return this.hasProjects() || (this.userConfig.profiles && Object.keys(this.userConfig.profiles).length > 0); + } + + displayGrid() { + console.log(); + console.log('*************************************************************************'); + console.log('* *'); + console.log('* To get started, please create a twilio-cli profile: *'); + console.log('* *'); + console.log('* twilio login *'); + console.log('* *'); + console.log('* OR *'); + console.log('* *'); + console.log('* twilio profiles:create *'); + console.log('* *'); + console.log('*************************************************************************'); + console.log(); + } + + async displayMessage() { + this.userConfig = this.userConfig || (await this.configFile.load()); + + if (!this.hasPreConfiguredProfiles()) { + this.displayGrid(); + } + + if (this.hasProjects()) { + console.warn(chalk.yellowBright(` » ${PORT_WARNING}`)); + } + } +} + +module.exports = { + PostInstallDisplayManager, + PORT_WARNING, +}; diff --git a/test/services/post-install-utility.test.js b/test/services/post-install-utility.test.js new file mode 100644 index 000000000..88c797ba0 --- /dev/null +++ b/test/services/post-install-utility.test.js @@ -0,0 +1,70 @@ +/* eslint-disable no-console */ +const tmp = require('tmp'); +const { expect, test, constants } = require('@twilio/cli-test'); +const sinon = require('sinon'); +const { ConfigData } = require('@twilio/cli-core').services.config; + +const { PostInstallDisplayManager } = require('../../src/services/post-install-utility'); + +describe('services', () => { + describe('post-install-utility', () => { + beforeEach(() => { + tempConfigDir = tmp.dirSync({ unsafeCleanup: true }); + warnSpy = sinon.spy(console, 'warn'); + logSpy = sinon.spy(console, 'log'); + }); + + afterEach(() => { + tempConfigDir.removeCallback(); + warnSpy.restore(); + logSpy.restore(); + }); + + test.it('should display grid when no profiles or projects configured', () => { + const configData = new ConfigData(); + const displayManager = new PostInstallDisplayManager(tempConfigDir.name, configData); + + displayManager.displayMessage(); + expect(displayManager.userConfig).to.not.be.undefined; + expect(console.warn.called).to.be.false; + expect(console.log.calledWith(sinon.match('twilio profiles:create'))).to.be.true; + expect(console.log.calledWith(sinon.match('twilio login'))).to.be.true; + }); + + test.it('should display port warning if projects set', () => { + const configData = new ConfigData(); + configData.addProject('DUMMY_PROJECT', constants.FAKE_ACCOUNT_SID); + const displayManager = new PostInstallDisplayManager(tempConfigDir.name, configData); + + displayManager.displayMessage(); + expect(console.warn.calledOnce).to.be.true; + expect(console.warn.calledWith(sinon.match('twilio profiles:port'))).to.be.true; + expect(console.log.called).to.be.false; // Grid shouldn't be displayed + }); + + test.it('should not display port warning if profiles set, but projects not set', () => { + const configData = new ConfigData(); + configData.addProfile( + 'DUMMY_PROJECT', + constants.FAKE_ACCOUNT_SID, + '', + constants.FAKE_API_KEY, + constants.FAKE_API_SECRET, + ); + + const displayManager = new PostInstallDisplayManager(tempConfigDir.name, configData); + + displayManager.displayMessage(); + expect(console.warn.called).to.be.false; + expect(console.log.called).to.be.false; + }); + + test.it('should initialise as per system defaults', async () => { + const displayManager = new PostInstallDisplayManager(); + expect(displayManager.configDir).to.equal(process.env.TWILIO_CONFIG_DIR); + expect(displayManager.userConfig).to.be.undefined; + await displayManager.displayMessage(); + expect(displayManager.userConfig).to.not.be.undefined; // Should load from config directory + }); + }); +}); diff --git a/welcome.js b/welcome.js index 7700ed888..751e9ff0b 100644 --- a/welcome.js +++ b/welcome.js @@ -1,14 +1,3 @@ -/* eslint-disable no-console */ -console.log(); -console.log('*************************************************************************'); -console.log('* *'); -console.log('* To get started, please create a twilio-cli profile: *'); -console.log('* *'); -console.log('* twilio login *'); -console.log('* *'); -console.log('* OR *'); -console.log('* *'); -console.log('* twilio profiles:create *'); -console.log('* *'); -console.log('*************************************************************************'); -console.log(); +const { PostInstallDisplayManager } = require('./src/services/post-install-utility'); + +new PostInstallDisplayManager().displayMessage(); From 434ab5157eff4f02010a26d9063ac4516e7d4a5d Mon Sep 17 00:00:00 2001 From: ravali-rimmalapudi <83863595+ravali-rimmalapudi@users.noreply.github.com> Date: Tue, 20 Jul 2021 23:51:21 +0530 Subject: [PATCH 09/18] feat: Added twilio config to list/set edge configuration (#270) * feat: Added twilio config to list/set edge configuration * Added the missing changes * Addressed the review comments. * Updated the missing messages * Updated the review comments. * Moved the common code functionality to utility. * Added the missing change. * Minor refactoring in the getFromEnvironment method. --- package.json | 3 ++ src/commands/config/list.js | 25 +++++++++ src/commands/config/set.js | 89 +++++++++++++++++++++++++++++++ src/services/config-utility.js | 6 +++ test/commands/config/list.test.js | 51 ++++++++++++++++++ test/commands/config/set.test.js | 89 +++++++++++++++++++++++++++++++ 6 files changed, 263 insertions(+) create mode 100644 src/commands/config/list.js create mode 100644 src/commands/config/set.js create mode 100644 src/services/config-utility.js create mode 100644 test/commands/config/list.test.js create mode 100644 test/commands/config/set.test.js diff --git a/package.json b/package.json index 85c93d138..bbe4d7e5e 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,9 @@ }, "profiles": { "description": "manage credentials for Twilio profiles" + }, + "config": { + "description": "manage Twilio CLI configurations" } }, "hooks": { diff --git a/src/commands/config/list.js b/src/commands/config/list.js new file mode 100644 index 000000000..2c76bc89c --- /dev/null +++ b/src/commands/config/list.js @@ -0,0 +1,25 @@ +const { BaseCommand } = require('@twilio/cli-core').baseCommands; + +const { availableConfigs, getFromEnvironment } = require('../../services/config-utility'); + +class ConfigList extends BaseCommand { + async run() { + await super.run(); + const configData = []; + availableConfigs.forEach((config) => { + let configEnvValue = getFromEnvironment(config); + if (configEnvValue) { + configEnvValue += '[env]'; + } + configData.push({ + configName: config, + value: configEnvValue || this.userConfig[config], + }); + }); + this.output(configData); + } +} +ConfigList.description = 'list Twilio CLI configurations.'; + +ConfigList.flags = BaseCommand.flags; +module.exports = ConfigList; diff --git a/src/commands/config/set.js b/src/commands/config/set.js new file mode 100644 index 000000000..7aa17f243 --- /dev/null +++ b/src/commands/config/set.js @@ -0,0 +1,89 @@ +const { TwilioCliError } = require('@twilio/cli-core/src/services/error'); +const { BaseCommand } = require('@twilio/cli-core').baseCommands; +const { flags } = require('@oclif/command'); + +const { availableConfigs, getFromEnvironment } = require('../../services/config-utility'); + +class ConfigSet extends BaseCommand { + async run() { + await super.run(); + let isError = true; + let isUserConfigUpdated = false; + for (const flag of availableConfigs) { + if (this.flags[flag] !== undefined) { + isError = false; + this.preWarnings(flag); + if (this.flags[flag] === '') { + isUserConfigUpdated = await this.removeConfig(flag, isUserConfigUpdated); + continue; + } + if (await this.isOverwrite(flag)) { + this.userConfig[flag] = this.userConfig.sanitize(this.flags[flag]); + isUserConfigUpdated = true; + } + } + } + if (isError) { + throw new TwilioCliError( + `No configuration is added to set. Run "twilio configs:set --help" to see how to set a configurations.`, + ); + } + if (isUserConfigUpdated) { + await this.saveConfiguration(); + } + } + + preWarnings(flag) { + const flagEnvValue = getFromEnvironment(flag); + if (flagEnvValue) { + this.logger.warn(`There is an environment variable already set for ${flag} : ${flagEnvValue}`); + } + } + + async removeConfig(flag, isUserConfigUpdated) { + if (this.userConfig[flag]) { + const verdict = await this.confirmDialog('Remove', flag); + if (verdict) { + isUserConfigUpdated = true; + this.userConfig[flag] = undefined; + } + } else { + this.logger.info(`There is no existing ${flag} to remove.`); + } + return isUserConfigUpdated; + } + + async isOverwrite(flag) { + if (this.userConfig[flag]) { + return this.confirmDialog('Overwrite', flag); + } + return true; + } + + async saveConfiguration() { + const configSavedMessage = await this.configFile.save(this.userConfig); + this.logger.info(configSavedMessage); + } + + async confirmDialog(type, flag) { + const confirm = await this.inquirer.prompt([ + { + type: 'confirm', + name: type, + message: `${type} existing ${flag} value?`, + default: false, + }, + ]); + return confirm[type]; + } +} +ConfigSet.description = 'update Twilio CLI configurations.'; + +ConfigSet.flags = { + edge: flags.string({ + char: 'e', + description: 'Sets an Edge configuration.', + }), + ...BaseCommand.flags, // To add the same flags as BaseCommand +}; +module.exports = ConfigSet; diff --git a/src/services/config-utility.js b/src/services/config-utility.js new file mode 100644 index 000000000..24aaab4f9 --- /dev/null +++ b/src/services/config-utility.js @@ -0,0 +1,6 @@ +const availableConfigs = ['edge']; +function getFromEnvironment(config) { + const configEnv = `TWILIO_${config.toUpperCase()}`; + return process.env[configEnv]; +} +module.exports = { availableConfigs, getFromEnvironment }; diff --git a/test/commands/config/list.test.js b/test/commands/config/list.test.js new file mode 100644 index 000000000..7b4514839 --- /dev/null +++ b/test/commands/config/list.test.js @@ -0,0 +1,51 @@ +const { expect, test } = require('@twilio/cli-test'); +const { Config, ConfigData } = require('@twilio/cli-core').services.config; + +const ConfigList = require('../../../src/commands/config/list'); + +describe('commands', () => { + describe('config', () => { + describe('list', () => { + const listConfig = ({ addEdge = '' } = {}) => + test + .do((ctx) => { + ctx.userConfig = new ConfigData(); + if (addEdge) { + ctx.userConfig.edge = addEdge; + } + }) + .twilioCliEnv(Config) + .twilioCreateCommand(ConfigList, []) + .stdout() + .stderr(); + + listConfig({ addEdge: 'testEdge' }) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:list, should list config variables', (ctx) => { + expect(ctx.stdout).to.contain('Config Name'); + expect(ctx.stdout).to.contain('Value'); + expect(ctx.stdout).to.contain('testEdge'); + expect(ctx.stdout).to.contain('edge'); + }); + listConfig({ addEdge: 'testEdge' }) + .do((ctx) => { + process.env.TWILIO_EDGE = 'fakeEdge'; + return ctx.testCmd.run(); + }) + .it('runs config:list, should prioritize environment if both environment and config edge set', (ctx) => { + expect(ctx.stdout).to.contain('Config Name'); + expect(ctx.stdout).to.contain('Value'); + expect(ctx.stdout).to.contain('fakeEdge[env]'); + expect(ctx.stdout).to.contain('edge'); + }); + listConfig({}) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:list, should list empty as edge is not set', (ctx) => { + expect(ctx.stdout).to.contain('Config Name'); + expect(ctx.stdout).to.contain('Value'); + expect(ctx.stdout).to.contain(''); + expect(ctx.stdout).to.contain('edge'); + }); + }); + }); +}); diff --git a/test/commands/config/set.test.js b/test/commands/config/set.test.js new file mode 100644 index 000000000..ca5b9da55 --- /dev/null +++ b/test/commands/config/set.test.js @@ -0,0 +1,89 @@ +const sinon = require('sinon'); +const { expect, test } = require('@twilio/cli-test'); +const { Config, ConfigData } = require('@twilio/cli-core').services.config; + +const ConfigSet = require('../../../src/commands/config/set'); + +describe('commands', () => { + describe('config', () => { + describe('set', () => { + const createConfig = (commandArgs = [], { addEdge = '', updateEdge = true, removeEdge = true } = {}) => + test + .do((ctx) => { + ctx.userConfig = new ConfigData(); + if (addEdge) { + ctx.userConfig.edge = addEdge; + } + }) + .twilioCliEnv(Config) + .twilioCreateCommand(ConfigSet, commandArgs) + .do((ctx) => { + const fakePrompt = sinon.stub(); + fakePrompt.onFirstCall().resolves({ + Overwrite: updateEdge, + Remove: removeEdge, + }); + ctx.testCmd.inquirer.prompt = fakePrompt; + }) + .stdout() + .stderr(); + + createConfig(['--edge=createEdge']) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --edge=createEdge', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.contain('createEdge'); + expect(ctx.stderr).to.contain('configuration saved'); + }); + createConfig(['--edge=updateEdge'], { addEdge: 'createEdge' }) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --edge=updateEdge', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.contain('updateEdge'); + expect(ctx.stderr).to.contain('configuration saved'); + }); + createConfig(['--edge=updateEdge'], { addEdge: 'createEdge', updateEdge: false }) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --edge=updateEdge with overwrite as false', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.contain('createEdge'); + expect(ctx.stderr).to.not.contain('There is an environment variable already set'); + }); + createConfig(['--edge=createEdge'], {}) + .do((ctx) => { + process.env.TWILIO_EDGE = 'envEdge'; + return ctx.testCmd.run(); + }) + .it('runs config:set --edge=createEdge, will show warning if environment variable exists', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.contain('createEdge'); + expect(ctx.stderr).to.contain('There is an environment variable already set for edge : envEdge'); + expect(ctx.stderr).to.contain('configuration saved'); + }); + createConfig(['--edge='], { addEdge: 'createEdge' }) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --edge= ,should remove the edge from configuration', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.be.undefined; + expect(ctx.stderr).to.contain('configuration saved'); + }); + createConfig(['--edge='], { addEdge: 'createEdge', removeEdge: false }) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --edge= ,should not remove the edge from configuration', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.contain('createEdge'); + expect(ctx.stderr).to.not.contain('configuration saved'); + }); + createConfig(['--edge='], {}) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --edge= ,should throw error as there is no existing edge', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.be.undefined; + expect(ctx.stderr).to.contain('There is no existing edge to remove.'); + }); + createConfig(['--edge='], {}) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --edge= ,should throw error as there is no existing edge', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.be.undefined; + expect(ctx.stderr).to.contain('There is no existing edge to remove.'); + }); + createConfig([], {}) + .do((ctx) => ctx.testCmd.run()) + .catch(/No configuration is added to set. Run "twilio configs:set --help" to see how to set a configurations./) + .it('runs config:set ,should throw error as there are no configurations/flags provided.'); + }); + }); +}); From fe510738a609975127ace383650e5e34fa499a20 Mon Sep 17 00:00:00 2001 From: ravali-rimmalapudi <83863595+ravali-rimmalapudi@users.noreply.github.com> Date: Thu, 29 Jul 2021 01:10:36 +0530 Subject: [PATCH 10/18] chore - Updated package.json to point to cli-core release candidate and cli-test new release (#273) * [chore] - Updating package.json to point to cli-core release candidate and cli-test new release * Removed the cli-test version upgrade. * Added the shortform --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bbe4d7e5e..bcae5342a 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@oclif/plugin-plugins": "^1.8.3", "@oclif/plugin-warn-if-update-available": "^1.7.0", "@sendgrid/mail": "^7.2.1", - "@twilio/cli-core": "^5.9.1", + "@twilio/cli-core": "twilio/twilio-cli-core#5.26.1-rc", "chalk": "^4.1.0", "cli-ux": "^5.6.2", "file-type": "^14.6.2", From 89cebb9413ced71dfc0cb9223dcde5f47a6bcfd6 Mon Sep 17 00:00:00 2001 From: ravali-rimmalapudi <83863595+ravali-rimmalapudi@users.noreply.github.com> Date: Thu, 29 Jul 2021 10:25:59 +0530 Subject: [PATCH 11/18] Updated the cli-core version. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bcae5342a..76f9f103e 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@oclif/plugin-plugins": "^1.8.3", "@oclif/plugin-warn-if-update-available": "^1.7.0", "@sendgrid/mail": "^7.2.1", - "@twilio/cli-core": "twilio/twilio-cli-core#5.26.1-rc", + "@twilio/cli-core": "twilio/twilio-cli-core#5.27.0-rc", "chalk": "^4.1.0", "cli-ux": "^5.6.2", "file-type": "^14.6.2", From d97d98b42bd8ee71ecb02d726befc1da94ee87f2 Mon Sep 17 00:00:00 2001 From: ravali-rimmalapudi <83863595+ravali-rimmalapudi@users.noreply.github.com> Date: Thu, 29 Jul 2021 15:49:55 +0530 Subject: [PATCH 12/18] fix: Update release-feature-branch with main (#275) * update slack token * feat: add assets plugin to available plugins (#261) * [Librarian] Regenerated @ 8ec9e610ad617fae257b54113f6d7fa08c457a87 * Release 2.25.0 * chore: updating plugin-help version (#268) Co-authored-by: kridai * [Librarian] Regenerated @ 7bffdec2c05f6a34b94cebd095be5b1b30705f5d * Release 2.26.0 * [Librarian] Regenerated @ 54ad9271a294331f8d036e4a3fc173bb98ecdb82 * Release 2.27.0 Co-authored-by: Elmer Thomas Co-authored-by: Phil Nash Co-authored-by: Twilio Co-authored-by: kridai Co-authored-by: kridai --- .travis.yml | 2 +- CHANGES.md | 62 +++ package-lock.json | 1168 ++++++++++++++++++--------------------- package.json | 4 +- src/services/plugins.js | 1 + 5 files changed, 592 insertions(+), 645 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9c352a8ab..71fee41c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,4 +30,4 @@ notifications: on_success: never on_failure: change rooms: - secure: KQn/oMX7mIUMqDWd/ppQTB8iRtIFPfcZn+/rZfgZrJbPR5nOKdU+Kl+/C3721SueZHsau0QM8g1rl9XVlFweHOamkd+nHeD1hu4eUPIGfDPwmTBiutKgwinDXqCCFAPr2VDzwJiBkwRPO4TW1iHmGItrX7/Ew98mA4l9YY0CSmz/9ifXD/1o1rd1xJYYBcmzjB9+L4BubtSCHW8xPfDmZMESfz3aaptQ/Ad2CDg5srQJXbHpEdhgXH3vIG41t4L7LXomi01jQYT68npUS60JyUD1HiVrgPY8VYKIpw9idQO2Vote5eutZLy9EhKQbtqxh7Cq9pmVnyYnohGE36NaaoAk/uXXRLoCjXIHOcjrqy5xaTnYo04Xoq40olS2iEqMtcsh/itGCJTlvX0IJ7nACPMO/R0isFRoipWNDfgTulWgUaMohZlQxIVitYA7G3Os0rrV5CdhXQGghS47rhIHtYz3NhZIlBLW11RrWkB0peMhJIkA7DLfYaLLBNoTyZ0TNgW/ruAX63smNa+zors4timghGSuIwmADkcaVeYC/csnn+PM3ux/M4u2tbR9rzlcLlt4l9g8HJpwXNrMBqy4/E2Gb8EJtADookUt4VDeQU6TAgLIB62dLkDzMgt0BrAgv+6IVg363vA3z+vD4zQwXP0cDpea2ThLFvRbJhVG4mI= + secure: RfnO47KWRcZhzznK2zU7AQBQd8fmuXQFSCNQ0CpPM/4GywA6XBSVHbnI4uEr7lmEdB2WKktGHtKcfC0H/tCFkxBzovlkiHEsOPrc6/4RsJKrPYjO1qX35ttdiILkmClyo2V8cPOu2m5HhTYddn9wuKN/A7tjjNRHz8ani+CmVBnXMkbyRW5DlY1/q99Y4y2GLMhERyMqNZyeaGDoR4UOl2OuRExBZeN2E7gtUhf5LyCsD/995DQRHx0cJrAZh2tMcubdGOKH0hYVohUFjgeTBOu1Fcwn845i3cXp1IG5cx83EEsNSvcmux96EpMTlu+D7ABjFtUjTFXuDDjXhWGhdC3S3C5/UbVX4tNGbGq2ErSGS4TP8WRPthVyFnvOY2j4jWLmiDDUGyVY8rPsKd2PsO9JDMnL9GCJxwUlK222oAqdkObntm8bEjUk8YODgX7SsgeJ4s5Zp5nKnG/GBiWZAYp2WbiSMltEJiL/jTpAgUMfhVAefgHMi362TC97y/nYcUhGTftMqlx+ca3FlEayV0CTQd9CSjBKuZ/IGeE3qlZbZhWqQMt9gtvNaZ5yToAxNxfxIwJqgdaRlQ5ZJRJESrTXVRIo2v4+9qaofK57trz05oLREyTAUk/8hWGFTcOfXSQw/Up1ycGjtLSLFPuU3PJ7px4lQGfATonv1SgXfrg= diff --git a/CHANGES.md b/CHANGES.md index 6ca6eaf44..249be601e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,68 @@ twilio-cli changelog ===================== +[2021-07-29] Version 2.27.0 +--------------------------- +**Api** +- Added `domain_sid` in sip_credential_list_mapping and sip_ip_access_control_list_mapping APIs **(breaking change)** + +**Conversations** +- Expose ParticipantConversations resource + +**Taskrouter** +- Adding `links` to the activity resource + +**Verify** +- Added a `Version` to Verify Factors `Webhooks` to add new fields without breaking old Webhooks. + + +[2021-07-15] Version 2.26.0 +--------------------------- +**Library - Chore** +- [PR #268](https://github.com/twilio/twilio-cli/pull/268): updating plugin-help version. Thanks to [@kridai](https://github.com/kridai)! + +**Conversations** +- Changed `last_read_message_index` and `unread_messages_count` type in User Conversation's resource **(breaking change)** +- Expose UserConversations resource + +**Messaging** +- Add brand_score field to brand registration responses + +**Supersim** +- Add Billing Period resource for the Super Sim Pilot +- Add List endpoint to Billing Period resource for Super Sim Pilot +- Add Fetch endpoint to Billing Period resource for Super Sim Pilot + +**Taskrouter** +- Update `transcribe` & `transcription_configuration` form params in Reservation update endpoint to have private visibility **(breaking change)** + + +[2021-06-22] Version 2.25.0 +--------------------------- +**Library - Feature** +- [PR #261](https://github.com/twilio/twilio-cli/pull/261): add assets plugin to available plugins. Thanks to [@philnash](https://github.com/philnash)! + +**Api** +- Update `status` enum for Messages to include 'canceled' +- Update `update_status` enum for Messages to include 'canceled' + +**Conversations** +- Read-only Conversation Email Binding property `binding` + +**Events** +- join Sinks and Subscriptions service + +**Taskrouter** +- Add `transcribe` & `transcription_configuration` form params to Reservation update endpoint + +**Trusthub** +- Corrected the sid for policy sid in customer_profile_evaluation.json and trust_product_evaluation.json **(breaking change)** + +**Verify** +- Improved the documentation of `challenge` adding the maximum and minimum expected lengths of some fields. +- Improve documentation regarding `notification` by updating the documentation of the field `ttl`. + + [2021-05-19] Version 2.24.0 --------------------------- **Library - Fix** diff --git a/package-lock.json b/package-lock.json index 834cba6b2..a7209dff6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,39 +1,39 @@ { "name": "twilio-cli", - "version": "2.24.0", + "version": "2.27.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.14.5" } }, "@babel/compat-data": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", - "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", + "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==", "dev": true }, "@babel/core": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz", - "integrity": "sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.3", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-module-transforms": "^7.14.2", - "@babel/helpers": "^7.14.0", - "@babel/parser": "^7.14.3", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.8.tgz", + "integrity": "sha512-/AtaeEhT6ErpDhInbXmjHcUQXH0L0TEgscfcxk1qbOvLuKCa5aZT0SOOtDKFY96/CLROwbLSKyFor6idgNaU4Q==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.8", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-module-transforms": "^7.14.8", + "@babel/helpers": "^7.14.8", + "@babel/parser": "^7.14.8", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -42,15 +42,6 @@ "source-map": "^0.5.0" }, "dependencies": { - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -60,25 +51,25 @@ } }, "@babel/generator": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", - "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", "dev": true, "requires": { - "@babel/types": "^7.14.2", + "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-compilation-targets": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", - "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", + "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", "dev": true, "requires": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.14.5", + "@babel/compat-data": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", "semver": "^6.3.0" }, "dependencies": { @@ -91,128 +82,137 @@ } }, "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", + "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.14.5" } }, "@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.14.5" } }, "@babel/helper-module-transforms": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz", - "integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz", + "integrity": "sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.14.0", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2" + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-simple-access": "^7.14.8", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8" } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-replace-supers": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.3.tgz", - "integrity": "sha512-Rlh8qEWZSTfdz+tgNV/N4gz1a0TMNwCUcENhMjHTHKp3LseYH5Jha0NSlyTQWMnjbYcwFt+bqAMqSLHVXkQ6UA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2" + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz", + "integrity": "sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==", "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.14.8" } }, "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz", + "integrity": "sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", - "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", "dev": true }, "@babel/helpers": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", - "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.8.tgz", + "integrity": "sha512-ZRDmI56pnV+p1dH6d+UN6GINGz7Krps3+270qqI9UJ4wxYThfAIcI5i7j5vXC4FJ3Wap+S9qcebxeYiqn87DZw==", "dev": true, "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0" + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8" } }, "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -237,34 +237,35 @@ } }, "@babel/parser": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.3.tgz", - "integrity": "sha512-7MpZDIfI7sUC5zWo2+foJ50CSI5lcqDehZ0lVgIhSi4bFEk94fLAKlF3Q0nzSQQ+ca0lm+O6G9ztKVBeu8PMRQ==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==", "dev": true }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.8", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -278,25 +279,25 @@ } }, "@babel/types": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz", - "integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } }, "@eslint/eslintrc": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz", - "integrity": "sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.1.1", "espree": "^7.3.0", - "globals": "^12.1.0", + "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", @@ -304,15 +305,6 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -324,15 +316,26 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true } } }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -367,25 +370,25 @@ "dev": true }, "@nodelib/fs.scandir": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", - "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "requires": { - "@nodelib/fs.stat": "2.0.4", + "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", - "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" }, "@nodelib/fs.walk": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", - "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "requires": { - "@nodelib/fs.scandir": "2.1.4", + "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, @@ -454,77 +457,6 @@ "@oclif/plugin-help": "^3", "debug": "^4.1.1", "semver": "^7.3.2" - }, - "dependencies": { - "@oclif/plugin-help": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-3.2.2.tgz", - "integrity": "sha512-SPZ8U8PBYK0n4srFjCLedk0jWU4QlxgEYLCXIBShJgOwPhTTQknkUlsEwaMIevvCU4iCQZhfMX+D8Pz5GZjFgA==", - "requires": { - "@oclif/command": "^1.5.20", - "@oclif/config": "^1.15.1", - "@oclif/errors": "^1.2.2", - "chalk": "^4.1.0", - "indent-string": "^4.0.0", - "lodash.template": "^4.4.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "widest-line": "^3.1.0", - "wrap-ansi": "^4.0.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "wrap-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-4.0.0.tgz", - "integrity": "sha512-uMTsj9rDb0/7kk1PbcbCcwvHUxp60fGDB/NNXpVa0Q+ic/e7y5+BwTxKfQ33VYgDppSwi/FBzpetYzo8s6tfbg==", - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - } } }, "@oclif/config": { @@ -541,9 +473,9 @@ }, "dependencies": { "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } } }, @@ -568,95 +500,18 @@ "tslib": "^2.0.3" }, "dependencies": { - "@oclif/plugin-help": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-3.2.2.tgz", - "integrity": "sha512-SPZ8U8PBYK0n4srFjCLedk0jWU4QlxgEYLCXIBShJgOwPhTTQknkUlsEwaMIevvCU4iCQZhfMX+D8Pz5GZjFgA==", - "dev": true, - "requires": { - "@oclif/command": "^1.5.20", - "@oclif/config": "^1.15.1", - "@oclif/errors": "^1.2.2", - "chalk": "^4.1.0", - "indent-string": "^4.0.0", - "lodash.template": "^4.4.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "widest-line": "^3.1.0", - "wrap-ansi": "^4.0.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", "dev": true - }, - "wrap-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-4.0.0.tgz", - "integrity": "sha512-uMTsj9rDb0/7kk1PbcbCcwvHUxp60fGDB/NNXpVa0Q+ic/e7y5+BwTxKfQ33VYgDppSwi/FBzpetYzo8s6tfbg==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } } } }, "@oclif/errors": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@oclif/errors/-/errors-1.3.4.tgz", - "integrity": "sha512-pJKXyEqwdfRTUdM8n5FIHiQQHg5ETM0Wlso8bF9GodczO40mF5Z3HufnYWJE7z8sGKxOeJCdbAVZbS8Y+d5GCw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@oclif/errors/-/errors-1.3.5.tgz", + "integrity": "sha512-OivucXPH/eLLlOT7FkCMoZXiaVYf8I/w1eTAM1+gKzfhALwWTusxEx7wBmW0uzvkSg/9ovWLycPaBgJbM3LOCQ==", "requires": { "clean-stack": "^3.0.0", "fs-extra": "^8.1", @@ -770,85 +625,38 @@ } }, "@oclif/plugin-help": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-2.2.3.tgz", - "integrity": "sha512-bGHUdo5e7DjPJ0vTeRBMIrfqTRDBfyR5w0MP41u0n3r7YG5p14lvMmiCXxi6WDaP2Hw5nqx3PnkAIntCKZZN7g==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-3.2.2.tgz", + "integrity": "sha512-SPZ8U8PBYK0n4srFjCLedk0jWU4QlxgEYLCXIBShJgOwPhTTQknkUlsEwaMIevvCU4iCQZhfMX+D8Pz5GZjFgA==", "requires": { - "@oclif/command": "^1.5.13", - "chalk": "^2.4.1", + "@oclif/command": "^1.5.20", + "@oclif/config": "^1.15.1", + "@oclif/errors": "^1.2.2", + "chalk": "^4.1.0", "indent-string": "^4.0.0", "lodash.template": "^4.4.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0", - "widest-line": "^2.0.1", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "widest-line": "^3.1.0", "wrap-ansi": "^4.0.0" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "widest-line": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", - "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { - "string-width": "^2.1.1" - }, - "dependencies": { - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } + "ansi-regex": "^5.0.0" } }, "wrap-ansi": { @@ -861,6 +669,11 @@ "strip-ansi": "^4.0.0" }, "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -883,9 +696,9 @@ } }, "@oclif/plugin-plugins": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@oclif/plugin-plugins/-/plugin-plugins-1.10.0.tgz", - "integrity": "sha512-lfHNiuuCrCUtH9A912T/ztxRA9lS1lCZm+gcmVWksIJG/gwKH/fMn+GdLTbRzU2k6ojtMhBblYk1RWKxUEJuzA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@oclif/plugin-plugins/-/plugin-plugins-1.10.1.tgz", + "integrity": "sha512-JDUA3NtOa4OlH8ofUBXQMTFlpEkSmeE9BxoQTD6+BeUvMgqFuZThENucRvCD00sywhCmDngmIYN59gKcXpGJeQ==", "requires": { "@oclif/color": "^0.x", "@oclif/command": "^1.5.12", @@ -923,9 +736,9 @@ } }, "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" }, "universalify": { "version": "2.0.0", @@ -1014,9 +827,9 @@ } }, "@sendgrid/mail": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-7.4.4.tgz", - "integrity": "sha512-9+dyArajxbPY7eJJAd2eps7MVsXW9E4Y294gWeMYku0/Id/qAivexidjstvkkVlZdlrHbEsGDlnMEcw2eXOiFg==", + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-7.4.5.tgz", + "integrity": "sha512-adXMvrTUOlYr7+UTigZRGSYR9vheBv1y4fF2mugn29NBdQMfcQPGLQ5vIHgSAfcboBFCagZdamZqM5FeSGU0Hw==", "requires": { "@sendgrid/client": "^7.4.3", "@sendgrid/helpers": "^7.4.3" @@ -1032,9 +845,9 @@ } }, "@sinonjs/fake-timers": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.0.5.tgz", - "integrity": "sha512-fUt6b15bjV/VW93UP5opNXJxdwZSbK1EdiwnhN7XrQrcpaOhMJpZ/CjwFpM3THpxwA+YviBUJKSuEqKlCK5alw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" @@ -1063,9 +876,9 @@ "integrity": "sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w==" }, "@twilio/cli-core": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@twilio/cli-core/-/cli-core-5.24.0.tgz", - "integrity": "sha512-8JR6pJZi3/9xoeUcpOVVS11ixVYbvq3C60vbCkgSIstm+8tV/Vmt8t9TIeJ06Ko2OkwoEDtq3mLhpUwXjt5aGA==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@twilio/cli-core/-/cli-core-5.27.0.tgz", + "integrity": "sha512-xChzpPRpgW4aw3RS6G0J4OggC9BYulgozrCyhMVdCbRF2Wpetq3IODk6FkQdcL89DQc3oRhi0lqpQaAHMEOdbQ==", "requires": { "@oclif/command": "^1.7.0", "@oclif/config": "^1.16.0", @@ -1085,6 +898,48 @@ "twilio": "^3.54.2" }, "dependencies": { + "@oclif/plugin-help": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-2.2.3.tgz", + "integrity": "sha512-bGHUdo5e7DjPJ0vTeRBMIrfqTRDBfyR5w0MP41u0n3r7YG5p14lvMmiCXxi6WDaP2Hw5nqx3PnkAIntCKZZN7g==", + "requires": { + "@oclif/command": "^1.5.13", + "chalk": "^2.4.1", + "indent-string": "^4.0.0", + "lodash.template": "^4.4.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0", + "widest-line": "^2.0.1", + "wrap-ansi": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -1096,6 +951,11 @@ "universalify": "^2.0.0" } }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -1105,17 +965,83 @@ "universalify": "^2.0.0" } }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "requires": { + "string-width": "^2.1.1" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "wrap-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-4.0.0.tgz", + "integrity": "sha512-uMTsj9rDb0/7kk1PbcbCcwvHUxp60fGDB/NNXpVa0Q+ic/e7y5+BwTxKfQ33VYgDppSwi/FBzpetYzo8s6tfbg==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } } } }, "@twilio/cli-test": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@twilio/cli-test/-/cli-test-2.1.1.tgz", - "integrity": "sha512-PwiRBVEhbDcuP9KRkdO9hZh+XHC3gOxZDFW9yHNdIkpMB5fY1MECrEU8OwM1D56DJmYBG356DKRwnFRllOH/wg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@twilio/cli-test/-/cli-test-2.1.2.tgz", + "integrity": "sha512-WNLjLLelM1Q9nI2O6seauLWK7Y4XWBevqQhI0cVLB5QDPfUGeecIV/fSpIn3ntK3EaieMMKViOMMK0wFYPXm7g==", "dev": true, "requires": { "@oclif/command": "^1.7.0", @@ -1135,42 +1061,31 @@ "optional": true }, "@types/chai": { - "version": "4.2.18", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.18.tgz", - "integrity": "sha512-rS27+EkB/RE1Iz3u0XtVL5q36MGDWbgYe7zWiodyKNUnthxY0rukK5V36eiUCtCisB7NN8zKYH6DO2M37qxFEQ==", + "version": "4.2.21", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.21.tgz", + "integrity": "sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg==", "dev": true }, - "@types/debug": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", - "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" - }, "@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==", "dev": true, "requires": { "@types/minimatch": "*", "@types/node": "*" } }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, "@types/lodash": { - "version": "4.14.169", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.169.tgz", - "integrity": "sha512-DvmZHoHTFJ8zhVYwCLWbQ7uAbYQEk52Ev2/ZiQ7Y7gQGeV9pjBqjnQpECMHfKS1rCYAhMI7LHVxwyZLZinJgdw==", + "version": "4.14.171", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.171.tgz", + "integrity": "sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg==", "dev": true }, "@types/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", "dev": true }, "@types/node": { @@ -1179,9 +1094,9 @@ "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==" }, "@types/request": { - "version": "2.48.5", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", - "integrity": "sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ==", + "version": "2.48.7", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.7.tgz", + "integrity": "sha512-GWP9AZW7foLd4YQxyFZDBepl0lPsWLMEXDZUjQ/c1gqVPDPECrRZyEzuhJdnPWioFCq3Tv0qoGpMD6U+ygd4ZA==", "optional": true, "requires": { "@types/caseless": "*", @@ -1191,18 +1106,18 @@ } }, "@types/sinon": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.0.tgz", - "integrity": "sha512-jDZ55oCKxqlDmoTBBbBBEx+N8ZraUVhggMZ9T5t+6/Dh8/4NiOjSUfpLrPiEwxQDlAe3wpAkoXhWvE6LibtsMQ==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.2.tgz", + "integrity": "sha512-BHn8Bpkapj8Wdfxvh2jWIUoaYB/9/XhsL0oOvBfRagJtKlSl9NWPcFOz2lRukI9szwGxFtYZCTejJSqsGDbdmw==", "dev": true, "requires": { - "@sinonjs/fake-timers": "^7.0.4" + "@sinonjs/fake-timers": "^7.1.0" } }, "@types/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", + "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==", "optional": true }, "@ungap/promise-all-settled": { @@ -1224,9 +1139,9 @@ "dev": true }, "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true }, "agent-base": { @@ -1306,14 +1221,6 @@ "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" - }, - "dependencies": { - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - } } }, "append-transform": { @@ -1428,9 +1335,9 @@ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" }, "aws-sdk": { - "version": "2.910.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.910.0.tgz", - "integrity": "sha512-PC4x3qzxXxZEhd4CbcMdso/WnxeQvsiuYXHL+lb5B5d8zdORcXPaNmyIL/2DByyjJ2Ur4tfXgoQVPDKyYLcWEA==", + "version": "2.956.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.956.0.tgz", + "integrity": "sha512-vSzL66tjeRSBPnLR2Pkx4qS7SPqADT7K9QBjWdMhVd9BF5spyMvJ9hReIEShILp3hq99sHI+MvO+uTUm5s023g==", "dev": true, "requires": { "buffer": "4.9.2", @@ -1638,9 +1545,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001228", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz", - "integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==", + "version": "1.0.30001248", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001248.tgz", + "integrity": "sha512-NwlQbJkxUFJ8nMErnGtT0QTM2TJ33xgz4KXJSMIrjXIbDVdaYueGyjOrLKRtJC+rTiWfi6j5cnZN1NBiSBJGNw==", "dev": true }, "cardinal": { @@ -1755,14 +1662,6 @@ "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.5.0" - }, - "dependencies": { - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - } } }, "chownr": { @@ -1796,9 +1695,9 @@ } }, "cli-ux": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/cli-ux/-/cli-ux-5.5.1.tgz", - "integrity": "sha512-t3DT1U1C3rArLGYLpKa3m9dr/8uKZRI8HRm/rXKL7UTjm4c+Yd9zHNWg1tP8uaJkUbhmvx5SQHwb3VWpPUVdHQ==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/cli-ux/-/cli-ux-5.6.3.tgz", + "integrity": "sha512-/oDU4v8BiDjX2OKcSunGH0iGDiEtj2rZaGyqNuv9IT4CgcSMyVWAMfn0+rEHaOc4n9ka78B0wo1+N1QX89f7mw==", "requires": { "@oclif/command": "^1.6.0", "@oclif/errors": "^1.2.1", @@ -1823,7 +1722,7 @@ "semver": "^7.3.2", "string-width": "^4.2.0", "strip-ansi": "^6.0.0", - "supports-color": "^7.1.0", + "supports-color": "^8.1.0", "supports-hyperlinks": "^2.1.0", "tslib": "^2.0.0" }, @@ -1863,17 +1762,17 @@ } }, "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "requires": { "has-flag": "^4.0.0" } }, "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } } }, @@ -1996,25 +1895,15 @@ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "optional": true }, - "contains-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-1.0.0.tgz", - "integrity": "sha1-NFizMhhWA+ju0Y9RjUoQiIo6vJE=", - "dev": true, - "requires": { - "normalize-path": "^2.1.1", - "path-starts-with": "^1.0.0" - } - }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -2063,14 +1952,14 @@ } }, "dayjs": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz", - "integrity": "sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw==" + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.6.tgz", + "integrity": "sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw==" }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "requires": { "ms": "2.1.2" } @@ -2210,9 +2099,9 @@ "optional": true }, "detect-indent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", - "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", "dev": true }, "detect-libc": { @@ -2263,9 +2152,9 @@ } }, "electron-to-chromium": { - "version": "1.3.733", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.733.tgz", - "integrity": "sha512-6VUgcqRBo7FC+SG08arfFwr9qKVnBSmagXN332SWvvG2j/26Xy7AfQMqPsVq3vVW4fw20SrnmBedQzTD3slVEQ==", + "version": "1.3.790", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.790.tgz", + "integrity": "sha512-epMH/S2MkhBv+Y0+nHK8dC7bzmOaPwcmiYqt+VwxSUJLgPzkqZnGUEQ8eVhy5zGmgWm9tDDdXkHDzOEsVU979A==", "dev": true }, "emoji-regex": { @@ -2299,9 +2188,9 @@ } }, "es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", + "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -2312,14 +2201,14 @@ "has-symbols": "^1.0.2", "is-callable": "^1.2.3", "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.10.3", "object-keys": "^1.1.1", "object.assign": "^4.1.2", "string.prototype.trimend": "^1.0.4", "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" + "unbox-primitive": "^1.0.1" } }, "es-to-primitive": { @@ -2351,28 +2240,31 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, "eslint": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.26.0.tgz", - "integrity": "sha512-4R1ieRf52/izcZE7AlLy56uIHHDLT74Yzz2Iv2l6kDaYvEu9x+wMB5dZArVL8SYGXSYV2YAg70FcW5Y5nGGNIg==", + "version": "7.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.31.0.tgz", + "integrity": "sha512-vafgJpSh2ia8tnTkNUkwxGmnumgckLh5aAbLa1xRmIn9+owi8qBNGKL+B881kNKNTy7FFqTEkpNkUvmw0n6PkA==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.1", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", "eslint-scope": "^5.1.1", "eslint-utils": "^2.1.0", "eslint-visitor-keys": "^2.0.0", "espree": "^7.3.1", "esquery": "^1.4.0", "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", + "glob-parent": "^5.1.2", "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", @@ -2381,7 +2273,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.21", + "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -2390,7 +2282,7 @@ "semver": "^7.2.1", "strip-ansi": "^6.0.0", "strip-json-comments": "^3.1.0", - "table": "^6.0.4", + "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, @@ -2495,9 +2387,9 @@ } }, "eslint-config-twilio-base": { - "version": "1.34.0", - "resolved": "https://registry.npmjs.org/eslint-config-twilio-base/-/eslint-config-twilio-base-1.34.0.tgz", - "integrity": "sha512-uIzNlUh0lyPl9bCQ5CrZvFmOUB8yMSWYoZBX075m172pSS/rrnYP+LF6MryBkyESARVuFp1LrEeWSXe4lgR3HA==", + "version": "1.35.1", + "resolved": "https://registry.npmjs.org/eslint-config-twilio-base/-/eslint-config-twilio-base-1.35.1.tgz", + "integrity": "sha512-f+Wh5P1SBUrdW8oc8qZU42bbKG6Xia36AYtIJiUhXV5BRt5bNWwp6f07ftH1dhryc8yZ6KuRG4a/z+4e64Vtsw==", "dev": true }, "eslint-config-twilio-mocha": { @@ -2629,14 +2521,13 @@ "dev": true }, "eslint-plugin-import": { - "version": "2.23.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.23.2.tgz", - "integrity": "sha512-LmNoRptHBxOP+nb0PIKz1y6OSzCJlB+0g0IGS3XV4KaKk2q4szqQ6s6F1utVf5ZRkxk/QOTjdxe7v4VjS99Bsg==", + "version": "2.23.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.23.4.tgz", + "integrity": "sha512-6/wP8zZRsnQFiR3iaPFgh5ImVRM1WN5NUWfTIRqwOdeiGJlBcSk82o1FEVq8yXmy4lkIzTo7YhHCIxlU/2HyEQ==", "dev": true, "requires": { "array-includes": "^3.1.3", "array.prototype.flat": "^1.2.4", - "contains-path": "^1.0.0", "debug": "^2.6.9", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.4", @@ -2960,16 +2851,15 @@ "dev": true }, "fast-glob": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", - "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", + "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" + "micromatch": "^4.0.4" } }, "fast-json-stable-stringify": { @@ -2984,9 +2874,9 @@ "dev": true }, "fastq": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", - "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", + "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", "requires": { "reusify": "^1.0.4" } @@ -3102,9 +2992,9 @@ } }, "flatted": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", - "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", "dev": true }, "follow-redirects": { @@ -3378,9 +3268,9 @@ } }, "globals": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.8.0.tgz", - "integrity": "sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q==", + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", + "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -3395,9 +3285,9 @@ } }, "globby": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", - "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -3672,9 +3562,9 @@ "dev": true }, "is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", + "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", "dev": true, "requires": { "has": "^1.0.3" @@ -3754,9 +3644,9 @@ "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" }, "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" }, "is-string": { "version": "1.0.6", @@ -4028,12 +3918,12 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "dev": true, "requires": { - "minimist": "^1.2.0" + "minimist": "^1.2.5" } }, "jsonfile": { @@ -4217,6 +4107,12 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -4306,18 +4202,18 @@ } }, "mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", + "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", "optional": true }, "mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", + "version": "2.1.32", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", + "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", "optional": true, "requires": { - "mime-db": "1.47.0" + "mime-db": "1.49.0" } }, "mimic-fn": { @@ -4395,6 +4291,23 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -4587,9 +4500,9 @@ } }, "nock": { - "version": "13.0.11", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.11.tgz", - "integrity": "sha512-sKZltNkkWblkqqPAsjYW0bm3s9DcHRPiMOyKO/PkfJ+ANHZ2+LA2PLe22r4lLrKgXaiSaDQwW3qGsJFtIpQIeQ==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.1.1.tgz", + "integrity": "sha512-YKTR9MjfK3kS9/l4nuTxyYm30cgOExRHzkLNhL8nhEUyU4f8Za/dRxOqjhVT1vGs0svWo3dDnJTUX1qxYeWy5w==", "dev": true, "requires": { "debug": "^4.1.0", @@ -4599,9 +4512,9 @@ } }, "node-abi": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.26.0.tgz", - "integrity": "sha512-ag/Vos/mXXpWLLAYWsAoQdgS+gW7IwvgMLOgqopm/DbzAjazLltzgzpVMsFlgmo9TzG5hGXeaBZx2AI731RIsQ==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.0.tgz", + "integrity": "sha512-g6bZh3YCKQRdwuO/tSZZYJAw622SjsRfJ2X0Iy4sSOHZ34/sPPdVBn8fev2tj7njzLwuqPw9uMtGsGkO5kIQvg==", "optional": true, "requires": { "semver": "^5.4.1" @@ -4616,9 +4529,9 @@ } }, "node-addon-api": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.0.tgz", - "integrity": "sha512-kcwSAWhPi4+QzAtsL2+2s/awvDo2GKLsvMCwNRxb5BUshteXU8U97NCyvQDsGKs/m0He9WcG4YWew/BnuLx++w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "optional": true }, "node-preload": { @@ -4631,17 +4544,11 @@ } }, "node-releases": { - "version": "1.1.72", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", - "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==", + "version": "1.1.73", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", + "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", "dev": true }, - "noop-logger": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=", - "optional": true - }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -4664,13 +4571,10 @@ } }, "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "npm-run-path": { "version": "4.0.1", @@ -4878,9 +4782,9 @@ "optional": true }, "object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==" + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==" }, "object-keys": { "version": "1.1.1", @@ -4906,15 +4810,14 @@ } }, "object.values": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz", - "integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", + "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "has": "^1.0.3" + "es-abstract": "^1.18.2" } }, "once": { @@ -5055,20 +4958,11 @@ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "path-starts-with": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-starts-with/-/path-starts-with-1.0.0.tgz", - "integrity": "sha1-soJDAV6LE43lcmgqxS2kLmRq2E4=", - "dev": true, - "requires": { - "normalize-path": "^2.1.1" - } - }, "path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -5098,9 +4992,9 @@ "dev": true }, "peek-readable": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-3.1.3.tgz", - "integrity": "sha512-mpAcysyRJxmICBcBa5IXH7SZPvWkcghm6Fk8RekoS3v+BpbSzlZzuWbMx+GXrlUwESi9qHar4nVEZNMKylIHvg==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.0.0.tgz", + "integrity": "sha512-kLbU4cz6h86poGVBKgAVMpFmD47nX04fPPQNKnv9fuj+IJZYkEBjsYAVu5nDbZWx0ZsWwWlMzeG90zQa5KLBaA==" }, "performance-now": { "version": "2.1.0", @@ -5109,9 +5003,9 @@ "optional": true }, "picomatch": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", - "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" }, "pify": { "version": "4.0.1", @@ -5193,9 +5087,9 @@ "integrity": "sha1-zqz9q0q/NT16DyqqLB/Hs/lBO6M=" }, "prebuild-install": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.2.tgz", - "integrity": "sha512-PzYWIKZeP+967WuKYXlTOhYBgGOvTRSfaKI89XnfJ0ansRAH7hDU45X+K+FZeI1Wb/7p/NnuctPH3g0IqKUuSQ==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.3.tgz", + "integrity": "sha512-iqqSR84tNYQUQHRXalSKdIaM8Ov1QxOVuBNWI7+BzZWv6Ih9k75wOnH1rGQ9WWTaaLkTpxWKIciOF0KyfM74+Q==", "optional": true, "requires": { "detect-libc": "^1.0.3", @@ -5205,7 +5099,6 @@ "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", "node-abi": "^2.21.0", - "noop-logger": "^0.1.1", "npmlog": "^4.0.1", "pump": "^3.0.0", "rc": "^1.2.7", @@ -5221,9 +5114,9 @@ "dev": true }, "prettier": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz", - "integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", "dev": true }, "prettier-linter-helpers": { @@ -5631,9 +5524,9 @@ } }, "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, "release-zalgo": { @@ -5645,12 +5538,6 @@ "es6-error": "^4.0.1" } }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -6046,9 +5933,9 @@ } }, "spdx-license-ids": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.8.tgz", - "integrity": "sha512-NDgA96EnaLSvtbM7trJj+t1LUR3pirkDCcz9nOUlPb5DMBGsH7oES6C3hs3j7R9oHEa1EMvReS/BUAIT5Tcr0g==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", + "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", "dev": true }, "sprintf-js": { @@ -6192,13 +6079,11 @@ "optional": true }, "strtok3": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.0.8.tgz", - "integrity": "sha512-QLgv+oiXwXgCgp2PdPPa+Jpp4D9imK9e/0BsyfeFMr6QL6wMVqoVn9+OXQ9I7MZbmUzN6lmitTJ09uwS2OmGcw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.2.2.tgz", + "integrity": "sha512-iUzLl3UhF2RfqQah80JngnfltQFLEidGyTX8+hHFMQFjzUj3UpIpOx824FtFmRI9bwyywReENpdHGDkFJwJlGQ==", "requires": { - "@tokenizer/token": "^0.1.1", - "@types/debug": "^4.1.5", - "peek-readable": "^3.1.3" + "peek-readable": "^4.0.0" } }, "supports-color": { @@ -6250,9 +6135,9 @@ }, "dependencies": { "ajv": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.4.0.tgz", - "integrity": "sha512-7QD2l6+KBSLwf+7MuYocbWvRPdOu63/trReTLu2KFwkgctnub1auoF+Y1WYcm09CTM7quuscrzqmASaLHC/K4Q==", + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", + "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -6427,13 +6312,12 @@ "optional": true }, "tsconfig-paths": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", - "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.10.1.tgz", + "integrity": "sha512-rETidPDgCpltxF7MjBZlAFPUHv5aHH2MymyPvh+vEyWAED4Eb/WeMbsnD/JDr4OKPOA1TssDHgIcpTN5Kh0p6Q==", "dev": true, "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^2.2.0", "minimist": "^1.2.0", "strip-bom": "^3.0.0" } @@ -6463,15 +6347,15 @@ "optional": true }, "twilio": { - "version": "3.63.0", - "resolved": "https://registry.npmjs.org/twilio/-/twilio-3.63.0.tgz", - "integrity": "sha512-ftZckbTBjJ5dgzdII9j0sqYw9SYq3wqTC9r6NmV7CRU0EXXDil5/AbKb78xNPLtMPx3+mn2N+2oTkQlTtWs9TQ==", + "version": "3.66.1", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-3.66.1.tgz", + "integrity": "sha512-BmIgfx2VuS7tj4IscBhyEj7CdmtfIaaJ1IuNeGoJFYBx5xikpuwkR0Ceo5CNtK5jnN3SCKmxHxToec/MYEXl0A==", "requires": { "axios": "^0.21.1", "dayjs": "^1.8.29", "https-proxy-agent": "^5.0.0", "jsonwebtoken": "^8.5.1", - "lodash": "^4.17.19", + "lodash": "^4.17.21", "q": "2.0.x", "qs": "^6.9.4", "rootpath": "^0.1.2", @@ -6557,9 +6441,9 @@ } }, "url-parse": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", - "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", + "integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -6844,9 +6728,9 @@ } }, "yarn": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.10.tgz", - "integrity": "sha512-IanQGI9RRPAN87VGTF7zs2uxkSyQSrSPsju0COgbsKQOOXr5LtcVPeyXWgwVa0ywG3d8dg6kSYKGBuYK021qeA==" + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.11.tgz", + "integrity": "sha512-AWje4bzqO9RUn3sdnM5N8n4ZJ0BqCc/kqFJvpOI5/EVkINXui0yuvU7NDCEF//+WaxHuNay2uOHxA4+tq1P3cg==" }, "yocto-queue": { "version": "0.1.0", diff --git a/package.json b/package.json index 76f9f103e..a929357e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "twilio-cli", - "version": "2.24.0", + "version": "2.27.0", "description": "Unleash the power of Twilio from your command prompt. Visit https://twil.io/cli for documentation.", "keywords": [ "oclif" @@ -39,7 +39,7 @@ "@oclif/command": "^1.7.0", "@oclif/config": "^1.16.0", "@oclif/plugin-autocomplete": "^0.2.0", - "@oclif/plugin-help": "^2.2.3", + "@oclif/plugin-help": "^3.2.2", "@oclif/plugin-plugins": "^1.8.3", "@oclif/plugin-warn-if-update-available": "^1.7.0", "@sendgrid/mail": "^7.2.1", diff --git a/src/services/plugins.js b/src/services/plugins.js index 1245a9e59..5363d3af7 100644 --- a/src/services/plugins.js +++ b/src/services/plugins.js @@ -7,6 +7,7 @@ const PLUGIN_COMMANDS = { '@twilio-labs/plugin-signal2020': ['signal', 'signal2020'], '@twilio-labs/plugin-token': ['token'], '@twilio-labs/plugin-watch': ['watch'], + '@twilio-labs/plugin-assets': ['assets'], '@dabblelab/plugin-autopilot': ['autopilot'], }; From 3198c8924bc85bf4367cef80d1c1e39fe2ff3d62 Mon Sep 17 00:00:00 2001 From: ravali-rimmalapudi <83863595+ravali-rimmalapudi@users.noreply.github.com> Date: Thu, 29 Jul 2021 17:49:50 +0530 Subject: [PATCH 13/18] Updated the cli-core package version with latest. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a929357e3..fab45d83d 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@oclif/plugin-plugins": "^1.8.3", "@oclif/plugin-warn-if-update-available": "^1.7.0", "@sendgrid/mail": "^7.2.1", - "@twilio/cli-core": "twilio/twilio-cli-core#5.27.0-rc", + "@twilio/cli-core": "twilio/twilio-cli-core#5.27.1-rc", "chalk": "^4.1.0", "cli-ux": "^5.6.2", "file-type": "^14.6.2", From 8c1cd5d98b922d25b60fa2122565823275aa4a69 Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 29 Jul 2021 12:31:41 +0000 Subject: [PATCH 14/18] [Librarian] Regenerated @ 54ad9271a294331f8d036e4a3fc173bb98ecdb82 --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 249be601e..82af643fc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,12 @@ twilio-cli changelog ===================== +[2021-07-29] Version 2.27.1-rc +------------------------------ +**Library - Fix** +- [PR #275](https://github.com/twilio/twilio-cli/pull/275): Update release-feature-branch with main. Thanks to [@ravali-rimmalapudi](https://github.com/ravali-rimmalapudi)! + + [2021-07-29] Version 2.27.0 --------------------------- **Api** From 639d6094b21feef964c9df1bfd7ee6fd119847fb Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 29 Jul 2021 13:33:25 +0000 Subject: [PATCH 15/18] Release 2.27.1-rc --- package-lock.json | 7 +++---- package.json | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index a7209dff6..2ded981df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "twilio-cli", - "version": "2.27.0", + "version": "2.27.1-rc", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -876,9 +876,8 @@ "integrity": "sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w==" }, "@twilio/cli-core": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@twilio/cli-core/-/cli-core-5.27.0.tgz", - "integrity": "sha512-xChzpPRpgW4aw3RS6G0J4OggC9BYulgozrCyhMVdCbRF2Wpetq3IODk6FkQdcL89DQc3oRhi0lqpQaAHMEOdbQ==", + "version": "github:twilio/twilio-cli-core#d24389088e974d79718687d7db9fb6f2f9aa95dc", + "from": "github:twilio/twilio-cli-core#5.27.1-rc", "requires": { "@oclif/command": "^1.7.0", "@oclif/config": "^1.16.0", diff --git a/package.json b/package.json index fab45d83d..56ef37ffa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "twilio-cli", - "version": "2.27.0", + "version": "2.27.1-rc", "description": "Unleash the power of Twilio from your command prompt. Visit https://twil.io/cli for documentation.", "keywords": [ "oclif" From fbb8fc92bc4f493c367c95314dbedd6a1553d580 Mon Sep 17 00:00:00 2001 From: shamantraghav <87780745+shamantraghav@users.noreply.github.com> Date: Fri, 30 Jul 2021 11:20:59 +0530 Subject: [PATCH 16/18] chore: Fix autocomplete bugs (#274) Co-authored-by: Shamant Raghav --- package.json | 5 +- src/hooks/postrun/plugin-postrun.js | 20 +++++++ src/services/post-install-utility.js | 6 ++ test/hooks/postrun/plugin-postrun.test.js | 67 ++++++++++++++++++++++ test/services/post-install-utility.test.js | 6 +- 5 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 src/hooks/postrun/plugin-postrun.js create mode 100644 test/hooks/postrun/plugin-postrun.test.js diff --git a/package.json b/package.json index 56ef37ffa..9ddd531ee 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "@twilio/cli-test": "^2.1.0", "aws-sdk": "^2.757.0", "chai": "^4.2.0", - "eslint": "^7.5.0", + "eslint": "^7.31.0", "eslint-config-twilio": "~1.31.0", "eslint-config-twilio-mocha": "~1.31.0", "globby": "^11.0.1", @@ -111,6 +111,9 @@ "plugins:preinstall": [ "./src/hooks/plugin-install" ], + "postrun": [ + "./src/hooks/postrun/plugin-postrun" + ], "command_not_found": [ "./src/hooks/command-not-found" ] diff --git a/src/hooks/postrun/plugin-postrun.js b/src/hooks/postrun/plugin-postrun.js new file mode 100644 index 000000000..e7426387e --- /dev/null +++ b/src/hooks/postrun/plugin-postrun.js @@ -0,0 +1,20 @@ +/* eslint-disable no-console */ +const chalk = require('chalk'); + +const AUTOCOMLETE_WARNING = `If you’re using autocomplete, you’ll need to run ${chalk.bold( + 'twilio autocomplete', +)} after installing a plugin and then open a new terminal window. The CLI needs to re-build its cache.`; +const AUTOCOMLETE_ALERT = `If you are running bash or zsh on macOS or Linux, you can run one of the two commands below (as appropriate for the shell you are using): \n '${chalk.bold( + '1) twilio autocomplete bash', +)}' or \n '${chalk.bold('2) twilio autocomplete zsh')}' `; + +module.exports = async function pluginPostRun(options) { + if (options.Command.id === 'plugins:update') { + console.warn(chalk.yellowBright(` » ${AUTOCOMLETE_WARNING}`)); + } + if (options.Command.id === 'autocomplete') { + if (Object.keys(options.argv).length === 0) { + console.warn(chalk.yellowBright(` » ${AUTOCOMLETE_ALERT}`)); + } + } +}; diff --git a/src/services/post-install-utility.js b/src/services/post-install-utility.js index 277708cbf..7b6c13850 100644 --- a/src/services/post-install-utility.js +++ b/src/services/post-install-utility.js @@ -7,6 +7,10 @@ const PORT_WARNING = `Profiles exist with API keys in system keychain. Please ru 'twilio profiles:port', )} to port all keys to the config file`; +const AUTOCOMLETE_WARNING = `If you’re using autocomplete, you’ll need to run '${chalk.bold( + 'twilio autocomplete', +)}' after installing a plugin and then open a new terminal window. The CLI needs to re-build its cache.`; + class PostInstallDisplayManager { constructor(configDir, userConfig) { configureEnv(); @@ -49,10 +53,12 @@ class PostInstallDisplayManager { if (this.hasProjects()) { console.warn(chalk.yellowBright(` » ${PORT_WARNING}`)); } + console.warn(chalk.yellowBright(` » ${AUTOCOMLETE_WARNING}`)); } } module.exports = { PostInstallDisplayManager, PORT_WARNING, + AUTOCOMLETE_WARNING, }; diff --git a/test/hooks/postrun/plugin-postrun.test.js b/test/hooks/postrun/plugin-postrun.test.js new file mode 100644 index 000000000..6e3cc594e --- /dev/null +++ b/test/hooks/postrun/plugin-postrun.test.js @@ -0,0 +1,67 @@ +/* eslint-disable no-console */ +const sinon = require('sinon'); +const { expect, test } = require('@twilio/cli-test'); + +const pluginFunc = require('../../../src/hooks/postrun/plugin-postrun'); + +const pluginUpdate = { + Command: { + id: 'plugins:update', + }, +}; + +const pluginAvailable = { + Command: { + id: 'plugins:available', + }, +}; + +const autocomplete = { + Command: { + id: 'autocomplete', + }, + argv: [], +}; + +const autocompleteBash = { + Command: { + id: 'autocomplete', + }, + argv: ['bash'], +}; + +describe('hooks', () => { + describe('postrun', () => { + describe('plugin-postrun', () => { + before(() => { + warnSpy = sinon.spy(console, 'warn'); + }); + + after(() => { + warnSpy.restore(); + }); + + test.it('no warning when other plugin commands are run', () => { + pluginFunc(pluginAvailable); + expect(console.warn.calledOnce).to.be.false; + }); + + test.it('warning when plugin is updated', () => { + pluginFunc(pluginUpdate); + expect(console.warn.calledOnce).to.be.true; + expect(console.warn.calledWith(sinon.match('twilio autocomplete'))).to.be.true; + }); + + test.it('warning when autocorrect has no arguments', () => { + pluginFunc(autocomplete); + expect(console.warn.calledWith(sinon.match('twilio autocomplete bash'))).to.be.true; + expect(console.warn.calledWith(sinon.match('twilio autocomplete zsh'))).to.be.true; + }); + + test.it('no warning autocorrect has arguments', () => { + pluginFunc(autocompleteBash); + expect(console.warn.calledOnce).to.be.false; + }); + }); + }); +}); diff --git a/test/services/post-install-utility.test.js b/test/services/post-install-utility.test.js index 88c797ba0..596f0721c 100644 --- a/test/services/post-install-utility.test.js +++ b/test/services/post-install-utility.test.js @@ -26,7 +26,7 @@ describe('services', () => { displayManager.displayMessage(); expect(displayManager.userConfig).to.not.be.undefined; - expect(console.warn.called).to.be.false; + expect(console.warn.calledWith(sinon.match('twilio autocomplete'))).to.be.true; expect(console.log.calledWith(sinon.match('twilio profiles:create'))).to.be.true; expect(console.log.calledWith(sinon.match('twilio login'))).to.be.true; }); @@ -37,7 +37,7 @@ describe('services', () => { const displayManager = new PostInstallDisplayManager(tempConfigDir.name, configData); displayManager.displayMessage(); - expect(console.warn.calledOnce).to.be.true; + expect(console.warn.calledWith(sinon.match('twilio autocomplete'))).to.be.true; expect(console.warn.calledWith(sinon.match('twilio profiles:port'))).to.be.true; expect(console.log.called).to.be.false; // Grid shouldn't be displayed }); @@ -55,7 +55,7 @@ describe('services', () => { const displayManager = new PostInstallDisplayManager(tempConfigDir.name, configData); displayManager.displayMessage(); - expect(console.warn.called).to.be.false; + expect(console.warn.calledWith(sinon.match('twilio autocomplete'))).to.be.true; expect(console.log.called).to.be.false; }); From 586d3fa0a6c755c63db21586cfb81615b3f09941 Mon Sep 17 00:00:00 2001 From: kridai Date: Mon, 2 Aug 2021 13:05:00 +0530 Subject: [PATCH 17/18] feat: add new flag support for config commands to force profile input flag (#272) * feat: add new flag support require-profile-input for config commands * updated tests for config set and list * refactor logic for config set * nit, adding comment * changes per review comment, updating the parsing login for config list command --- CONTRIBUTING.md | 4 +- src/commands/config/list.js | 7 +- src/commands/config/set.js | 14 ++- src/services/config-utility.js | 2 +- test/commands/config/list.test.js | 60 ++++++++---- test/commands/config/set.test.js | 147 ++++++++++++++++++------------ 6 files changed, 152 insertions(+), 82 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d225c2461..7e136c756 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -93,13 +93,13 @@ Before you submit your pull request consider the following guidelines: * `npm uninstall -g twilio-cli` * `git clone https://github.com/twilio/twilio-cli-core.git` * `cd twilio-cli-core` - * ``make clean install` + * `make clean install` * `cd ..` * `git clone https://github.com/twilio/twilio-cli.git` * `cd twilio-cli` * In `package.json` replace `"@twilio/cli-core": ""` with `"@twilio/cli-core": "file:../twilio-cli-core"` * `make clean install` - * Test that everything is wired up correctuly with `./bin/run` + * Test that everything is wired up correctly with `./bin/run` * Understanding the code base: * The Twilio CLI utilizes the Open CLI Framework ([oclif](https://oclif.io/)). It may be useful to familiarize yourself with it, in particular, the [multi-command CLI](https://oclif.io/docs/multi). diff --git a/src/commands/config/list.js b/src/commands/config/list.js index 2c76bc89c..7da0d58a6 100644 --- a/src/commands/config/list.js +++ b/src/commands/config/list.js @@ -1,19 +1,20 @@ const { BaseCommand } = require('@twilio/cli-core').baseCommands; -const { availableConfigs, getFromEnvironment } = require('../../services/config-utility'); +const { getFromEnvironment } = require('../../services/config-utility'); class ConfigList extends BaseCommand { async run() { await super.run(); const configData = []; - availableConfigs.forEach((config) => { + Object.keys(this.userConfig).forEach((config) => { let configEnvValue = getFromEnvironment(config); if (configEnvValue) { configEnvValue += '[env]'; } + configData.push({ configName: config, - value: configEnvValue || this.userConfig[config], + value: JSON.stringify(configEnvValue || this.userConfig[config]), }); }); this.output(configData); diff --git a/src/commands/config/set.js b/src/commands/config/set.js index 7aa17f243..9c5905760 100644 --- a/src/commands/config/set.js +++ b/src/commands/config/set.js @@ -1,6 +1,7 @@ const { TwilioCliError } = require('@twilio/cli-core/src/services/error'); const { BaseCommand } = require('@twilio/cli-core').baseCommands; const { flags } = require('@oclif/command'); +const { camelCase } = require('@twilio/cli-core').services.namingConventions; const { availableConfigs, getFromEnvironment } = require('../../services/config-utility'); @@ -10,6 +11,7 @@ class ConfigSet extends BaseCommand { let isError = true; let isUserConfigUpdated = false; for (const flag of availableConfigs) { + const configProperty = camelCase(flag); if (this.flags[flag] !== undefined) { isError = false; this.preWarnings(flag); @@ -17,8 +19,8 @@ class ConfigSet extends BaseCommand { isUserConfigUpdated = await this.removeConfig(flag, isUserConfigUpdated); continue; } - if (await this.isOverwrite(flag)) { - this.userConfig[flag] = this.userConfig.sanitize(this.flags[flag]); + if (await this.isOverwrite(configProperty)) { + this.userConfig[configProperty] = this.sanitizeFlag(this.flags[flag]); isUserConfigUpdated = true; } } @@ -33,6 +35,10 @@ class ConfigSet extends BaseCommand { } } + sanitizeFlag(flag) { + return typeof flag === 'string' ? this.userConfig.sanitize(flag) : flag; + } + preWarnings(flag) { const flagEnvValue = getFromEnvironment(flag); if (flagEnvValue) { @@ -84,6 +90,10 @@ ConfigSet.flags = { char: 'e', description: 'Sets an Edge configuration.', }), + 'require-profile-input': flags.boolean({ + description: 'Whether the profile flag for Twilio CLI commands is required or not.', + allowNo: true, + }), ...BaseCommand.flags, // To add the same flags as BaseCommand }; module.exports = ConfigSet; diff --git a/src/services/config-utility.js b/src/services/config-utility.js index 24aaab4f9..a077d5ddb 100644 --- a/src/services/config-utility.js +++ b/src/services/config-utility.js @@ -1,4 +1,4 @@ -const availableConfigs = ['edge']; +const availableConfigs = ['edge', 'require-profile-input']; function getFromEnvironment(config) { const configEnv = `TWILIO_${config.toUpperCase()}`; return process.env[configEnv]; diff --git a/test/commands/config/list.test.js b/test/commands/config/list.test.js index 7b4514839..af362c2aa 100644 --- a/test/commands/config/list.test.js +++ b/test/commands/config/list.test.js @@ -6,12 +6,12 @@ const ConfigList = require('../../../src/commands/config/list'); describe('commands', () => { describe('config', () => { describe('list', () => { - const listConfig = ({ addEdge = '' } = {}) => + const listConfig = ({ configProperty, configPropertyValue } = {}) => test .do((ctx) => { ctx.userConfig = new ConfigData(); - if (addEdge) { - ctx.userConfig.edge = addEdge; + if (configProperty) { + ctx.userConfig[configProperty] = configPropertyValue; } }) .twilioCliEnv(Config) @@ -19,32 +19,56 @@ describe('commands', () => { .stdout() .stderr(); - listConfig({ addEdge: 'testEdge' }) + // parsing the stdout to a key value pair + const parseOutputToMap = (output) => { + const propertyWithValue = output.split('\n'); + const propertyMap = new Map(); + propertyWithValue.forEach((value) => { + const entry = value.trim().split(/\s{2,}/); + propertyMap.set(entry[0], entry[1]); + }); + return propertyMap; + }; + listConfig({ configProperty: 'edge', configPropertyValue: 'testEdge' }) .do((ctx) => ctx.testCmd.run()) .it('runs config:list, should list config variables', (ctx) => { - expect(ctx.stdout).to.contain('Config Name'); - expect(ctx.stdout).to.contain('Value'); - expect(ctx.stdout).to.contain('testEdge'); - expect(ctx.stdout).to.contain('edge'); + const configListOutput = parseOutputToMap(ctx.stdout); + expect(configListOutput.get('edge')).is.equal('"testEdge"'); }); - listConfig({ addEdge: 'testEdge' }) + listConfig({ configProperty: 'edge', configPropertyValue: 'testEdge' }) .do((ctx) => { process.env.TWILIO_EDGE = 'fakeEdge'; return ctx.testCmd.run(); }) .it('runs config:list, should prioritize environment if both environment and config edge set', (ctx) => { - expect(ctx.stdout).to.contain('Config Name'); - expect(ctx.stdout).to.contain('Value'); - expect(ctx.stdout).to.contain('fakeEdge[env]'); - expect(ctx.stdout).to.contain('edge'); + const configListOutput = parseOutputToMap(ctx.stdout); + expect(configListOutput.get('edge')).is.not.undefined; + expect(configListOutput.get('edge')).is.equal('"fakeEdge[env]"'); + }); + listConfig({}) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:list, should list empty config properties are not set', (ctx) => { + const configListOutput = parseOutputToMap(ctx.stdout); + expect(configListOutput.get('edge')).is.undefined; + expect(configListOutput.get('requireProfileInput')).is.undefined; + expect(configListOutput.get('email')).is.equal('{}'); + expect(configListOutput.get('projects')).is.equal('[]'); + expect(configListOutput.get('activeProfile')).is.equal('null'); + expect(configListOutput.get('profiles')).is.equal('{}'); + }); + listConfig({ configProperty: 'requireProfileInput', configPropertyValue: true }) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:list, should list requireProfileInput as set', (ctx) => { + const configListOutput = parseOutputToMap(ctx.stdout); + expect(configListOutput.get('requireProfileInput')).is.not.undefined; + expect(configListOutput.get('requireProfileInput')).is.equal('true'); }); listConfig({}) .do((ctx) => ctx.testCmd.run()) - .it('runs config:list, should list empty as edge is not set', (ctx) => { - expect(ctx.stdout).to.contain('Config Name'); - expect(ctx.stdout).to.contain('Value'); - expect(ctx.stdout).to.contain(''); - expect(ctx.stdout).to.contain('edge'); + .it('runs config:list, should list all config properties in userConfig', (ctx) => { + Object.keys(new ConfigData()).forEach((configProperty) => { + expect(ctx.stdout).to.contain(configProperty); + }); }); }); }); diff --git a/test/commands/config/set.test.js b/test/commands/config/set.test.js index ca5b9da55..c16a9e00a 100644 --- a/test/commands/config/set.test.js +++ b/test/commands/config/set.test.js @@ -7,12 +7,15 @@ const ConfigSet = require('../../../src/commands/config/set'); describe('commands', () => { describe('config', () => { describe('set', () => { - const createConfig = (commandArgs = [], { addEdge = '', updateEdge = true, removeEdge = true } = {}) => + const createConfig = ( + commandArgs = [], + { configProperty = '', configPropertyValue, updateConfigProperty = true, removeConfigProperty = true } = {}, + ) => test .do((ctx) => { ctx.userConfig = new ConfigData(); - if (addEdge) { - ctx.userConfig.edge = addEdge; + if (configProperty) { + ctx.userConfig[configProperty] = configPropertyValue; } }) .twilioCliEnv(Config) @@ -20,66 +23,98 @@ describe('commands', () => { .do((ctx) => { const fakePrompt = sinon.stub(); fakePrompt.onFirstCall().resolves({ - Overwrite: updateEdge, - Remove: removeEdge, + Overwrite: updateConfigProperty, + Remove: removeConfigProperty, }); ctx.testCmd.inquirer.prompt = fakePrompt; }) .stdout() .stderr(); + describe('edge', () => { + createConfig(['--edge=createEdge']) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --edge=createEdge', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.contain('createEdge'); + expect(ctx.stderr).to.contain('configuration saved'); + }); + createConfig(['--edge=updateEdge'], { configProperty: 'edge', configPropertyValue: 'createEdge' }) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --edge=updateEdge', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.contain('updateEdge'); + expect(ctx.stderr).to.contain('configuration saved'); + }); + createConfig(['--edge=updateEdge'], { + configProperty: 'edge', + configPropertyValue: 'createEdge', + updateConfigProperty: false, + }) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --edge=updateEdge with overwrite as false', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.contain('createEdge'); + expect(ctx.stderr).to.not.contain('There is an environment variable already set'); + }); + createConfig(['--edge=createEdge'], {}) + .do((ctx) => { + process.env.TWILIO_EDGE = 'envEdge'; + return ctx.testCmd.run(); + }) + .it('runs config:set --edge=createEdge, will show warning if environment variable exists', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.contain('createEdge'); + expect(ctx.stderr).to.contain('There is an environment variable already set for edge : envEdge'); + expect(ctx.stderr).to.contain('configuration saved'); + }); + createConfig(['--edge='], { configProperty: 'edge', configPropertyValue: 'createEdge' }) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --edge= ,should remove the edge from configuration', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.be.undefined; + expect(ctx.stderr).to.contain('configuration saved'); + }); + createConfig(['--edge='], { + configProperty: 'edge', + configPropertyValue: 'createEdge', + removeConfigProperty: false, + }) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --edge= ,should not remove the edge from configuration', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.contain('createEdge'); + expect(ctx.stderr).to.not.contain('configuration saved'); + }); + createConfig(['--edge='], {}) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --edge= ,should throw error as there is no existing edge', (ctx) => { + expect(ctx.testCmd.userConfig.edge).to.be.undefined; + expect(ctx.stderr).to.contain('There is no existing edge to remove.'); + }); + }); - createConfig(['--edge=createEdge']) - .do((ctx) => ctx.testCmd.run()) - .it('runs config:set --edge=createEdge', (ctx) => { - expect(ctx.testCmd.userConfig.edge).to.contain('createEdge'); - expect(ctx.stderr).to.contain('configuration saved'); - }); - createConfig(['--edge=updateEdge'], { addEdge: 'createEdge' }) - .do((ctx) => ctx.testCmd.run()) - .it('runs config:set --edge=updateEdge', (ctx) => { - expect(ctx.testCmd.userConfig.edge).to.contain('updateEdge'); - expect(ctx.stderr).to.contain('configuration saved'); - }); - createConfig(['--edge=updateEdge'], { addEdge: 'createEdge', updateEdge: false }) - .do((ctx) => ctx.testCmd.run()) - .it('runs config:set --edge=updateEdge with overwrite as false', (ctx) => { - expect(ctx.testCmd.userConfig.edge).to.contain('createEdge'); - expect(ctx.stderr).to.not.contain('There is an environment variable already set'); - }); - createConfig(['--edge=createEdge'], {}) - .do((ctx) => { - process.env.TWILIO_EDGE = 'envEdge'; - return ctx.testCmd.run(); + describe('require-profile-input', () => { + createConfig(['--require-profile-input']) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --require-profile-input', (ctx) => { + expect(ctx.testCmd.userConfig.requireProfileInput).to.be.true; + expect(ctx.stderr).to.contain('configuration saved'); + }); + createConfig(['--no-require-profile-input'], { + configProperty: 'requireProfileInput', + configPropertyValue: true, }) - .it('runs config:set --edge=createEdge, will show warning if environment variable exists', (ctx) => { - expect(ctx.testCmd.userConfig.edge).to.contain('createEdge'); - expect(ctx.stderr).to.contain('There is an environment variable already set for edge : envEdge'); - expect(ctx.stderr).to.contain('configuration saved'); - }); - createConfig(['--edge='], { addEdge: 'createEdge' }) - .do((ctx) => ctx.testCmd.run()) - .it('runs config:set --edge= ,should remove the edge from configuration', (ctx) => { - expect(ctx.testCmd.userConfig.edge).to.be.undefined; - expect(ctx.stderr).to.contain('configuration saved'); - }); - createConfig(['--edge='], { addEdge: 'createEdge', removeEdge: false }) - .do((ctx) => ctx.testCmd.run()) - .it('runs config:set --edge= ,should not remove the edge from configuration', (ctx) => { - expect(ctx.testCmd.userConfig.edge).to.contain('createEdge'); - expect(ctx.stderr).to.not.contain('configuration saved'); - }); - createConfig(['--edge='], {}) - .do((ctx) => ctx.testCmd.run()) - .it('runs config:set --edge= ,should throw error as there is no existing edge', (ctx) => { - expect(ctx.testCmd.userConfig.edge).to.be.undefined; - expect(ctx.stderr).to.contain('There is no existing edge to remove.'); - }); - createConfig(['--edge='], {}) - .do((ctx) => ctx.testCmd.run()) - .it('runs config:set --edge= ,should throw error as there is no existing edge', (ctx) => { - expect(ctx.testCmd.userConfig.edge).to.be.undefined; - expect(ctx.stderr).to.contain('There is no existing edge to remove.'); - }); + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --no-require-profile-input', (ctx) => { + expect(ctx.testCmd.userConfig.requireProfileInput).to.be.false; + expect(ctx.stderr).to.contain('configuration saved'); + }); + createConfig(['--no-require-profile-input'], { + configProperty: 'requireProfileInput', + configPropertyValue: true, + updateConfigProperty: false, + }) + .do((ctx) => ctx.testCmd.run()) + .it('runs config:set --no-require-profile-input with confirmation overwrite as false', (ctx) => { + expect(ctx.testCmd.userConfig.requireProfileInput).to.be.true; + expect(ctx.stderr).to.not.contain('There is an environment variable already set'); + }); + }); + createConfig([], {}) .do((ctx) => ctx.testCmd.run()) .catch(/No configuration is added to set. Run "twilio configs:set --help" to see how to set a configurations./) From c9c3768a5e92cee3242602a65467d219a6e5333e Mon Sep 17 00:00:00 2001 From: shamantraghav <87780745+shamantraghav@users.noreply.github.com> Date: Thu, 12 Aug 2021 14:34:10 +0530 Subject: [PATCH 18/18] feature: Twilio update (#278) * SR: Oclif Update changes * SR: adding message to alert about the update * minor change Co-authored-by: Shamant Raghav --- package.json | 9 +++-- src/hooks/plugin-install.js | 7 ++++ src/hooks/postrun/plugin-postrun.js | 8 ++--- src/hooks/prerun/install-prerun.js | 10 ++++++ src/services/post-install-utility.js | 2 +- test/hooks/plugin-install.test.js | 6 ++-- test/hooks/postrun/plugin-postrun.test.js | 22 +++++++++++++ test/hooks/prerun/install-prerun.test.js | 40 +++++++++++++++++++++++ 8 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 src/hooks/prerun/install-prerun.js create mode 100644 test/hooks/prerun/install-prerun.test.js diff --git a/package.json b/package.json index 9ddd531ee..afd44739f 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@oclif/plugin-help": "^3.2.2", "@oclif/plugin-plugins": "^1.8.3", "@oclif/plugin-warn-if-update-available": "^1.7.0", + "@oclif/plugin-update": "^1.3.10", "@sendgrid/mail": "^7.2.1", "@twilio/cli-core": "twilio/twilio-cli-core#5.27.1-rc", "chalk": "^4.1.0", @@ -84,11 +85,12 @@ "@oclif/plugin-autocomplete", "@oclif/plugin-help", "@oclif/plugin-plugins", - "@oclif/plugin-warn-if-update-available" + "@oclif/plugin-warn-if-update-available", + "@oclif/plugin-update" ], "warn-if-update-available": { "timeoutInDays": 1, - "message": "<%= config.name %> update available from <%= chalk.greenBright(config.version) %> to <%= chalk.greenBright(latest) %>.", + "message": "<%= config.name %> update available from <%= chalk.greenBright(config.version) %> to <%= chalk.greenBright(latest) %>.\nTry running '<%= chalk.greenBright('twilio update') %>' to update the CLI", "registry": "https://registry.npmjs.org" }, "topics": { @@ -111,6 +113,9 @@ "plugins:preinstall": [ "./src/hooks/plugin-install" ], + "prerun": [ + "./src/hooks/prerun/install-prerun" + ], "postrun": [ "./src/hooks/postrun/plugin-postrun" ], diff --git a/src/hooks/plugin-install.js b/src/hooks/plugin-install.js index 023012109..a6f915b06 100644 --- a/src/hooks/plugin-install.js +++ b/src/hooks/plugin-install.js @@ -1,8 +1,15 @@ const { logger } = require('@twilio/cli-core').services.logging; +const chalk = require('chalk'); const { isTwilioPlugin } = require('../services/plugins'); +const AUTOCOMLETE_INSTALL_WARNING = `If you’re using autocomplete, you’ll need to run ${chalk.bold( + 'twilio autocomplete', +)} after installing a plugin and then open a new terminal window. The CLI needs to re-build its cache.`; + module.exports = async function pluginInstall(options) { + logger.warn(chalk.yellowBright(`${AUTOCOMLETE_INSTALL_WARNING}`)); + if (!isTwilioPlugin(options.plugin.name)) { logger.warn('WARNING!!! You are attempting to install a plugin from an untrusted source.'); logger.warn('It could contain malicious software or in other ways compromise your system.'); diff --git a/src/hooks/postrun/plugin-postrun.js b/src/hooks/postrun/plugin-postrun.js index e7426387e..40c7f7bf5 100644 --- a/src/hooks/postrun/plugin-postrun.js +++ b/src/hooks/postrun/plugin-postrun.js @@ -1,16 +1,16 @@ /* eslint-disable no-console */ const chalk = require('chalk'); -const AUTOCOMLETE_WARNING = `If you’re using autocomplete, you’ll need to run ${chalk.bold( +const AUTOCOMLETE_UPDATE_WARNING = `If you’re using autocomplete, you’ll need to run ${chalk.bold( 'twilio autocomplete', -)} after installing a plugin and then open a new terminal window. The CLI needs to re-build its cache.`; +)} after an update and then open a new terminal window. The CLI needs to re-build its cache.`; const AUTOCOMLETE_ALERT = `If you are running bash or zsh on macOS or Linux, you can run one of the two commands below (as appropriate for the shell you are using): \n '${chalk.bold( '1) twilio autocomplete bash', )}' or \n '${chalk.bold('2) twilio autocomplete zsh')}' `; module.exports = async function pluginPostRun(options) { - if (options.Command.id === 'plugins:update') { - console.warn(chalk.yellowBright(` » ${AUTOCOMLETE_WARNING}`)); + if (options.Command.id === 'plugins:update' || options.Command.id === 'update') { + console.warn(chalk.yellowBright(` » ${AUTOCOMLETE_UPDATE_WARNING}`)); } if (options.Command.id === 'autocomplete') { if (Object.keys(options.argv).length === 0) { diff --git a/src/hooks/prerun/install-prerun.js b/src/hooks/prerun/install-prerun.js new file mode 100644 index 000000000..940ca42f6 --- /dev/null +++ b/src/hooks/prerun/install-prerun.js @@ -0,0 +1,10 @@ +/* eslint-disable no-console */ +const chalk = require('chalk'); + +const UPDATE_ALERT = `Warning: Use '${chalk.bold('npm update twilio-cli')}' for npm based installations. `; + +module.exports = async function pluginPostRun(options) { + if (options.Command.id === 'update') { + console.warn(chalk.greenBright(` » ${UPDATE_ALERT}`)); + } +}; diff --git a/src/services/post-install-utility.js b/src/services/post-install-utility.js index 7b6c13850..ff1833ac4 100644 --- a/src/services/post-install-utility.js +++ b/src/services/post-install-utility.js @@ -9,7 +9,7 @@ const PORT_WARNING = `Profiles exist with API keys in system keychain. Please ru const AUTOCOMLETE_WARNING = `If you’re using autocomplete, you’ll need to run '${chalk.bold( 'twilio autocomplete', -)}' after installing a plugin and then open a new terminal window. The CLI needs to re-build its cache.`; +)}' after the install and then open a new terminal window. The CLI needs to re-build its cache.`; class PostInstallDisplayManager { constructor(configDir, userConfig) { diff --git a/test/hooks/plugin-install.test.js b/test/hooks/plugin-install.test.js index 045c013fc..5cd64cd69 100644 --- a/test/hooks/plugin-install.test.js +++ b/test/hooks/plugin-install.test.js @@ -33,11 +33,13 @@ describe('hooks', () => { before(() => { inquirer._prompt = inquirer.prompt; inquirer.prompt = sinon.stub().resolves({ continue: false }); + warnSpy = sinon.spy(console, 'warn'); }); after(() => { inquirer.prompt = inquirer._prompt; delete inquirer._prompt; + warnSpy.restore(); }); test.stderr().it('warning when non Twilio plugin is installed', async (ctx) => { @@ -54,9 +56,9 @@ describe('hooks', () => { test.stderr().it('outputs nothing when Twilio plugin is installed', async (ctx) => { await pluginFunc.call(ctx, getTwilioPlugin()); - expect(ctx.stderr).to.be.empty; + expect(ctx.stderr).to.contain('twilio autocomplete'); await pluginFunc.call(ctx, getTwilioLabsPlugin()); - expect(ctx.stderr).to.be.empty; + expect(ctx.stderr).to.contain('twilio autocomplete'); }); }); }); diff --git a/test/hooks/postrun/plugin-postrun.test.js b/test/hooks/postrun/plugin-postrun.test.js index 6e3cc594e..cb2cd0945 100644 --- a/test/hooks/postrun/plugin-postrun.test.js +++ b/test/hooks/postrun/plugin-postrun.test.js @@ -10,6 +10,18 @@ const pluginUpdate = { }, }; +const update = { + Command: { + id: 'update', + }, +}; + +const pluginInstall = { + Command: { + id: 'plugins:install', + }, +}; + const pluginAvailable = { Command: { id: 'plugins:available', @@ -52,6 +64,16 @@ describe('hooks', () => { expect(console.warn.calledWith(sinon.match('twilio autocomplete'))).to.be.true; }); + test.it('warning when cli is updated', () => { + pluginFunc(update); + expect(console.warn.calledWith(sinon.match('twilio autocomplete'))).to.be.true; + }); + + test.it('warning when plugin is installed', () => { + pluginFunc(pluginInstall); + expect(console.warn.calledWith(sinon.match('twilio autocomplete'))).to.be.true; + }); + test.it('warning when autocorrect has no arguments', () => { pluginFunc(autocomplete); expect(console.warn.calledWith(sinon.match('twilio autocomplete bash'))).to.be.true; diff --git a/test/hooks/prerun/install-prerun.test.js b/test/hooks/prerun/install-prerun.test.js new file mode 100644 index 000000000..4562f1f84 --- /dev/null +++ b/test/hooks/prerun/install-prerun.test.js @@ -0,0 +1,40 @@ +/* eslint-disable no-console */ +const sinon = require('sinon'); +const { expect, test } = require('@twilio/cli-test'); + +const pluginFunc = require('../../../src/hooks/prerun/install-prerun'); + +const update = { + Command: { + id: 'update', + }, +}; +const pluginAvailable = { + Command: { + id: 'plugins:available', + }, +}; + +describe('hooks', () => { + describe('postrun', () => { + describe('plugin-postrun', () => { + before(() => { + warnSpy = sinon.spy(console, 'warn'); + }); + + after(() => { + warnSpy.restore(); + }); + + test.it('no warning when other commands are run', () => { + pluginFunc(pluginAvailable); + expect(console.warn.calledWith(sinon.match('npm update'))).to.be.false; + }); + + test.it('warning when update command is run', () => { + pluginFunc(update); + expect(console.warn.calledWith(sinon.match('npm update'))).to.be.true; + }); + }); + }); +});