Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add confirmation prompts to unsafe cli commands #6878

Merged
merged 53 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
7240f02
feat: added logic handeling for the `env:set` command
wconrad265 Oct 9, 2024
8bc880a
feat: prompt before setting env variable across context and scope
Oct 9, 2024
e068e56
fix: prettier
Oct 9, 2024
5b5c050
fix: refactored prompts
Oct 9, 2024
f77233d
fix: refactor prompts
Oct 9, 2024
ba41978
feat: env:unset prompts user before unsetting env variable indiscrimi…
Oct 9, 2024
679dcc4
feat: created tests for env:set prompts
wconrad265 Oct 10, 2024
f19207c
build: refactored env:set promts and rewrote tests
wconrad265 Oct 11, 2024
a404382
feat: added prompt for env:clone and tests
wconrad265 Oct 11, 2024
1d6c8f6
fix: prettier fix
wconrad265 Oct 11, 2024
4ebe87f
build: added prompts and tests for blob command
wconrad265 Oct 14, 2024
d7c4065
Merge branch 'netlify:main' into prompts
wconrad265 Oct 14, 2024
98a1399
fix: updated tests in file to reflect new prompts
wconrad265 Oct 14, 2024
31cd93e
fix: updated documentation
wconrad265 Oct 15, 2024
cf6b27a
fix: updated error
wconrad265 Oct 15, 2024
6bfb620
fix: updated new lines in messages for consistence
wconrad265 Oct 15, 2024
4f21d60
fix: fixed prettier error
wconrad265 Oct 15, 2024
fc46eb3
Merge branch 'netlify:main' into prompts
wconrad265 Oct 21, 2024
7527f74
feat: env-set refactored
wconrad265 Oct 21, 2024
469691c
fix: reactored env:unset prompts
wconrad265 Oct 21, 2024
f6aa58d
fix: refactored prompts and tests messages
wconrad265 Oct 22, 2024
9ba5ada
fix: another pass of refactoring
wconrad265 Oct 22, 2024
a51fe6e
feat: added skip for non interactive shell and CI
wconrad265 Oct 22, 2024
fe4bc7f
feat: refactored code for tests realted to ci and prompts
wconrad265 Oct 22, 2024
fea9bb4
fix: prettier fix
wconrad265 Oct 22, 2024
8c65226
fix: removed console.log statements
wconrad265 Oct 22, 2024
0ef4b7b
fix: updated prompts based on pr feedback
wconrad265 Oct 24, 2024
2ad45c2
feat: added force flag option to all commands
Oct 25, 2024
4e42609
fix: started updating tests to work with higher level --force flag fo…
Oct 28, 2024
486d457
feat: refactored tests to use mockProgram
wconrad265 Oct 28, 2024
4f78ab7
Merge remote-tracking branch 'origin/main' into prompts
wconrad265 Oct 28, 2024
621c640
feat: refactor of run.js into components to add force flag
wconrad265 Oct 29, 2024
0d35797
Merge branch 'main' into prompts
wconrad265 Oct 29, 2024
26d70c8
fix: types.ts merge } deletion
wconrad265 Oct 29, 2024
f45f6d4
fix: fix default lint issue and typescript issue
wconrad265 Oct 29, 2024
637df20
fix: update blob to blobs
wconrad265 Oct 29, 2024
cbc3b7c
fix: updated prompt tests for ci/cd enviroment
wconrad265 Oct 29, 2024
c9592a0
fix: updated prompt tests to work correctly in ci/cd enviroments
wconrad265 Oct 29, 2024
b2b200d
fix: updated types and env variables not being restored after tests
wconrad265 Oct 29, 2024
c929fcc
fix: fixed tests
wconrad265 Oct 30, 2024
6707b33
fix: fixed flakey deploy test and added env cleanup to more tests
wconrad265 Oct 30, 2024
34d0b88
fix: removed a console.log() statement
wconrad265 Oct 30, 2024
ed3c6f3
fix: cleaned up unused functions and comments
wconrad265 Oct 31, 2024
7392566
chore: cleanup comments minor bug fixes
tlane25 Nov 4, 2024
b5bdf41
Merge branch 'main' into prompts
wconrad265 Nov 4, 2024
fa4511e
chore: prettier, needed to update docs
tlane25 Nov 4, 2024
2e54152
chore: merged updates to main.ts
tlane25 Nov 4, 2024
69792dd
fix: reset env variable and mocks type error
wconrad265 Nov 4, 2024
e4bbd4f
fix: fixed test type error part 2
wconrad265 Nov 4, 2024
31a2269
fix: removed restModules from test
wconrad265 Nov 4, 2024
e35908c
fix: updated test to have inquirer mocked correctly
wconrad265 Nov 4, 2024
969e422
fix: reverted the force flags on lm commands to original behavior
tlane25 Nov 4, 2024
71b6ab2
Merge branch 'main' into prompts
DanielSLew Nov 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/commands/blobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ netlify blobs:delete
**Flags**

- `filter` (*string*) - For monorepos, specify the name of the application to run the command in
- `force` (*boolean*) - Force the operation to proceed without confirmation or warnings
- `debug` (*boolean*) - Print debugging information

---
Expand Down Expand Up @@ -124,6 +125,7 @@ netlify blobs:set
**Flags**

- `filter` (*string*) - For monorepos, specify the name of the application to run the command in
- `force` (*boolean*) - Force the operation to proceed without confirmation or warnings
- `input` (*string*) - Defines the filesystem path where the blob data should be read from
- `debug` (*boolean*) - Print debugging information

Expand Down
3 changes: 3 additions & 0 deletions docs/commands/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ netlify env:clone
**Flags**

- `filter` (*string*) - For monorepos, specify the name of the application to run the command in
- `force` (*boolean*) - Force the operation to proceed without confirmation or warnings
- `from` (*string*) - Site ID (From)
- `to` (*string*) - Site ID (To)
- `debug` (*boolean*) - Print debugging information
Expand Down Expand Up @@ -167,6 +168,7 @@ netlify env:set

- `context` (*string*) - Specify a deploy context or branch (contexts: "production", "deploy-preview", "branch-deploy", "dev") (default: all contexts)
- `filter` (*string*) - For monorepos, specify the name of the application to run the command in
- `force` (*boolean*) - Force the operation to proceed without confirmation or warnings
- `scope` (*builds | functions | post-processing | runtime*) - Specify a scope (default: all scopes)
- `secret` (*boolean*) - Indicate whether the environment variable value can be read again.
- `debug` (*boolean*) - Print debugging information
Expand Down Expand Up @@ -202,6 +204,7 @@ netlify env:unset

- `context` (*string*) - Specify a deploy context or branch (contexts: "production", "deploy-preview", "branch-deploy", "dev") (default: all contexts)
- `filter` (*string*) - For monorepos, specify the name of the application to run the command in
- `force` (*boolean*) - Force the operation to proceed without confirmation or warnings
- `debug` (*boolean*) - Print debugging information

**Examples**
Expand Down
17 changes: 15 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@
"@netlify/functions": "2.8.2",
"@sindresorhus/slugify": "2.2.1",
"@types/fs-extra": "11.0.4",
"@types/inquirer": "9.0.7",
"@types/inquirer": "^9.0.7",
"@types/jsonwebtoken": "9.0.7",
"@types/lodash": "4.17.10",
"@types/node": "20.14.8",
Expand Down
3 changes: 1 addition & 2 deletions src/commands/base-command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { isCI } from 'ci-info'

import { existsSync } from 'fs'
import { join, relative, resolve } from 'path'
import process from 'process'
Expand All @@ -8,6 +6,7 @@ import { format } from 'util'
import { DefaultLogger, Project } from '@netlify/build-info'
import { NodeFS, NoopLogger } from '@netlify/build-info/node'
import { resolveConfig } from '@netlify/config'
import { isCI } from 'ci-info'
import { Command, Help, Option } from 'commander'
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'debu... Remove this comment to see the full error message
import debug from 'debug'
Expand Down
11 changes: 10 additions & 1 deletion src/commands/blobs/blobs-delete.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
import { getStore } from '@netlify/blobs'

import { chalk, error as printError } from '../../utils/command-helpers.js'
import { chalk, error as printError, log } from '../../utils/command-helpers.js'
import { blobDeletePrompts } from '../../utils/prompts/blob-delete-prompts.js'

/**
* The blobs:delete command
*/
export const blobsDelete = async (storeName: string, key: string, _options: Record<string, unknown>, command: any) => {
const { api, siteInfo } = command.netlify
const { force } = _options

const store = getStore({
apiURL: `${api.scheme}://${api.host}`,
name: storeName,
siteID: siteInfo.id ?? '',
token: api.accessToken ?? '',
})

if (force === undefined) {
await blobDeletePrompts(key, storeName)
}

try {
await store.delete(key)

log(`${chalk.greenBright('Success')}: Blob ${chalk.yellow(key)} deleted from store ${chalk.yellow(storeName)}`)
} catch {
return printError(`Could not delete blob ${chalk.yellow(key)} from store ${chalk.yellow(storeName)}`)
}
Expand Down
17 changes: 13 additions & 4 deletions src/commands/blobs/blobs-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { resolve } from 'path'
import { getStore } from '@netlify/blobs'
import { OptionValues } from 'commander'

import { chalk, error as printError, isNodeError } from '../../utils/command-helpers.js'
import { chalk, error as printError, isNodeError, log } from '../../utils/command-helpers.js'
import { blobSetPrompts } from '../../utils/prompts/blob-set-prompt.js'
import BaseCommand from '../base-command.js'

interface Options extends OptionValues {
input?: string
force?: string
}

export const blobsSet = async (
Expand All @@ -19,19 +21,17 @@ export const blobsSet = async (
command: BaseCommand,
) => {
const { api, siteInfo } = command.netlify
const { input } = options
const { force, input } = options
const store = getStore({
apiURL: `${api.scheme}://${api.host}`,
name: storeName,
siteID: siteInfo.id ?? '',
token: api.accessToken ?? '',
})

let value = valueParts.join(' ')

if (input) {
const inputPath = resolve(input)

try {
value = await fs.readFile(inputPath, 'utf8')
} catch (error) {
Expand All @@ -57,8 +57,17 @@ export const blobsSet = async (
)
}

if (force === undefined) {
const existingValue = await store.get(key)

if (existingValue) {
await blobSetPrompts(key, storeName)
}
}

try {
await store.set(key, value)
log(`${chalk.greenBright('Success')}: Blob ${chalk.yellow(key)} set in store ${chalk.yellow(storeName)}`)
} catch {
return printError(`Could not set blob ${chalk.yellow(key)} in store ${chalk.yellow(storeName)}`)
}
Expand Down
2 changes: 2 additions & 0 deletions src/commands/blobs/blobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const createBlobsCommand = (program: BaseCommand) => {
.description(`Deletes an object with a given key, if it exists, from a Netlify Blobs store`)
.argument('<store>', 'Name of the store')
.argument('<key>', 'Object key')
.option('-f, --force', 'Force the operation to proceed without confirmation or warnings')
.alias('blob:delete')
.hook('preAction', requiresSiteInfo)
.action(async (storeName: string, key: string, _options: OptionValues, command: BaseCommand) => {
Expand Down Expand Up @@ -70,6 +71,7 @@ export const createBlobsCommand = (program: BaseCommand) => {
.argument('<key>', 'Object key')
.argument('[value...]', 'Object value')
.option('-i, --input <path>', 'Defines the filesystem path where the blob data should be read from')
.option('-f, --force', 'Force the operation to proceed without confirmation or warnings')
.alias('blob:set')
.hook('preAction', requiresSiteInfo)

Expand Down
11 changes: 8 additions & 3 deletions src/commands/env/env-clone.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { OptionValues } from 'commander'

import { chalk, log, error as logError } from '../../utils/command-helpers.js'
import { envClonePrompts } from '../../utils/prompts/env-clone-prompt.js'
import BaseCommand from '../base-command.js'

// @ts-expect-error TS(7006) FIXME: Parameter 'api' implicitly has an 'any' type.
Expand All @@ -18,7 +19,7 @@ const safeGetSite = async (api, siteId) => {
* @returns {Promise<boolean>}
*/
// @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message
const cloneEnvVars = async ({ api, siteFrom, siteTo }): Promise<boolean> => {
const cloneEnvVars = async ({ api, force, siteFrom, siteTo }): Promise<boolean> => {
const [envelopeFrom, envelopeTo] = await Promise.all([
api.getEnvVars({ accountId: siteFrom.account_slug, siteId: siteFrom.id }),
api.getEnvVars({ accountId: siteTo.account_slug, siteId: siteTo.id }),
Expand All @@ -36,6 +37,10 @@ const cloneEnvVars = async ({ api, siteFrom, siteTo }): Promise<boolean> => {
const siteId = siteTo.id
// @ts-expect-error TS(7031) FIXME: Binding element 'key' implicitly has an 'any' type... Remove this comment to see the full error message
const envVarsToDelete = envelopeTo.filter(({ key }) => keysFrom.includes(key))

if (envVarsToDelete.length !== 0 && Boolean(force) === false) {
await envClonePrompts(siteTo.id, envVarsToDelete)
}
// delete marked env vars in parallel
// @ts-expect-error TS(7031) FIXME: Binding element 'key' implicitly has an 'any' type... Remove this comment to see the full error message
await Promise.all(envVarsToDelete.map(({ key }) => api.deleteEnvVar({ accountId, siteId, key })))
Expand All @@ -47,12 +52,12 @@ const cloneEnvVars = async ({ api, siteFrom, siteTo }): Promise<boolean> => {
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
throw error.json ? error.json.msg : error
}

return true
}

export const envClone = async (options: OptionValues, command: BaseCommand) => {
const { api, site } = command.netlify
const { force } = options

if (!site.id && !options.from) {
log(
Expand Down Expand Up @@ -81,7 +86,7 @@ export const envClone = async (options: OptionValues, command: BaseCommand) => {
return false
}

const success = await cloneEnvVars({ api, siteFrom, siteTo })
const success = await cloneEnvVars({ api, siteFrom, siteTo, force })

if (!success) {
return false
Expand Down
16 changes: 11 additions & 5 deletions src/commands/env/env-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { OptionValues } from 'commander'

import { chalk, error, log, logJson } from '../../utils/command-helpers.js'
import { AVAILABLE_CONTEXTS, AVAILABLE_SCOPES, translateFromEnvelopeToMongo } from '../../utils/env/index.js'
import { envSetPrompts } from '../../utils/prompts/env-set-prompts.js'
import BaseCommand from '../base-command.js'

/**
* Updates the env for a site configured with Envelope with a new key/value pair
* @returns {Promise<object | boolean>}
*/
// @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message
const setInEnvelope = async ({ api, context, key, scope, secret, siteInfo, value }) => {
const setInEnvelope = async ({ api, context, force, key, scope, secret, siteInfo, value }) => {
const accountId = siteInfo.account_slug
const siteId = siteInfo.id

Expand All @@ -29,6 +30,8 @@ const setInEnvelope = async ({ api, context, key, scope, secret, siteInfo, value
}

// fetch envelope env vars
// const userData = await api.getAccount({accountId})
// log(userData)
wconrad265 marked this conversation as resolved.
Show resolved Hide resolved
const envelopeVariables = await api.getEnvVars({ accountId, siteId })
const contexts = context || ['all']
let scopes = scope || AVAILABLE_SCOPES
Expand All @@ -48,6 +51,11 @@ const setInEnvelope = async ({ api, context, key, scope, secret, siteInfo, value
// @ts-expect-error TS(7006) FIXME: Parameter 'envVar' implicitly has an 'any' type.
const existing = envelopeVariables.find((envVar) => envVar.key === key)

// Checks if --force is passed and if it is an existing variaible, then we need to prompt the user
if (Boolean(force) === false && existing) {
await envSetPrompts(key)
}

const params = { accountId, siteId, key }
try {
if (existing) {
Expand Down Expand Up @@ -108,11 +116,9 @@ const setInEnvelope = async ({ api, context, key, scope, secret, siteInfo, value
}

export const envSet = async (key: string, value: string, options: OptionValues, command: BaseCommand) => {
const { context, scope, secret } = options

const { context, force, scope, secret } = options
const { api, cachedConfig, site } = command.netlify
const siteId = site.id

if (!siteId) {
log('No site id found, please run inside a site folder or `netlify link`')
return false
Expand All @@ -121,7 +127,7 @@ export const envSet = async (key: string, value: string, options: OptionValues,
const { siteInfo } = cachedConfig

// Get current environment variables set in the UI
const finalEnv = await setInEnvelope({ api, siteInfo, key, value, context, scope, secret })
const finalEnv = await setInEnvelope({ api, siteInfo, force, key, value, context, scope, secret })

if (!finalEnv) {
return false
Expand Down
Loading