Skip to content

Commit

Permalink
feat: add confirmation prompts to unsafe cli commands (#6878)
Browse files Browse the repository at this point in the history
* feat: added logic handeling for the `env:set` command

Will prompt the user if scope and/or context is not provided

Co-authored-by: Thomas Lane <[email protected]>

* feat: prompt before setting env variable across context and scope
Co-authored-by: Will <[email protected]>

* fix: prettier
Co-authored-by: Will <[email protected]>

* fix: refactored prompts
Co-authored-by: Will <[email protected]>

* fix: refactor prompts
Co-authored-by: Will <[email protected]>

* feat: env:unset prompts user before unsetting env variable indiscriminantly across contexts
Co-authored-by: Will <[email protected]>

* feat: created tests for env:set prompts

Created several tests to check env:test prompts

* build: refactored env:set promts and rewrote tests

created a new directory in utils called prompts, to store all future prompts.

rewrote the prompts to only check for destructive actions.

added tests for each of the destructive prompts

Co-authored-by: Thomas Lane <[email protected]>

* feat: added prompt for env:clone and tests

Co-authored-by: Thomas Lane <[email protected]>

* fix: prettier fix
Co-authored-by: Thomas Lane <[email protected]>

* build: added prompts and tests for blob command

for blobl:set and blob:delete

Co-authored-by: Thomas Lane <[email protected]>

* fix: updated tests in file to reflect new prompts

* fix: updated documentation

updated the documentation

Co-authored-by: Thomas Lane <[email protected]>

* fix: updated error

updated error handeling

Co-authored-by: Thomas Lane <[email protected]>
Co-authored-by: Thomas Lane <[email protected]>

* fix: updated new lines in messages for consistence

updated prompts spacing for consistencey

Co-authored-by: Thomas Lane <[email protected]>

* fix: fixed prettier error

Co-authored-by: Thomas Lane <[email protected]>

* feat: env-set refactored

refactored messages in env-set to a function that exports an object to be reused

Co-authored-by: Thomas Lane <[email protected]>

* fix: reactored env:unset prompts

Co-authored-by: Thomas Lane <[email protected]>

* fix: refactored prompts and tests messages

Co-authored-by: Thomas Lane <[email protected]>
Co-authored-by: Thomas Lane <[email protected]>

* fix: another pass of refactoring

env and blob commands

Co-authored-by: Thomas Lane <[email protected]>
Co-authored-by: Thomas Lane <[email protected]>

* feat: added skip for non interactive shell and CI

Co-authored-by: Thomas Lane <[email protected]>
Co-authored-by: Thomas Lane <[email protected]>

* feat: refactored code for tests realted to ci and prompts

Co-authored-by: Thomas Lane <[email protected]>
Co-authored-by: Thomas Lane <[email protected]>

* fix: prettier fix

Co-authored-by: Thomas Lane <[email protected]>

* fix: removed console.log statements

Co-authored-by: Thomas Lane <[email protected]>

* fix: updated prompts based on pr feedback

Co-authored-by: Thomas Lane <[email protected]>

* feat: added force flag option to all commands
scripted commands automatically given the force flag
Co-authored-by: Will <[email protected]>

* fix: started updating tests to work with higher level --force flag for scritped commands
Co-authored-by: Will <[email protected]>

* feat: refactored tests to use mockProgram
Co-authored-by: Thomas Lane <[email protected]>

* feat: refactor of run.js into components to add force flag

Co-authored-by: Thomas Lane <[email protected]>

* fix: types.ts merge } deletion
Co-authored-by: Thomas Lane <[email protected]>

* fix: fix default lint issue and typescript issue

fixed lint issue that was casuing test in ci enviroment to fail

Co-authored-by: Thomas Lane <[email protected]>

* fix: update blob to blobs

Co-authored-by: Thomas Lane <[email protected]>

* fix: updated prompt tests for ci/cd enviroment

Co-authored-by: Thomas Lane <[email protected]>

* fix: updated prompt tests to work correctly in ci/cd enviroments

Co-authored-by: Thomas Lane <[email protected]>

* fix: updated types and env variables not being restored after tests

Co-authored-by: Thomas Lane <[email protected]>

* fix: fixed tests

Co-authored-by: Thomas Lane <[email protected]>

* fix: fixed flakey deploy test and added env cleanup to more tests

* fix: removed a console.log() statement

Co-authored-by: Thomas Lane <[email protected]>

* fix: cleaned up unused functions and comments

Co-authored-by: Thomas Lane <[email protected]>

* chore: cleanup comments minor bug fixes
Co-authored-by: Will <[email protected]>

* chore: prettier, needed to update docs
Co-authored-by: Will <[email protected]>

* chore: merged updates to main.ts
Co-authored-by: Will <[email protected]>

* fix: reset env variable and mocks type error

* fix: fixed test type error part 2

* fix: removed restModules from test

* fix: updated test to have inquirer mocked correctly

add the missing methods to the inquirer mock to fix all type errors

* fix: reverted the force flags on lm commands to original behavior
as the force option was for credential helper installation rather than skipping prompts
Co-authored-by: Will <[email protected]>

---------

Co-authored-by: Thomas Lane <[email protected]>
Co-authored-by: t <[email protected]>
Co-authored-by: Thomas Lane <[email protected]>
Co-authored-by: Daniel Lew <[email protected]>
  • Loading branch information
5 people authored Nov 6, 2024
1 parent d132ddd commit fd77a12
Show file tree
Hide file tree
Showing 46 changed files with 1,639 additions and 140 deletions.
6 changes: 4 additions & 2 deletions bin/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { argv } from 'process'

import updateNotifier from 'update-notifier'

import { createMainCommand } from '../dist/commands/index.js'
import { runProgram } from '../dist/utils/run-program.js'
import { error } from '../dist/utils/command-helpers.js'
import getPackageJson from '../dist/utils/get-package-json.js'
import { createMainCommand } from '../dist/commands/main.js'

// 12 hours
const UPDATE_CHECK_INTERVAL = 432e5
Expand All @@ -24,7 +25,8 @@ try {
const program = createMainCommand()

try {
await program.parseAsync(argv)
await runProgram(program, argv)

program.onEnd()
} catch (error_) {
program.onEnd(error_)
Expand Down
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*) - Bypasses prompts & Force the command to run.
- `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*) - Bypasses prompts & Force the command to run.
- `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*) - Bypasses prompts & Force the command to run.
- `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*) - Bypasses prompts & Force the command to run.
- `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*) - Bypasses prompts & Force the command to run.
- `debug` (*boolean*) - Print debugging information

**Examples**
Expand Down
2 changes: 1 addition & 1 deletion docs/commands/sites.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ netlify sites:delete
**Flags**

- `filter` (*string*) - For monorepos, specify the name of the application to run the command in
- `force` (*boolean*) - delete without prompting (useful for CI)
- `force` (*boolean*) - Delete without prompting (useful for CI).
- `debug` (*boolean*) - Print debugging information

**Examples**
Expand Down
33 changes: 24 additions & 9 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.13",
"@types/node": "20.14.8",
Expand Down
1 change: 0 additions & 1 deletion src/commands/addons/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ Add-ons are a way to extend the functionality of your Netlify site`,
.description(
`Remove an add-on extension to your site\nAdd-ons are a way to extend the functionality of your Netlify site`,
)
.option('-f, --force', 'delete without prompting (useful for CI)')
.action(async (addonName: string, options: OptionValues, command: BaseCommand) => {
const { addonsDelete } = await import('./addons-delete.js')
await addonsDelete(addonName, options, command)
Expand Down
4 changes: 2 additions & 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 Expand Up @@ -187,6 +186,7 @@ export default class BaseCommand extends Command {
createCommand(name: string): BaseCommand {
const base = new BaseCommand(name)
// If --silent or --json flag passed disable logger
// .addOption(new Option('--force', 'Force command to run. Bypasses prompts for certain destructive commands.'))
.addOption(new Option('--json', 'Output return values as JSON').hideHelp(true))
.addOption(new Option('--silent', 'Silence CLI output').hideHelp(true))
.addOption(new Option('--cwd <cwd>').hideHelp(true))
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 { promptBlobDelete } 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 promptBlobDelete(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 { promptBlobSetOverwrite } from '../../utils/prompts/blob-set-prompt.js'
import BaseCommand from '../base-command.js'

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

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 promptBlobSetOverwrite(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
Loading

0 comments on commit fd77a12

Please sign in to comment.