From eebef3e46198b22558e19d87f16a88a24b63a16f Mon Sep 17 00:00:00 2001 From: Juwan Wheatley Date: Wed, 7 Apr 2021 13:08:00 -0400 Subject: [PATCH] [eas-cli] use autocomplete list for secrets:delete (#309) * [eas-cli] use autocomplete list for secrets:delete * update changelog * check if secret is account-wide and tell user that it could affect multiple apps --- CHANGELOG.md | 2 + .../eas-cli/src/commands/secrets/delete.ts | 55 ++++++++++++++++--- packages/eas-cli/src/commands/secrets/list.ts | 12 +--- .../queries/EnvironmentSecretsQuery.ts | 19 +++++++ 4 files changed, 69 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 908774a442..3556eacd4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ This is the log of notable changes to EAS CLI and related packages. - `secrets:create` now uses flags rather than positional arguments ([#300](https://github.com/expo/eas-cli/pull/300) by [@fiberjw](https://github.com/fiberjw)) - `secrets:create`'s `target` arg is now called `scope` ([#300](https://github.com/expo/eas-cli/pull/300) by [@fiberjw](https://github.com/fiberjw)) - `secrets:list`'s `target` property is now called `scope` ([#300](https://github.com/expo/eas-cli/pull/300) by [@fiberjw](https://github.com/fiberjw)) +- `secrets:delete`'s `ID` arg is now optional ([#309](https://github.com/expo/eas-cli/pull/309) by [@fiberjw](https://github.com/fiberjw)) +- `secrets:delete`'s now allows users to choose secrets from a list ([#309](https://github.com/expo/eas-cli/pull/309) by [@fiberjw](https://github.com/fiberjw)) ### 🎉 New features diff --git a/packages/eas-cli/src/commands/secrets/delete.ts b/packages/eas-cli/src/commands/secrets/delete.ts index 1b621e3dd8..96524cbbfd 100644 --- a/packages/eas-cli/src/commands/secrets/delete.ts +++ b/packages/eas-cli/src/commands/secrets/delete.ts @@ -3,14 +3,24 @@ import { Command } from '@oclif/command'; import chalk from 'chalk'; import { EnvironmentSecretMutation } from '../../graphql/mutations/EnvironmentSecretMutation'; +import { + EnvironmentSecretWithScope, + EnvironmentSecretsQuery, +} from '../../graphql/queries/EnvironmentSecretsQuery'; import Log from '../../log'; import { isEasEnabledForProjectAsync, warnEasUnavailable, } from '../../project/isEasEnabledForProject'; -import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils'; -import { toggleConfirmAsync } from '../../prompts'; +import { + findProjectRootAsync, + getProjectAccountNameAsync, + getProjectFullNameAsync, + getProjectIdAsync, +} from '../../project/projectUtils'; +import { promptAsync, toggleConfirmAsync } from '../../prompts'; import { ensureLoggedInAsync } from '../../user/actions'; +import { EnvironmentSecretScope } from './create'; export default class EnvironmentSecretDelete extends Command { static description = `Delete an environment secret by ID. @@ -19,7 +29,7 @@ Unsure where to find the secret's ID? Run ${chalk.bold('eas secrets:list')}`; static args = [ { name: 'id', - required: true, + required: false, description: `ID of the secret to delete`, }, ]; @@ -30,6 +40,8 @@ Unsure where to find the secret's ID? Run ${chalk.bold('eas secrets:list')}`; const projectDir = (await findProjectRootAsync()) ?? process.cwd(); const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true }); const projectId = await getProjectIdAsync(exp); + const projectFullName = await getProjectFullNameAsync(exp); + const projectAccountName = await getProjectAccountNameAsync(exp); if (!(await isEasEnabledForProjectAsync(projectId))) { warnEasUnavailable(); @@ -37,23 +49,50 @@ Unsure where to find the secret's ID? Run ${chalk.bold('eas secrets:list')}`; return; } - const { + let { args: { id }, } = this.parse(EnvironmentSecretDelete); + let secret: EnvironmentSecretWithScope | undefined; + + if (!id) { + const secrets = await EnvironmentSecretsQuery.allAsync(projectAccountName, projectFullName); + + ({ secret } = await promptAsync({ + type: 'autocomplete', + name: 'secret', + message: 'Pick the secret to be deleted:', + choices: secrets.map(secret => ({ + title: `${secret.name} (${secret.scope})`, + value: secret, + })), + })); + + id = secret?.id; + } Log.addNewLineIfNone(); Log.warn( - `You are about to permamently delete secret with id: "${id}".\nThis action is irreversible.` + `You are about to permamently delete secret${ + secret?.name ? ` "${secret?.name}"` : '' + } with id: "${id}".\nThis action is irreversible.` ); Log.newLine(); - const confirmed = await toggleConfirmAsync({ message: 'Are you sure you wish to proceed?' }); + const confirmed = await toggleConfirmAsync({ + message: `Are you sure you wish to proceed?${ + secret?.scope === EnvironmentSecretScope.ACCOUNT + ? ' This secret is applied across your whole account and may affect multiple apps.' + : '' + }`, + }); if (!confirmed) { - Log.error(`Canceled deletion of secret with id: "${id}".`); + Log.error( + `Canceled deletion of secret${secret?.name ? ` "${secret?.name}"` : ''} with id: "${id}".` + ); process.exit(1); } await EnvironmentSecretMutation.delete(id); - Log.withTick(`️Deleted secret with id "${id}".`); + Log.withTick(`️Deleted secret${secret?.name ? ` "${secret?.name}"` : ''} with id "${id}".`); } } diff --git a/packages/eas-cli/src/commands/secrets/list.ts b/packages/eas-cli/src/commands/secrets/list.ts index b1eff69210..915d0e43e6 100644 --- a/packages/eas-cli/src/commands/secrets/list.ts +++ b/packages/eas-cli/src/commands/secrets/list.ts @@ -4,7 +4,6 @@ import chalk from 'chalk'; import Table from 'cli-table3'; import dateFormat from 'dateformat'; -import { EnvironmentSecretFragment } from '../../graphql/generated'; import { EnvironmentSecretsQuery } from '../../graphql/queries/EnvironmentSecretsQuery'; import Log from '../../log'; import { @@ -18,7 +17,6 @@ import { getProjectIdAsync, } from '../../project/projectUtils'; import { ensureLoggedInAsync } from '../../user/actions'; -import { EnvironmentSecretScope } from './create'; export default class EnvironmentSecretsList extends Command { static description = 'Lists environment secrets available for your current app'; @@ -43,15 +41,7 @@ export default class EnvironmentSecretsList extends Command { throw new Error("Please run this command inside your project's directory"); } - const [accountSecrets, appSecrets] = await Promise.all([ - EnvironmentSecretsQuery.byAcccountNameAsync(projectAccountName), - EnvironmentSecretsQuery.byAppFullNameAsync(projectFullName), - ]); - - const secrets = [ - ...appSecrets.map(s => ({ ...s, scope: EnvironmentSecretScope.PROJECT })), - ...accountSecrets.map(s => ({ ...s, scope: EnvironmentSecretScope.ACCOUNT })), - ] as (EnvironmentSecretFragment & { scope: EnvironmentSecretScope })[]; + const secrets = await EnvironmentSecretsQuery.allAsync(projectAccountName, projectFullName); const table = new Table({ head: ['Name', 'Scope', 'ID', 'Updated at'], diff --git a/packages/eas-cli/src/graphql/queries/EnvironmentSecretsQuery.ts b/packages/eas-cli/src/graphql/queries/EnvironmentSecretsQuery.ts index ca539f1d0c..a8521d7263 100644 --- a/packages/eas-cli/src/graphql/queries/EnvironmentSecretsQuery.ts +++ b/packages/eas-cli/src/graphql/queries/EnvironmentSecretsQuery.ts @@ -1,6 +1,7 @@ import { print } from 'graphql'; import gql from 'graphql-tag'; +import { EnvironmentSecretScope } from '../../commands/secrets/create'; import { graphqlClient, withErrorHandlingAsync } from '../client'; import { EnvironmentSecretFragment, @@ -9,6 +10,10 @@ import { } from '../generated'; import { EnvironmentSecretFragmentNode } from '../types/EnvironmentSecret'; +export type EnvironmentSecretWithScope = EnvironmentSecretFragment & { + scope: EnvironmentSecretScope; +}; + export const EnvironmentSecretsQuery = { async byAcccountNameAsync(accountName: string): Promise { const data = await withErrorHandlingAsync( @@ -60,4 +65,18 @@ export const EnvironmentSecretsQuery = { return data.app?.byFullName.environmentSecrets ?? []; }, + async allAsync( + projectAccountName: string, + projectFullName: string + ): Promise { + const [accountSecrets, appSecrets] = await Promise.all([ + this.byAcccountNameAsync(projectAccountName), + this.byAppFullNameAsync(projectFullName), + ]); + + return [ + ...appSecrets.map(s => ({ ...s, scope: EnvironmentSecretScope.PROJECT })), + ...accountSecrets.map(s => ({ ...s, scope: EnvironmentSecretScope.ACCOUNT })), + ]; + }, };