diff --git a/e2e/install.e2e.ts b/e2e/install.e2e.ts index a8d8bc2fddf..fdd388676c3 100644 --- a/e2e/install.e2e.ts +++ b/e2e/install.e2e.ts @@ -7,6 +7,7 @@ import { platform } from 'node:os' import path from 'node:path' import { fileURLToPath } from 'node:url' +import ansis from 'ansis' import execa from 'execa' import { runServer } from 'verdaccio' import { describe, expect, it } from 'vitest' @@ -175,11 +176,14 @@ describe.each(tests)('%s → installs the cli and runs the help command without const binary = path.resolve(path.join(cwd, `./node_modules/.bin/netlify${platform() === 'win32' ? '.cmd' : ''}`)) const { stdout } = await execa(binary, ['help'], { cwd }) + const normalizedOutput = ansis.strip(stdout.trim()) - expect(stdout.trim(), `Help command does not start with 'VERSION':\n\n${stdout}`).toMatch(/^VERSION/) - expect(stdout, `Help command does not include 'netlify-cli/${pkg.version}':\n\n${stdout}`).toContain( + expect(normalizedOutput).toMatch(/VERSION/) + expect(normalizedOutput, `Help command does not include 'netlify-cli/${pkg.version}':\n\n${stdout}`).toContain( `netlify-cli/${pkg.version}`, ) - expect(stdout, `Help command does not include '$ netlify [COMMAND]':\n\n${stdout}`).toMatch('$ netlify [COMMAND]') + expect(normalizedOutput, `Help command does not include '$ netlify [COMMAND]':\n\n${stdout}`).toMatch( + '$ netlify [COMMAND]', + ) }) }) diff --git a/eslint.config.js b/eslint.config.js index 2ea4282c6a7..f7903fba5c9 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -79,6 +79,7 @@ export default tseslint.config( }, }, { + files: ['src/**/*'], rules: { 'no-restricted-imports': [ 'error', @@ -96,9 +97,9 @@ export default tseslint.config( }, { - name: 'chalk', + name: 'ansis', message: - 'Use the safe chalk import that handles colors for json output: `import { chalk } from "src/utils/command-helpers.js"`', + 'Use the configured ansis import that handles colors for json output: `import { ansis } from "src/utils/command-helpers.js"`', }, ], }, diff --git a/package-lock.json b/package-lock.json index e85d821ba16..24d48fbb222 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,10 +26,10 @@ "@pnpm/tabtab": "0.5.4", "ansi-escapes": "7.0.0", "ansi-to-html": "0.7.2", + "ansis": "^3.17.0", "ascii-table": "0.0.9", "backoff": "2.5.0", "boxen": "8.0.1", - "chalk": "5.4.1", "chokidar": "3.6.0", "ci-info": "4.1.0", "clean-deep": "3.4.0", @@ -8877,6 +8877,15 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -22412,10 +22421,6 @@ "sharp": "^0.32.5", "strip-ansi": "7.1.0" } - }, - "tools/lint-rules": { - "name": "eslint-plugin-workspace", - "extraneous": true } }, "dependencies": { @@ -27947,6 +27952,11 @@ } } }, + "ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==" + }, "anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", diff --git a/package.json b/package.json index 6e00df8b101..b050a17dccf 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "url": "https://github.com/netlify/cli/issues" }, "scripts": { - "_format": "prettier --loglevel=warn \"{src,tools,scripts,tests,e2e,.github}/**/*.{mjs,cjs,js,mts,md,yml,json,html,ts}\" \"*.{mjs,cjs,js,mts,yml,json,html,ts}\" \".*.{mjs,cjs,js,yml,json,html,ts}\" \"!CHANGELOG.md\" \"!**/*/package-lock.json\" \"!.github/**/*.md\"", + "_format": "prettier --loglevel=warn \"{src,scripts,tests,e2e,.github}/**/*.{mjs,cjs,js,mts,md,yml,json,html,ts}\" \"*.{mjs,cjs,js,mts,yml,json,html,ts}\" \".*.{mjs,cjs,js,yml,json,html,ts}\" \"!CHANGELOG.md\" \"!**/*/package-lock.json\" \"!.github/**/*.md\"", "build": "tsc --project tsconfig.build.json", "clean": "rm -rf dist/", "dev": "tsc --project tsconfig.build.json --watch", @@ -74,10 +74,10 @@ "@pnpm/tabtab": "0.5.4", "ansi-escapes": "7.0.0", "ansi-to-html": "0.7.2", + "ansis": "^3.17.0", "ascii-table": "0.0.9", "backoff": "2.5.0", "boxen": "8.0.1", - "chalk": "5.4.1", "chokidar": "3.6.0", "ci-info": "4.1.0", "clean-deep": "3.4.0", @@ -217,11 +217,5 @@ "typescript-eslint": "^8.26.0", "verdaccio": "6.0.5", "vitest": "1.6.1" - }, - "ava": { - "files": [ - "tools/**/*.test.js", - "tests/**/*.test.cjs" - ] } } diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 5121f48f31a..3043c7f01a1 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -7,29 +7,10 @@ import fs from 'node:fs/promises' import path from 'node:path' import { fileURLToPath } from 'node:url' import process from 'node:process' -// eslint-disable-next-line no-restricted-imports -import chalk from 'chalk' -const __dirname = path.dirname(fileURLToPath(import.meta.url)) - -const identity = (message) => message +import ansis from 'ansis' -/** - * - * @param {string} message - * @param {Array} styles - * @returns - */ -const format = (message, styles) => { - let func = identity - try { - func = chalk - styles.forEach((style) => { - func = func[style] - }) - } catch {} - return func(message) -} +const __dirname = path.dirname(fileURLToPath(import.meta.url)) const postInstall = async () => { const { createMainCommand } = await import('../dist/commands/index.js') @@ -45,24 +26,17 @@ const postInstall = async () => { } console.log('') - console.log(await format('Success! Netlify CLI has been installed!', ['greenBright', 'bold', 'underline'])) + console.log(ansis.greenBright.bold.underline('Success! Netlify CLI has been installed!')) console.log('') console.log('Your device is now configured to use Netlify CLI to deploy and manage your Netlify sites.') console.log('') console.log('Next steps:') console.log('') - console.log( - ` ${await format('netlify init', [ - 'cyanBright', - 'bold', - ])} Connect or create a Netlify site from current directory`, - ) - console.log( - ` ${await format('netlify deploy', ['cyanBright', 'bold'])} Deploy the latest changes to your Netlify site`, - ) + console.log(` ${ansis.cyanBright.bold('netlify init')} Connect or create a Netlify site from current directory`) + console.log(` ${ansis.cyan.bold('netlify deploy')} Deploy the latest changes to your Netlify site`) console.log('') - console.log(`For more information on the CLI run ${await format('netlify help', ['cyanBright', 'bold'])}`) - console.log(`Or visit the docs at ${await format('https://cli.netlify.com', ['cyanBright', 'bold'])}`) + console.log(`For more information on the CLI run ${ansis.cyanBright.bold('netlify help')}`) + console.log(`Or visit the docs at ${ansis.cyanBright.bold('https://cli.netlify.com')}`) console.log('') } diff --git a/scripts/prepublishOnly.js b/scripts/prepublishOnly.js index 03f396a7d01..e0a33c2b988 100644 --- a/scripts/prepublishOnly.js +++ b/scripts/prepublishOnly.js @@ -10,7 +10,6 @@ const main = async () => { // // Leaving development dependencies makes the CLI installation significantly larger and increases // the risk of platform-specific dependency installation issues. - // eslint-disable-next-line no-restricted-properties const packageJSONPath = path.join(process.cwd(), 'package.json') const rawPackageJSON = await fs.readFile(packageJSONPath, 'utf8') diff --git a/site/scripts/util/generate-command-data.js b/site/scripts/util/generate-command-data.js index 9fa3599fdea..78f10ce7b75 100644 --- a/site/scripts/util/generate-command-data.js +++ b/site/scripts/util/generate-command-data.js @@ -1,11 +1,9 @@ import { createMainCommand } from '../../../src/commands/index.js' -import { sortOptions } from '../../../src/utils/command-helpers.js' +import { compareOptions } from '../../../src/utils/command-helpers.js' const program = createMainCommand() -/** @type {Array} */ -// @ts-ignore typecast needed -const commands = program.commands.sort((cmdA, cmdB) => cmdA.name().localeCompare(cmdB.name())) +const commands = [...program.commands].sort((cmdA, cmdB) => cmdA.name().localeCompare(cmdB.name())) /** * @@ -19,7 +17,7 @@ const parseCommand = function (command) { const flags = command.options .filter((option) => !option.hidden) - .sort(sortOptions) + .sort(compareOptions) .reduce((prev, cur) => { const name = cur.long.replace('--', '') const contentType = cur.argChoices ? cur.argChoices.join(' | ') : 'string' diff --git a/src/commands/api/api.ts b/src/commands/api/api.ts index ff70ec5d95c..0a44555627b 100644 --- a/src/commands/api/api.ts +++ b/src/commands/api/api.ts @@ -2,7 +2,7 @@ import AsciiTable from 'ascii-table' import type { OptionValues } from 'commander' import { methods, type NetlifyAPI } from 'netlify' -import { chalk, logAndThrowError, exit, log, logJson } from '../../utils/command-helpers.js' +import { ansis, logAndThrowError, exit, log, logJson } from '../../utils/command-helpers.js' import type BaseCommand from '../base-command.js' type ApiMethodName = keyof NetlifyAPI @@ -23,7 +23,7 @@ export const apiCommand = async (apiMethodName: string, options: OptionValues, c log(table.toString()) log() log('Above is a list of available API methods') - log(`To run a method use "${chalk.cyanBright('netlify api methodName')}"`) + log(`To run a method use "${ansis.cyanBright('netlify api methodName')}"`) exit() } diff --git a/src/commands/api/index.ts b/src/commands/api/index.ts index 2de2729d807..4b781e95292 100644 --- a/src/commands/api/index.ts +++ b/src/commands/api/index.ts @@ -1,4 +1,4 @@ -import { chalk } from '../../utils/command-helpers.js' +import { ansis } from '../../utils/command-helpers.js' import BaseCommand from '../base-command.js' export const createApiCommand = (program: BaseCommand) => @@ -7,7 +7,7 @@ export const createApiCommand = (program: BaseCommand) => .argument('[apiMethod]', 'Open API method to run') .description( `Run any Netlify API method -For more information on available methods checkout https://open-api.netlify.com/ or run '${chalk.grey( +For more information on available methods checkout https://open-api.netlify.com/ or run '${ansis.gray( 'netlify api --list', )}'`, ) diff --git a/src/commands/base-command.ts b/src/commands/base-command.ts index 9637e1df2cd..14885dae7f3 100644 --- a/src/commands/base-command.ts +++ b/src/commands/base-command.ts @@ -19,7 +19,7 @@ import { getAgent } from '../lib/http-agent.js' import { NETLIFY_CYAN, USER_AGENT, - chalk, + ansis, logAndThrowError, exit, getToken, @@ -28,7 +28,7 @@ import { normalizeConfig, padLeft, pollForToken, - sortOptions, + compareOptions, warn, logError, } from '../utils/command-helpers.js' @@ -107,7 +107,7 @@ async function selectWorkspace(project: Project, filter?: string): Promise pkg.path.includes(input)) .map((pkg) => ({ - name: `${pkg.name ? `${chalk.bold(pkg.name)} ` : ''}${pkg.path} ${chalk.dim( + name: `${pkg.name ? `${ansis.bold(pkg.name)} ` : ''}${pkg.path} ${ansis.dim( `--filter ${pkg.name || pkg.path}`, )}`, value: pkg.path, @@ -286,7 +286,7 @@ export default class BaseCommand extends Command { if (description) { const pad = termWidth + HELP_SEPARATOR_WIDTH - const fullText = `${bang}${term.padEnd(pad - (isCommand ? 2 : 0))}${chalk.grey(description)}` + const fullText = `${bang}${term.padEnd(pad - (isCommand ? 2 : 0))}${ansis.gray(description)}` return helper.wrap(fullText, helpWidth - HELP_INDENT_WIDTH, pad) } @@ -303,34 +303,34 @@ export default class BaseCommand extends Command { // on the parent help command the version should be displayed if (this.name() === 'netlify') { - output = [...output, chalk.bold('VERSION'), formatHelpList([formatItem(USER_AGENT)]), ''] + output = [...output, ansis.bold('VERSION'), formatHelpList([formatItem(USER_AGENT)]), ''] } // Usage - output = [...output, chalk.bold('USAGE'), helper.commandUsage(command), ''] + output = [...output, ansis.bold('USAGE'), helper.commandUsage(command), ''] // Arguments const argumentList = helper .visibleArguments(command) .map((argument) => formatItem(helper.argumentTerm(argument), helper.argumentDescription(argument))) if (argumentList.length !== 0) { - output = [...output, chalk.bold('ARGUMENTS'), formatHelpList(argumentList), ''] + output = [...output, ansis.bold('ARGUMENTS'), formatHelpList(argumentList), ''] } if (command.#noBaseOptions === false) { // Options const optionList = helper .visibleOptions(command) - .sort(sortOptions) + .sort(compareOptions) .map((option) => formatItem(helper.optionTerm(option), helper.optionDescription(option))) if (optionList.length !== 0) { - output = [...output, chalk.bold('OPTIONS'), formatHelpList(optionList), ''] + output = [...output, ansis.bold('OPTIONS'), formatHelpList(optionList), ''] } } // Description if (commandDescription.length !== 0) { - output = [...output, chalk.bold('DESCRIPTION'), formatHelpList(commandDescription), ''] + output = [...output, ansis.bold('DESCRIPTION'), formatHelpList(commandDescription), ''] } // Aliases @@ -339,13 +339,13 @@ export default class BaseCommand extends Command { if (command._aliases.length !== 0) { // @ts-expect-error TS(2551) FIXME: Property '_aliases' does not exist on type 'Comman... Remove this comment to see the full error message const aliases = command._aliases.map((alias) => formatItem(`${parentCommand.name()} ${alias}`, null, true)) - output = [...output, chalk.bold('ALIASES'), formatHelpList(aliases), ''] + output = [...output, ansis.bold('ALIASES'), formatHelpList(aliases), ''] } if (command.examples.length !== 0) { output = [ ...output, - chalk.bold('EXAMPLES'), + ansis.bold('EXAMPLES'), formatHelpList(command.examples.map((example) => `${HELP_$} ${example}`)), '', ] @@ -355,7 +355,7 @@ export default class BaseCommand extends Command { formatItem(cmd.name(), helper.subcommandDescription(cmd).split('\n')[0], true), ) if (commandList.length !== 0) { - output = [...output, chalk.bold('COMMANDS'), formatHelpList(commandList), ''] + output = [...output, ansis.bold('COMMANDS'), formatHelpList(commandList), ''] } return [...output, ''].join('\n') @@ -450,11 +450,11 @@ export default class BaseCommand extends Command { // Log success log() - log(chalk.greenBright('You are now logged into your Netlify account!')) + log(ansis.greenBright('You are now logged into your Netlify account!')) log() - log(`Run ${chalk.cyanBright('netlify status')} for account details`) + log(`Run ${ansis.cyanBright('netlify status')} for account details`) log() - log(`To see all available commands run: ${chalk.cyanBright('netlify help')}`) + log(`To see all available commands run: ${ansis.cyanBright('netlify help')}`) log() return accessToken } diff --git a/src/commands/blobs/blobs-delete.ts b/src/commands/blobs/blobs-delete.ts index 8b877670b7a..c2bfbdc33aa 100644 --- a/src/commands/blobs/blobs-delete.ts +++ b/src/commands/blobs/blobs-delete.ts @@ -1,6 +1,6 @@ import { getStore } from '@netlify/blobs' -import { chalk, logAndThrowError, log } from '../../utils/command-helpers.js' +import { ansis, logAndThrowError, log } from '../../utils/command-helpers.js' import { promptBlobDelete } from '../../utils/prompts/blob-delete-prompts.js' /** @@ -24,8 +24,8 @@ export const blobsDelete = async (storeName: string, key: string, _options: Reco try { await store.delete(key) - log(`${chalk.greenBright('Success')}: Blob ${chalk.yellow(key)} deleted from store ${chalk.yellow(storeName)}`) + log(`${ansis.greenBright('Success')}: Blob ${ansis.yellow(key)} deleted from store ${ansis.yellow(storeName)}`) } catch { - return logAndThrowError(`Could not delete blob ${chalk.yellow(key)} from store ${chalk.yellow(storeName)}`) + return logAndThrowError(`Could not delete blob ${ansis.yellow(key)} from store ${ansis.yellow(storeName)}`) } } diff --git a/src/commands/blobs/blobs-get.ts b/src/commands/blobs/blobs-get.ts index 2123f759454..b2df84daf4d 100644 --- a/src/commands/blobs/blobs-get.ts +++ b/src/commands/blobs/blobs-get.ts @@ -4,7 +4,7 @@ import { resolve } from 'path' import { getStore } from '@netlify/blobs' import { OptionValues } from 'commander' -import { chalk, logAndThrowError } from '../../utils/command-helpers.js' +import { ansis, logAndThrowError } from '../../utils/command-helpers.js' import BaseCommand from '../base-command.js' interface Options extends OptionValues { @@ -21,16 +21,16 @@ export const blobsGet = async (storeName: string, key: string, options: Options, token: api.accessToken ?? '', }) - let blob + let blob: undefined | Awaited> try { blob = await store.get(key) } catch { - return logAndThrowError(`Could not retrieve blob ${chalk.yellow(key)} from store ${chalk.yellow(storeName)}`) + return logAndThrowError(`Could not retrieve blob ${ansis.yellow(key)} from store ${ansis.yellow(storeName)}`) } if (blob === null) { - return logAndThrowError(`Blob ${chalk.yellow(key)} does not exist in store ${chalk.yellow(storeName)}`) + return logAndThrowError(`Blob ${ansis.yellow(key)} does not exist in store ${ansis.yellow(storeName)}`) } if (output) { diff --git a/src/commands/blobs/blobs-list.ts b/src/commands/blobs/blobs-list.ts index 55844976261..e6b6a86d43e 100644 --- a/src/commands/blobs/blobs-list.ts +++ b/src/commands/blobs/blobs-list.ts @@ -2,7 +2,7 @@ import { getStore } from '@netlify/blobs' import AsciiTable from 'ascii-table' import { OptionValues } from 'commander' -import { chalk, logAndThrowError, log, logJson } from '../../utils/command-helpers.js' +import { ansis, logAndThrowError, log, logJson } from '../../utils/command-helpers.js' import BaseCommand from '../base-command.js' interface Options extends OptionValues { @@ -32,7 +32,7 @@ export const blobsList = async (storeName: string, options: Options, command: Ba } if (blobs.length === 0 && directories.length === 0) { - log(`Netlify Blobs store ${chalk.yellow(storeName)} is empty`) + log(`Netlify Blobs store ${ansis.yellow(storeName)} is empty`) return } @@ -50,6 +50,6 @@ export const blobsList = async (storeName: string, options: Options, command: Ba log(table.toString()) } catch { - return logAndThrowError(`Could not list blobs from store ${chalk.yellow(storeName)}`) + return logAndThrowError(`Could not list blobs from store ${ansis.yellow(storeName)}`) } } diff --git a/src/commands/blobs/blobs-set.ts b/src/commands/blobs/blobs-set.ts index 89121afe206..0d4559bd403 100644 --- a/src/commands/blobs/blobs-set.ts +++ b/src/commands/blobs/blobs-set.ts @@ -4,7 +4,7 @@ import { resolve } from 'path' import { getStore } from '@netlify/blobs' import { OptionValues } from 'commander' -import { chalk, logAndThrowError, isNodeError, log } from '../../utils/command-helpers.js' +import { ansis, logAndThrowError, isNodeError, log } from '../../utils/command-helpers.js' import { promptBlobSetOverwrite } from '../../utils/prompts/blob-set-prompt.js' import BaseCommand from '../base-command.js' @@ -37,18 +37,18 @@ export const blobsSet = async ( } catch (error) { if (isNodeError(error) && error.code === 'ENOENT') { return logAndThrowError( - `Could not set blob ${chalk.yellow(key)} because the file ${chalk.underline(inputPath)} does not exist`, + `Could not set blob ${ansis.yellow(key)} because the file ${ansis.underline(inputPath)} does not exist`, ) } if (isNodeError(error) && error.code === 'EISDIR') { return logAndThrowError( - `Could not set blob ${chalk.yellow(key)} because the path ${chalk.underline(inputPath)} is a directory`, + `Could not set blob ${ansis.yellow(key)} because the path ${ansis.underline(inputPath)} is a directory`, ) } return logAndThrowError( - `Could not set blob ${chalk.yellow(key)} because the path ${chalk.underline(inputPath)} could not be read`, + `Could not set blob ${ansis.yellow(key)} because the path ${ansis.underline(inputPath)} could not be read`, ) } } else if (!value) { @@ -67,8 +67,8 @@ export const blobsSet = async ( try { await store.set(key, value) - log(`${chalk.greenBright('Success')}: Blob ${chalk.yellow(key)} set in store ${chalk.yellow(storeName)}`) + log(`${ansis.greenBright('Success')}: Blob ${ansis.yellow(key)} set in store ${ansis.yellow(storeName)}`) } catch { - return logAndThrowError(`Could not set blob ${chalk.yellow(key)} in store ${chalk.yellow(storeName)}`) + return logAndThrowError(`Could not set blob ${ansis.yellow(key)} in store ${ansis.yellow(storeName)}`) } } diff --git a/src/commands/completion/completion.ts b/src/commands/completion/completion.ts index e90b5550baf..20b700fd6cf 100644 --- a/src/commands/completion/completion.ts +++ b/src/commands/completion/completion.ts @@ -1,6 +1,7 @@ import fs from 'fs' import { homedir } from 'os' import { dirname, join } from 'path' +import process from 'process' import { fileURLToPath } from 'url' import inquirer from 'inquirer' @@ -11,7 +12,7 @@ import { generateAutocompletion } from '../../lib/completion/index.js' import { logAndThrowError, log, - chalk, + ansis, checkFileForLine, TABTAB_CONFIG_LINE, AUTOLOAD_COMPINIT, @@ -55,7 +56,7 @@ export const completionGenerate = async (_options: OptionValues, command: BaseCo !checkFileForLine(zshConfigFilepath, AUTOLOAD_COMPINIT) ) { log(`To enable Tabtab autocompletion with zsh, the following line may need to be added to your ~/.zshrc:`) - log(chalk.bold.cyan(`\n${AUTOLOAD_COMPINIT}\n`)) + log(ansis.bold(ansis.cyan(`\n${AUTOLOAD_COMPINIT}\n`))) const { compinitAdded } = await inquirer.prompt([ { type: 'confirm', @@ -79,8 +80,8 @@ export const completionGenerate = async (_options: OptionValues, command: BaseCo if (process.platform !== 'win32') { log("\nTo ensure proper functionality, you'll need to set appropriate file permissions.") - log(chalk.bold('Add executable permissions by running the following command:')) - log(chalk.bold.cyan(`\nchmod +x ${completer}\n`)) + log(ansis.bold('Add executable permissions by running the following command:')) + log(ansis.bold(ansis.cyan(`\nchmod +x ${completer}\n`))) } else { log(`\nTo ensure proper functionality, you may need to set appropriate file permissions to ${completer}.`) } diff --git a/src/commands/deploy/deploy.ts b/src/commands/deploy/deploy.ts index 5db63621422..2efa1b24459 100644 --- a/src/commands/deploy/deploy.ts +++ b/src/commands/deploy/deploy.ts @@ -23,7 +23,7 @@ import { NETLIFYDEV, NETLIFYDEVERR, NETLIFYDEVLOG, - chalk, + ansis, logAndThrowError, exit, getToken, @@ -678,7 +678,7 @@ const printResults = ({ log() log('If everything looks good on your draft URL, deploy it to your main site URL with the --prod flag.') log( - chalk.cyanBright.bold( + ansis.cyanBright.bold( `netlify ${isIntegrationDeploy ? 'integration:' : ''}deploy${runBuildCommand ? ' --build' : ''} --prod`, ), ) diff --git a/src/commands/dev/dev.ts b/src/commands/dev/dev.ts index d9096470c4c..a4b23a09d7e 100644 --- a/src/commands/dev/dev.ts +++ b/src/commands/dev/dev.ts @@ -14,7 +14,7 @@ import { NETLIFYDEVLOG, NETLIFYDEVWARN, type NormalizedCachedConfigConfig, - chalk, + ansis, log, normalizeConfig, } from '../../utils/command-helpers.js' @@ -52,10 +52,10 @@ const handleLiveTunnel = async ({ const customSlug = typeof live === 'string' && live.length !== 0 ? live : undefined const slug = getLiveTunnelSlug(state, customSlug) - let message = `${NETLIFYDEVWARN} Creating live URL with ID ${chalk.yellow(slug)}` + let message = `${NETLIFYDEVWARN} Creating live URL with ID ${ansis.yellow(slug)}` if (!customSlug) { - message += ` (to generate a custom URL, use ${chalk.magenta('--live=')})` + message += ` (to generate a custom URL, use ${ansis.magenta('--live=')})` } log(message) @@ -106,12 +106,12 @@ export const dev = async (options: OptionValues, command: BaseCommand) => { if (!options.offline && !options.offlineEnv) { env = await getEnvelopeEnv({ api, context: options.context, env, siteInfo }) - log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`) + log(`${NETLIFYDEVLOG} Injecting environment variable values for ${ansis.yellow('all scopes')}`) } env = await getDotEnvVariables({ devConfig, env, site }) injectEnvVariables(env) - await promptEditorHelper({ chalk, config, log, NETLIFYDEVLOG, repositoryRoot, state }) + await promptEditorHelper({ ansis, config, log, NETLIFYDEVLOG, repositoryRoot, state }) const { accountId, addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({ // inherited from base command --offline diff --git a/src/commands/dev/index.ts b/src/commands/dev/index.ts index f9011267508..6bbd929c6d4 100644 --- a/src/commands/dev/index.ts +++ b/src/commands/dev/index.ts @@ -1,6 +1,6 @@ import { Option, type OptionValues } from 'commander' -import { BANG, chalk } from '../../utils/command-helpers.js' +import { BANG, ansis } from '../../utils/command-helpers.js' import { normalizeContext } from '../../utils/env/index.js' import { getGeoCountryArgParser } from '../../utils/validation.js' import type BaseCommand from '../base-command.js' @@ -9,7 +9,7 @@ const validateShortFlagArgs = (args: string) => { if (args.startsWith('=')) { throw new Error( `Short flag options like -e or -E don't support the '=' sign - ${chalk.red(BANG)} Supported formats: + ${ansis.red(BANG)} Supported formats: netlify dev -e netlify dev -e 127.0.0.1:9229 netlify dev -e127.0.0.1:9229 diff --git a/src/commands/env/env-clone.ts b/src/commands/env/env-clone.ts index be25cddf729..545c4842e9c 100644 --- a/src/commands/env/env-clone.ts +++ b/src/commands/env/env-clone.ts @@ -1,6 +1,6 @@ import { OptionValues } from 'commander' -import { chalk, log, logAndThrowError } from '../../utils/command-helpers.js' +import { ansis, log, logAndThrowError } from '../../utils/command-helpers.js' import { promptEnvCloneOverwrite } from '../../utils/prompts/env-clone-prompt.js' import BaseCommand from '../base-command.js' @@ -29,7 +29,7 @@ const cloneEnvVars = async ({ api, force, siteFrom, siteTo }): Promise const keysFrom = envelopeFrom.map(({ key }) => key) if (keysFrom.length === 0) { - log(`${chalk.green(siteFrom.name)} has no environment variables, nothing to clone`) + log(`${ansis.green(siteFrom.name)} has no environment variables, nothing to clone`) return false } @@ -85,11 +85,11 @@ export const envClone = async (options: OptionValues, command: BaseCommand) => { ]) if (errorFrom) { - return logAndThrowError(`Can't find site with id ${chalk.bold(siteId.from)}. Please make sure the site exists.`) + return logAndThrowError(`Can't find site with id ${ansis.bold(siteId.from)}. Please make sure the site exists.`) } if (errorTo) { - return logAndThrowError(`Can't find site with id ${chalk.bold(siteId.to)}. Please make sure the site exists.`) + return logAndThrowError(`Can't find site with id ${ansis.bold(siteId.to)}. Please make sure the site exists.`) } const success = await cloneEnvVars({ api, siteFrom, siteTo, force }) @@ -98,7 +98,7 @@ export const envClone = async (options: OptionValues, command: BaseCommand) => { return false } - log(`Successfully cloned environment variables from ${chalk.green(siteFrom.name)} to ${chalk.green(siteTo.name)}`) + log(`Successfully cloned environment variables from ${ansis.green(siteFrom.name)} to ${ansis.green(siteTo.name)}`) return true } diff --git a/src/commands/env/env-get.ts b/src/commands/env/env-get.ts index 901fc51cd8a..d8556b46cce 100644 --- a/src/commands/env/env-get.ts +++ b/src/commands/env/env-get.ts @@ -1,6 +1,6 @@ import { OptionValues } from 'commander' -import { chalk, log, logJson } from '../../utils/command-helpers.js' +import { ansis, log, logJson } from '../../utils/command-helpers.js' import { AVAILABLE_CONTEXTS, getEnvelopeEnv } from '../../utils/env/index.js' import BaseCommand from '../base-command.js' @@ -27,9 +27,9 @@ export const envGet = async (name: string, options: OptionValues, command: BaseC if (!value) { const contextType = AVAILABLE_CONTEXTS.includes(context) ? 'context' : 'branch' - const withContext = `in the ${chalk.magenta(context)} ${contextType}` - const withScope = scope === 'any' ? '' : ` and the ${chalk.magenta(scope)} scope` - log(`No value set ${withContext}${withScope} for environment variable ${chalk.yellow(name)}`) + const withContext = `in the ${ansis.magenta(context)} ${contextType}` + const withScope = scope === 'any' ? '' : ` and the ${ansis.magenta(scope)} scope` + log(`No value set ${withContext}${withScope} for environment variable ${ansis.yellow(name)}`) return false } diff --git a/src/commands/env/env-list.ts b/src/commands/env/env-list.ts index 6b6fd71cc5c..c0a3ff31fda 100644 --- a/src/commands/env/env-list.ts +++ b/src/commands/env/env-list.ts @@ -5,10 +5,10 @@ import type { OptionValues } from 'commander' import inquirer from 'inquirer' import logUpdate from 'log-update' -import { chalk, log, logJson } from '../../utils/command-helpers.js' +import { ansis, log, logJson } from '../../utils/command-helpers.js' import { AVAILABLE_CONTEXTS, getEnvelopeEnv, getHumanReadableScopes } from '../../utils/env/index.js' -import type BaseCommand from '../base-command.js' import { EnvironmentVariables } from '../../utils/types.js' +import type BaseCommand from '../base-command.js' const MASK_LENGTH = 50 const MASK = '*'.repeat(MASK_LENGTH) @@ -80,10 +80,10 @@ export const envList = async (options: OptionValues, command: BaseCommand) => { return false } - const forSite = `for site ${chalk.green(siteInfo.name)}` + const forSite = `for site ${ansis.green(siteInfo.name)}` const contextType = AVAILABLE_CONTEXTS.includes(context) ? 'context' : 'branch' - const withContext = `in the ${chalk.magenta(options.context)} ${contextType}` - const withScope = scope === 'any' ? '' : `and ${chalk.yellow(options.scope)} scope` + const withContext = `in the ${ansis.magenta(options.context)} ${contextType}` + const withScope = scope === 'any' ? '' : `and ${ansis.yellow(options.scope)} scope` if (Object.keys(environment).length === 0) { log(`No environment variables set ${forSite} ${withContext} ${withScope}`) return false @@ -112,6 +112,6 @@ export const envList = async (options: OptionValues, command: BaseCommand) => { // since inquirer adds a prompt, we need to account for it when printing the table again log(ansiEscapes.eraseLines(3)) logUpdate(getTable({ environment, hideValues: false, scopesColumn: true })) - log(`${chalk.cyan('?')} Show values? ${chalk.cyan('Yes')}`) + log(`${ansis.cyan('?')} Show values? ${ansis.cyan('Yes')}`) } } diff --git a/src/commands/env/env-set.ts b/src/commands/env/env-set.ts index d216d6fb208..836024a4a27 100644 --- a/src/commands/env/env-set.ts +++ b/src/commands/env/env-set.ts @@ -1,6 +1,6 @@ import { OptionValues } from 'commander' -import { chalk, logAndThrowError, log, logJson } from '../../utils/command-helpers.js' +import { ansis, logAndThrowError, log, logJson } from '../../utils/command-helpers.js' import { AVAILABLE_CONTEXTS, AVAILABLE_SCOPES, translateFromEnvelopeToMongo } from '../../utils/env/index.js' import { promptOverwriteEnvVariable } from '../../utils/prompts/env-set-prompts.js' import BaseCommand from '../base-command.js' @@ -130,12 +130,12 @@ export const envSet = async (key: string, value: string, options: OptionValues, return false } - const withScope = scope ? ` scoped to ${chalk.white(scope)}` : '' - const withSecret = secret ? ` as a ${chalk.blue('secret')}` : '' + const withScope = scope ? ` scoped to ${ansis.white(scope)}` : '' + const withSecret = secret ? ` as a ${ansis.blue('secret')}` : '' const contextType = AVAILABLE_CONTEXTS.includes(context || 'all') ? 'context' : 'branch' log( - `Set environment variable ${chalk.yellow( + `Set environment variable ${ansis.yellow( `${key}${value && !secret ? `=${value}` : ''}`, - )}${withScope}${withSecret} in the ${chalk.magenta(context || 'all')} ${contextType}`, + )}${withScope}${withSecret} in the ${ansis.magenta(context || 'all')} ${contextType}`, ) } diff --git a/src/commands/env/env-unset.ts b/src/commands/env/env-unset.ts index 14668251e2e..432ca9d220c 100644 --- a/src/commands/env/env-unset.ts +++ b/src/commands/env/env-unset.ts @@ -1,6 +1,6 @@ import { OptionValues } from 'commander' -import { chalk, log, logJson, exit } from '../../utils/command-helpers.js' +import { ansis, log, logJson, exit } from '../../utils/command-helpers.js' import { AVAILABLE_CONTEXTS, translateFromEnvelopeToMongo } from '../../utils/env/index.js' import { promptOverwriteEnvVariable } from '../../utils/prompts/env-unset-prompts.js' import BaseCommand from '../base-command.js' @@ -88,5 +88,5 @@ export const envUnset = async (key: string, options: OptionValues, command: Base } const contextType = AVAILABLE_CONTEXTS.includes(context || 'all') ? 'context' : 'branch' - log(`Unset environment variable ${chalk.yellow(key)} in the ${chalk.magenta(context || 'all')} ${contextType}`) + log(`Unset environment variable ${ansis.yellow(key)} in the ${ansis.magenta(context || 'all')} ${contextType}`) } diff --git a/src/commands/functions/functions-create.ts b/src/commands/functions/functions-create.ts index a66165185ee..7f0ecd4465b 100644 --- a/src/commands/functions/functions-create.ts +++ b/src/commands/functions/functions-create.ts @@ -20,7 +20,7 @@ import { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, - chalk, + ansis, logAndThrowError, log, } from '../../utils/command-helpers.js' @@ -267,14 +267,14 @@ const ensureEdgeFuncDirExists = function (command) { if (!fs.existsSync(functionsDir)) { log( - `${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse( - relFunctionsDir, + `${NETLIFYDEVLOG} Edge Functions directory ${ansis.inverse( + ansis.magenta(relFunctionsDir), )} does not exist yet, creating it...`, ) fs.mkdirSync(functionsDir, { recursive: true }) - log(`${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse(relFunctionsDir)} created.`) + log(`${NETLIFYDEVLOG} Edge Functions directory ${ansis.inverse(ansis.magenta(relFunctionsDir))} created.`) } return functionsDir @@ -304,7 +304,7 @@ const promptFunctionsDirectory = async (command) => { ]) try { - log(`${NETLIFYDEVLOG} updating site settings with ${chalk.magenta.inverse(functionsDir)}`) + log(`${NETLIFYDEVLOG} updating site settings with ${ansis.inverse(ansis.magenta(functionsDir))}`) await api.updateSite({ siteId: site.id, @@ -315,7 +315,7 @@ const promptFunctionsDirectory = async (command) => { }, }) - log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(functionsDir)} updated in site settings`) + log(`${NETLIFYDEVLOG} functions directory ${ansis.inverse(ansis.magenta(functionsDir))} updated in site settings`) } catch { return logAndThrowError('Error updating site settings') } @@ -336,14 +336,14 @@ const ensureFunctionDirExists = async function (command) { if (!fs.existsSync(functionsDirHolder)) { log( - `${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse( - relFunctionsDirHolder, + `${NETLIFYDEVLOG} functions directory ${ansis.inverse( + ansis.magenta(relFunctionsDirHolder), )} does not exist yet, creating it...`, ) await mkdir(functionsDirHolder, { recursive: true }) - log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(relFunctionsDirHolder)} created`) + log(`${NETLIFYDEVLOG} functions directory ${ansis.inverse(ansis.magenta(relFunctionsDirHolder))} created`) } return functionsDirHolder @@ -512,7 +512,7 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun const name = await getNameFromArgs(argumentName, options, templateName) - log(`${NETLIFYDEVLOG} Creating function ${chalk.cyan.inverse(name)}`) + log(`${NETLIFYDEVLOG} Creating function ${ansis.inverse(ansis.cyan(name))}`) const functionPath = ensureFunctionPathIsOk(functionsDir, name) const vars = { name } @@ -526,7 +526,7 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun const filename = path.basename(filePath) if (!omittedFromOutput.has(filename)) { - log(`${NETLIFYDEVLOG} ${chalk.greenBright('Created')} ${filePath}`) + log(`${NETLIFYDEVLOG} ${ansis.greenBright('Created')} ${filePath}`) } fs.chmodSync(path.resolve(filePath), TEMPLATE_PERMISSIONS) @@ -553,11 +553,11 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun await handleOnComplete({ command, onComplete }) log() - log(chalk.greenBright(`Function created!`)) + log(ansis.greenBright(`Function created!`)) if (lang == 'rust') { log( - chalk.green( + ansis.green( `Please note that Rust functions require setting the NETLIFY_EXPERIMENTAL_BUILD_RUST_SOURCE environment variable to 'true' on your site.`, ), ) @@ -669,7 +669,7 @@ const installAddons = async function (command, functionAddons, fnPath) { // @ts-expect-error TS(7031) FIXME: Binding element 'addonDidInstall' implicitly has a... Remove this comment to see the full error message const arr = functionAddons.map(async ({ addonDidInstall, addonName }) => { - log(`${NETLIFYDEVLOG} installing addon: ${chalk.yellow.inverse(addonName)}`) + log(`${NETLIFYDEVLOG} installing addon: ${ansis.inverse(ansis.yellow(addonName))}`) try { const addonCreated = await createFunctionAddon({ api, diff --git a/src/commands/functions/functions-invoke.ts b/src/commands/functions/functions-invoke.ts index 04a4788a9b4..8511c5d5272 100644 --- a/src/commands/functions/functions-invoke.ts +++ b/src/commands/functions/functions-invoke.ts @@ -6,7 +6,7 @@ import { OptionValues } from 'commander' import inquirer from 'inquirer' import fetch from 'node-fetch' -import { APIError, NETLIFYDEVWARN, chalk, logAndThrowError, exit } from '../../utils/command-helpers.js' +import { APIError, NETLIFYDEVWARN, ansis, logAndThrowError, exit } from '../../utils/command-helpers.js' import { BACKGROUND, CLOCKWORK_USERAGENT, getFunctions } from '../../utils/functions/index.js' import BaseCommand from '../base-command.js' @@ -107,7 +107,7 @@ const getNameFromArgs = async function (functions, options, argumentName) { } console.warn( - `Function name ${chalk.yellow( + `Function name ${ansis.yellow( functionToTrigger, )} supplied but no matching function found in your functions folder, forcing you to pick a valid one...`, ) diff --git a/src/commands/functions/functions.ts b/src/commands/functions/functions.ts index 95ac5094c96..a55292f6586 100644 --- a/src/commands/functions/functions.ts +++ b/src/commands/functions/functions.ts @@ -1,6 +1,6 @@ import type { OptionValues } from 'commander' -import { chalk } from '../../utils/command-helpers.js' +import { ansis } from '../../utils/command-helpers.js' import requiresSiteInfo from '../../utils/hooks/requires-site-info.js' import type BaseCommand from '../base-command.js' @@ -106,7 +106,7 @@ NOT the same as listing the functions that have been deployed. For that info you await functionsServe(options, command) }) - const name = chalk.greenBright('`functions`') + const name = ansis.greenBright('`functions`') return program .command('functions') diff --git a/src/commands/init/init.ts b/src/commands/init/init.ts index c2481b16264..ade83d3e38b 100644 --- a/src/commands/init/init.ts +++ b/src/commands/init/init.ts @@ -2,7 +2,7 @@ import { OptionValues } from 'commander' import inquirer from 'inquirer' import isEmpty from 'lodash/isEmpty.js' -import { chalk, exit, log } from '../../utils/command-helpers.js' +import { ansis, exit, log } from '../../utils/command-helpers.js' import getRepoData from '../../utils/get-repo-data.js' import { ensureNetlifyIgnore } from '../../utils/gitignore.js' import { configureRepo } from '../../utils/init/config.js' @@ -23,15 +23,15 @@ const logExistingAndExit = ({ siteInfo }: { siteInfo: SiteInfo }): never => { log() log(`This site has been initialized`) log() - log(`Site Name: ${chalk.cyan(siteInfo.name)}`) - log(`Site Url: ${chalk.cyan(siteInfo.ssl_url || siteInfo.url)}`) - log(`Site Repo: ${chalk.cyan(getRepoUrl(siteInfo))}`) - log(`Site Id: ${chalk.cyan(siteInfo.id)}`) - log(`Admin URL: ${chalk.cyan(siteInfo.admin_url)}`) + log(`Site Name: ${ansis.cyan(siteInfo.name)}`) + log(`Site Url: ${ansis.cyan(siteInfo.ssl_url || siteInfo.url)}`) + log(`Site Repo: ${ansis.cyan(getRepoUrl(siteInfo))}`) + log(`Site Id: ${ansis.cyan(siteInfo.id)}`) + log(`Admin URL: ${ansis.cyan(siteInfo.admin_url)}`) log() log(`To disconnect this directory and create a new site (or link to another siteId)`) - log(`1. Run ${chalk.cyanBright.bold('netlify unlink')}`) - log(`2. Then run ${chalk.cyanBright.bold('netlify init')} again`) + log(`1. Run ${ansis.cyanBright.bold('netlify unlink')}`) + log(`2. Then run ${ansis.cyanBright.bold('netlify init')} again`) return exit() } @@ -43,7 +43,7 @@ const createNewSiteAndExit = async ({ command, state }: { command: BaseCommand; log(`"${siteInfo.name}" site was created`) log() - log(`To deploy to this site. Run your site build and then ${chalk.cyanBright.bold('netlify deploy')}`) + log(`To deploy to this site. Run your site build and then ${ansis.cyanBright(ansis.bold('netlify deploy'))}`) persistState({ state, siteInfo }) @@ -52,33 +52,33 @@ const createNewSiteAndExit = async ({ command, state }: { command: BaseCommand; const logGitSetupInstructionsAndExit = (): never => { log() - log(`${chalk.bold('To initialize a new git repo follow the steps below.')} + log(`${ansis.bold('To initialize a new git repo follow the steps below.')} 1. Initialize a new repo: - ${chalk.cyanBright.bold('git init')} + ${ansis.cyanBright(ansis.bold('git init'))} 2. Add your files - ${chalk.cyanBright.bold('git add .')} + ${ansis.cyanBright(ansis.bold('git add .'))} 3. Commit your files - ${chalk.cyanBright.bold("git commit -m 'initial commit'")} + ${ansis.cyanBright(ansis.bold("git commit -m 'initial commit'"))} -4. Create a new repo in GitHub ${chalk.cyanBright.bold('https://github.com/new')} +4. Create a new repo in GitHub ${ansis.cyanBright(ansis.bold('https://github.com/new'))} 5. Link the remote repo with this local directory - ${chalk.cyanBright.bold('git remote add origin git@github.com:YourGithubName/your-repo-slug.git')} + ${ansis.cyanBright(ansis.bold('git remote add origin git@github.com:YourGithubName/your-repo-slug.git'))} 6. Push up your files - ${chalk.cyanBright.bold('git push -u origin main')} + ${ansis.cyanBright(ansis.bold('git push -u origin main'))} 7. Initialize your Netlify Site - ${chalk.cyanBright.bold('netlify init')} + ${ansis.cyanBright(ansis.bold('netlify init'))} `) return exit() } @@ -96,7 +96,7 @@ const handleNoGitRemoteAndExit = async ({ state: CLIState }): Promise => { log() - log(chalk.yellow('No git remote was found, would you like to set one up?')) + log(ansis.yellow('No git remote was found, would you like to set one up?')) log(` It is recommended that you initialize a site that has a remote repository in GitHub. @@ -162,7 +162,7 @@ const createOrLinkSiteToRepo = async (command: BaseCommand) => { const logExistingRepoSetupAndExit = ({ repoUrl, siteName }: { repoUrl: string; siteName: string }): void => { log() - log(chalk.underline.bold(`Success`)) + log(ansis.underline(ansis.bold(`Success`))) log(`This site "${siteName}" is configured to automatically deploy via ${repoUrl}`) // TODO add support for changing GitHub repo in site:config command exit() diff --git a/src/commands/link/link.ts b/src/commands/link/link.ts index b300008f9ae..c7f8f50fb08 100644 --- a/src/commands/link/link.ts +++ b/src/commands/link/link.ts @@ -5,14 +5,14 @@ import inquirer from 'inquirer' import isEmpty from 'lodash/isEmpty.js' import { listSites } from '../../lib/api.js' -import { chalk, logAndThrowError, exit, log, APIError } from '../../utils/command-helpers.js' +import { ansis, logAndThrowError, exit, log, APIError } from '../../utils/command-helpers.js' import getRepoData from '../../utils/get-repo-data.js' import { ensureNetlifyIgnore } from '../../utils/gitignore.js' import { track } from '../../utils/telemetry/index.js' import type { SiteInfo } from '../../utils/types.js' import BaseCommand from '../base-command.js' -const linkPrompt = async (command: BaseCommand, options: OptionValues) => { +const linkPrompt = async (command: BaseCommand, options: OptionValues): Promise => { const { api, state } = command.netlify const SITE_NAME_PROMPT = 'Search by full or partial site name' @@ -20,7 +20,7 @@ const linkPrompt = async (command: BaseCommand, options: OptionValues) => { const SITE_ID_PROMPT = 'Enter a site ID' let GIT_REMOTE_PROMPT = 'Use the current git remote origin URL' - let site + let site!: SiteInfo // Get git remote data if exists const repoData = await getRepoData({ workingDir: command.workingDir, remoteName: options.gitRemoteName }) @@ -33,18 +33,18 @@ const linkPrompt = async (command: BaseCommand, options: OptionValues) => { } log() - log(`${chalk.cyanBright('netlify link')} will connect this folder to a site on Netlify`) + log(`${ansis.cyanBright('netlify link')} will connect this folder to a site on Netlify`) log() - const { linkType } = await inquirer.prompt([ + const { linkType } = (await inquirer.prompt([ { type: 'list', name: 'linkType', message: 'How do you want to link this folder to a site?', choices: linkChoices, }, - ]) + ])) as { linkType: typeof linkChoices[number] } - let kind + let kind: 'byName' | 'bySiteId' | 'fromList' | 'gitRemote' switch (linkType) { case GIT_REMOTE_PROMPT: { // TODO(serhalp): Refactor function to avoid this. We can only be here if `repoData` is not an error. @@ -58,7 +58,7 @@ const linkPrompt = async (command: BaseCommand, options: OptionValues) => { if (sites.length === 0) { return logAndThrowError( - `You don't have any sites yet. Run ${chalk.cyanBright('netlify sites:create')} to create a site.`, + `You don't have any sites yet. Run ${ansis.cyanBright('netlify sites:create')} to create a site.`, ) } @@ -68,13 +68,13 @@ const linkPrompt = async (command: BaseCommand, options: OptionValues) => { // If no remote matches. Throw error if (matchingSites.length === 0) { - log(chalk.redBright.bold.underline(`No Matching Site Found`)) + log(ansis.redBright.bold.underline('No Matching Site Found')) log() log(`No site found with the remote ${repoData.httpsUrl}. Double check you are in the correct working directory and a remote origin repo is configured. -Run ${chalk.cyanBright('git remote -v')} to see a list of your git remotes.`) +Run ${ansis.cyanBright('git remote -v')} to see a list of your git remotes.`) exit() } @@ -88,7 +88,7 @@ Run ${chalk.cyanBright('git remote -v')} to see a list of your git remotes.`) log(`Found ${matchingSites.length} matching sites!`) // Prompt which options - const { selectedSite } = await inquirer.prompt([ + const { selectedSite } = (await inquirer.prompt([ { type: 'list', name: 'selectedSite', @@ -98,7 +98,7 @@ Run ${chalk.cyanBright('git remote -v')} to see a list of your git remotes.`) value: matchingSite, })), }, - ]) + ])) as { selectedSite: SiteInfo | undefined } if (!selectedSite) { return logAndThrowError('No site selected') } @@ -135,8 +135,8 @@ Run ${chalk.cyanBright('git remote -v')} to see a list of your git remotes.`) if (!matchingSites || matchingSites.length === 0) { return logAndThrowError(`No site names found containing '${searchTerm}'. -Run ${chalk.cyanBright('netlify link')} again to try a new search, -or run ${chalk.cyanBright('netlify sites:create')} to create a site.`) +Run ${ansis.cyanBright('netlify link')} again to try a new search, +or run ${ansis.cyanBright('netlify sites:create')} to create a site.`) } if (matchingSites.length > 1) { @@ -165,7 +165,7 @@ or run ${chalk.cyanBright('netlify sites:create')} to create a site.`) log(`Fetching recently updated sites...`) log() - let sites + let sites: SiteInfo[] try { sites = await listSites({ api, options: { maxPages: 1, filter: 'all' } }) } catch (error_) { @@ -174,7 +174,7 @@ or run ${chalk.cyanBright('netlify sites:create')} to create a site.`) if (!sites || sites.length === 0) { return logAndThrowError( - `You don't have any sites yet. Run ${chalk.cyanBright('netlify sites:create')} to create a site.`, + `You don't have any sites yet. Run ${ansis.cyanBright('netlify sites:create')} to create a site.`, ) } @@ -204,6 +204,7 @@ or run ${chalk.cyanBright('netlify sites:create')} to create a site.`) ]) try { + // @ts-expect-error(serhalp) -- Mismatch between hardcoded `SiteInfo` and generated Netlify API types. site = await api.getSite({ siteId }) } catch (error_) { if ((error_ as APIError).status === 404) { @@ -215,7 +216,9 @@ or run ${chalk.cyanBright('netlify sites:create')} to create a site.`) break } default: - return + // This is not possible, but since the fixed set of choices contains one dynamically interpolated string, + // we can't tell TS that these are exhaustive values + return logAndThrowError(new Error('Invalid link type selected')) } if (!site) { @@ -233,10 +236,10 @@ or run ${chalk.cyanBright('netlify sites:create')} to create a site.`) // Log output log() - log(chalk.greenBright.bold.underline(`Directory Linked`)) + log(ansis.greenBright.bold.underline('Directory Linked')) log() - log(`Admin url: ${chalk.magentaBright(site.admin_url)}`) - log(`Site url: ${chalk.cyanBright(site.ssl_url || site.url)}`) + log(`Admin url: ${ansis.magentaBright(site.admin_url)}`) + log(`Site url: ${ansis.cyanBright(site.ssl_url || site.url)}`) log() log(`You can now run other \`netlify\` cli commands in this directory`) @@ -273,7 +276,7 @@ export const link = async (options: OptionValues, command: BaseCommand) => { log(`Site already linked to "${initialSiteData.name}"`) log(`Admin url: ${initialSiteData.admin_url}`) log() - log(`To unlink this site, run: ${chalk.cyanBright('netlify unlink')}`) + log(`To unlink this site, run: ${ansis.cyanBright('netlify unlink')}`) } else if (options.id) { try { // @ts-expect-error(serhalp) -- Mismatch between hardcoded `SiteInfo` and new generated Netlify API types. diff --git a/src/commands/login/login.ts b/src/commands/login/login.ts index c4ca33f562b..11e82d76f00 100644 --- a/src/commands/login/login.ts +++ b/src/commands/login/login.ts @@ -1,6 +1,6 @@ import { OptionValues } from 'commander' -import { chalk, exit, getToken, log } from '../../utils/command-helpers.js' +import { ansis, exit, getToken, log } from '../../utils/command-helpers.js' import { TokenLocation } from '../../utils/types.js' import BaseCommand from '../base-command.js' @@ -25,11 +25,11 @@ export const login = async (options: OptionValues, command: BaseCommand) => { if (accessToken && !options.new) { log(`Already logged in ${msg(location)}`) log() - log(`Run ${chalk.cyanBright('netlify status')} for account details`) + log(`Run ${ansis.cyanBright('netlify status')} for account details`) log() - log(`or run ${chalk.cyanBright('netlify switch')} to switch accounts`) + log(`or run ${ansis.cyanBright('netlify switch')} to switch accounts`) log() - log(`To see all available commands run: ${chalk.cyanBright('netlify help')}`) + log(`To see all available commands run: ${ansis.cyanBright('netlify help')}`) log() return exit() } diff --git a/src/commands/logs/build.ts b/src/commands/logs/build.ts index 36563aa55ce..43327ef3f16 100644 --- a/src/commands/logs/build.ts +++ b/src/commands/logs/build.ts @@ -1,7 +1,7 @@ import type { OptionValues } from 'commander' import inquirer from 'inquirer' -import { log, chalk } from '../../utils/command-helpers.js' +import { log, ansis } from '../../utils/command-helpers.js' import { getWebSocket } from '../../utils/websockets/index.js' import type BaseCommand from '../base-command.js' @@ -26,7 +26,7 @@ export function getName({ deploy, userId }: { deploy: any; userId: string }) { } if (isUserDeploy) { - normalisedName += chalk.yellow('*') + normalisedName += ansis.yellow('*') } return `(${deploy.id.slice(0, 7)}) ${normalisedName}` @@ -56,7 +56,7 @@ export const logsBuild = async (options: OptionValues, command: BaseCommand) => const { result } = await inquirer.prompt({ name: 'result', type: 'list', - message: `Select a deploy\n\n${chalk.yellow('*')} indicates a deploy created by you`, + message: `Select a deploy\n\n${ansis.yellow('*')} indicates a deploy created by you`, choices: deploys.map((dep: any) => ({ name: getName({ deploy: dep, userId }), value: dep.id, diff --git a/src/commands/logs/functions.ts b/src/commands/logs/functions.ts index 611bf2ad937..c85ca93b030 100644 --- a/src/commands/logs/functions.ts +++ b/src/commands/logs/functions.ts @@ -1,7 +1,7 @@ import { OptionValues } from 'commander' import inquirer from 'inquirer' -import { chalk, log } from '../../utils/command-helpers.js' +import { ansis, log } from '../../utils/command-helpers.js' import { getWebSocket } from '../../utils/websockets/index.js' import type BaseCommand from '../base-command.js' @@ -11,13 +11,13 @@ function getLog(logData: { level: string; message: string }) { let logString = '' switch (logData.level) { case LOG_LEVELS.INFO: - logString += chalk.blueBright(logData.level) + logString += ansis.blueBright(logData.level) break case LOG_LEVELS.WARN: - logString += chalk.yellowBright(logData.level) + logString += ansis.yellowBright(logData.level) break case LOG_LEVELS.ERROR: - logString += chalk.redBright(logData.level) + logString += ansis.redBright(logData.level) break default: logString += logData.level diff --git a/src/commands/main.ts b/src/commands/main.ts index 40f5eb45d06..db3d6936de8 100644 --- a/src/commands/main.ts +++ b/src/commands/main.ts @@ -7,7 +7,7 @@ import inquirer from 'inquirer' import { BANG, - chalk, + ansis, logAndThrowError, exit, log, @@ -63,7 +63,7 @@ export const CI_FORCED_COMMANDS = { process.on('uncaughtException', async (err: AddressInUseError | Error) => { if ('code' in err && err.code === 'EADDRINUSE') { logError( - `${chalk.red(`Port ${err.port} is already in use`)}\n\n` + + `${ansis.red(`Port ${err.port} is already in use`)}\n\n` + `Your serverless functions might be initializing a server\n` + `to listen on specific port without properly closing it.\n\n` + `This behavior is generally not advised\n` + @@ -75,21 +75,21 @@ process.on('uncaughtException', async (err: AddressInUseError | Error) => { ) } else { logError( - `${chalk.red( + `${ansis.red( 'Netlify CLI has terminated unexpectedly', - )}\nThis is a problem with the Netlify CLI, not with your application.\nIf you recently updated the CLI, consider reverting to an older version by running:\n\n${chalk.bold( + )}\nThis is a problem with the Netlify CLI, not with your application.\nIf you recently updated the CLI, consider reverting to an older version by running:\n\n${ansis.bold( 'npm install -g netlify-cli@VERSION', - )}\n\nYou can use any version from ${chalk.underline( + )}\n\nYou can use any version from ${ansis.underline( 'https://ntl.fyi/cli-versions', - )}.\n\nPlease report this problem at ${chalk.underline( + )}.\n\nPlease report this problem at ${ansis.underline( 'https://ntl.fyi/cli-error', )} including the error details below.\n`, ) const systemInfo = await getSystemInfo() - console.log(chalk.dim(err.stack || err)) - console.log(chalk.dim(systemInfo)) + console.log(ansis.dim(err.stack || err)) + console.log(ansis.dim(systemInfo)) reportError(err, { severity: 'error' }) } @@ -152,9 +152,9 @@ const mainCommand = async function (options, command) { if (command.args.length === 0) { const pkg = await getCLIPackageJson() - const title = chalk.bgBlack.cyan('⬥ Netlify CLI') - const docsMsg = `${chalk.greenBright('Read the docs:')} https://ntl.fyi/get-started-with-netlify-cli` - const supportMsg = `${chalk.magentaBright('Support and bugs:')} ${pkg.bugs?.url}` + const title = ansis.bgBlack.cyan('⬥ Netlify CLI') + const docsMsg = `${ansis.greenBright('Read the docs:')} https://ntl.fyi/get-started-with-netlify-cli` + const supportMsg = `${ansis.magentaBright('Support and bugs:')} ${pkg.bugs?.url}` console.log() console.log(title) @@ -177,7 +177,7 @@ const mainCommand = async function (options, command) { command.help() } - warn(`${chalk.yellow(command.args[0])} is not a ${command.name()} command.`) + warn(`${ansis.yellow(command.args[0])} is not a ${command.name()} command.`) // @ts-expect-error TS(7006) FIXME: Parameter 'cmd' implicitly has an 'any' type. const allCommands = command.commands.map((cmd) => cmd.name()) @@ -187,7 +187,7 @@ const mainCommand = async function (options, command) { const prompt = inquirer.prompt({ type: 'confirm', name: 'suggestion', - message: `Did you mean ${chalk.blue(suggestion)}`, + message: `Did you mean ${ansis.blue(suggestion)}`, default: false, }) @@ -255,8 +255,8 @@ export const createMainCommand = (): BaseCommand => { .noHelpOptions() .configureOutput({ outputError: (message, write) => { - write(` ${chalk.red(BANG)} Error: ${message.replace(/^error:\s/g, '')}`) - write(` ${chalk.red(BANG)} See more help with --help\n`) + write(` ${ansis.red(BANG)} Error: ${message.replace(/^error:\s/g, '')}`) + write(` ${ansis.red(BANG)} See more help with --help\n`) }, }) .action(mainCommand) diff --git a/src/commands/open/open-site.ts b/src/commands/open/open-site.ts index 46969c38831..a0dd9e9fa0b 100644 --- a/src/commands/open/open-site.ts +++ b/src/commands/open/open-site.ts @@ -4,7 +4,7 @@ import { exit, log } from '../../utils/command-helpers.js' import openBrowser from '../../utils/open-browser.js' import BaseCommand from '../base-command.js' -export const openSite = async (options: OptionValues, command: BaseCommand) => { +export const openSite = async (_options: OptionValues, command: BaseCommand) => { const { siteInfo } = command.netlify await command.authenticate() diff --git a/src/commands/recipes/recipes.ts b/src/commands/recipes/recipes.ts index 5c98aef8a33..5b6c5f466c7 100644 --- a/src/commands/recipes/recipes.ts +++ b/src/commands/recipes/recipes.ts @@ -4,7 +4,7 @@ import { OptionValues } from 'commander' import { closest } from 'fastest-levenshtein' import inquirer from 'inquirer' -import { NETLIFYDEVERR, chalk, log } from '../../utils/command-helpers.js' +import { NETLIFYDEVERR, ansis, log } from '../../utils/command-helpers.js' import BaseCommand from '../base-command.js' import { getRecipe, listRecipes } from './common.js' @@ -46,7 +46,7 @@ export const recipesCommand = async (recipeName: string, options: OptionValues, throw error } - log(`${NETLIFYDEVERR} ${chalk.yellow(recipeName)} is not a valid recipe name.`) + log(`${NETLIFYDEVERR} ${ansis.yellow(recipeName)} is not a valid recipe name.`) const recipes = await listRecipes() const recipeNames = recipes.map(({ name }) => name) @@ -55,7 +55,7 @@ export const recipesCommand = async (recipeName: string, options: OptionValues, const prompt = inquirer.prompt({ type: 'confirm', name: 'suggestion', - message: `Did you mean ${chalk.blue(suggestion)}`, + message: `Did you mean ${ansis.blue(suggestion)}`, default: false, }) diff --git a/src/commands/serve/serve.ts b/src/commands/serve/serve.ts index 796188620de..61955b0aef8 100644 --- a/src/commands/serve/serve.ts +++ b/src/commands/serve/serve.ts @@ -15,7 +15,7 @@ import { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, - chalk, + ansis, exit, log, normalizeConfig, @@ -52,12 +52,12 @@ export const serve = async (options: OptionValues, command: BaseCommand) => { if (!options.offline) { env = await getEnvelopeEnv({ api, context: options.context, env, siteInfo }) - log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`) + log(`${NETLIFYDEVLOG} Injecting environment variable values for ${ansis.yellow('all scopes')}`) } env = await getDotEnvVariables({ devConfig, env, site }) injectEnvVariables(env) - await promptEditorHelper({ chalk, config, log, NETLIFYDEVLOG, repositoryRoot, state }) + await promptEditorHelper({ ansis, config, log, NETLIFYDEVLOG, repositoryRoot, state }) const { accountId, addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({ // inherited from base command --offline diff --git a/src/commands/sites/sites-create-template.ts b/src/commands/sites/sites-create-template.ts index e77d639983a..715f19d4946 100644 --- a/src/commands/sites/sites-create-template.ts +++ b/src/commands/sites/sites-create-template.ts @@ -8,7 +8,7 @@ import { render } from 'prettyjson' import { v4 as uuid } from 'uuid' import { - chalk, + ansis, logAndThrowError, getTerminalLink, log, @@ -41,7 +41,7 @@ export const sitesCreateTemplate = async (repository: string, options: OptionVal if (!exists) { const githubLink = getGitHubLink({ options, templateName }) return logAndThrowError( - `Could not find template ${chalk.bold(templateName)}. Please verify it exists and you can ${getTerminalLink( + `Could not find template ${ansis.bold(templateName)}. Please verify it exists and you can ${getTerminalLink( 'access to it on GitHub', githubLink, )}`, @@ -49,7 +49,7 @@ export const sitesCreateTemplate = async (repository: string, options: OptionVal } if (!isTemplate) { const githubLink = getGitHubLink({ options, templateName }) - return logAndThrowError(`${getTerminalLink(chalk.bold(templateName), githubLink)} is not a valid GitHub template`) + return logAndThrowError(`${getTerminalLink(ansis.bold(templateName), githubLink)} is not a valid GitHub template`) } let { accountSlug } = options @@ -161,7 +161,7 @@ export const sitesCreateTemplate = async (repository: string, options: OptionVal ;[site, repoResp] = await inputSiteName(nameFlag) log() - log(chalk.greenBright.bold.underline(`Site Created`)) + log(ansis.greenBright.bold.underline(`Site Created`)) log() const siteUrl = site.ssl_url || site.url @@ -193,7 +193,7 @@ export const sitesCreateTemplate = async (repository: string, options: OptionVal await execa('git', ['clone', repoResp.clone_url, `${repoResp.name}`]) } - log(`🚀 Repository cloned successfully. You can find it under the ${chalk.magenta(repoResp.name)} folder`) + log(`🚀 Repository cloned successfully. You can find it under the ${ansis.magenta(repoResp.name)} folder`) const { linkConfirm } = await inquirer.prompt({ type: 'confirm', @@ -220,32 +220,34 @@ export const sitesCreateTemplate = async (repository: string, options: OptionVal const lineMatch = linkedSiteUrlRegex.exec(stdout) const urlMatch = lineMatch ? lineMatch[1] : undefined if (urlMatch) { - log(`\nDirectory ${chalk.cyanBright(repoResp.name)} linked to site ${chalk.cyanBright(urlMatch)}\n`) + log(`\nDirectory ${ansis.cyanBright(repoResp.name)} linked to site ${ansis.cyanBright(urlMatch)}\n`) log( - `${chalk.cyanBright.bold('cd', repoResp.name)} to use other netlify cli commands in the cloned directory.\n`, + `${ansis.cyanBright( + ansis.bold(`cd ${repoResp.name}`), + )} to use other netlify cli commands in the cloned directory.\n`, ) } else { const linkedSiteMatch = /Site already linked to\s+(\S+)/.exec(stdout) const linkedSiteNameMatch = linkedSiteMatch ? linkedSiteMatch[1] : undefined if (linkedSiteNameMatch) { - log(`\nThis directory appears to be linked to ${chalk.cyanBright(linkedSiteNameMatch)}`) + log(`\nThis directory appears to be linked to ${ansis.cyanBright(linkedSiteNameMatch)}`) log('This can happen if you cloned the template into a subdirectory of an existing Netlify project.') log( - `You may need to move the ${chalk.cyanBright( + `You may need to move the ${ansis.cyanBright( repoResp.name, - )} directory out of its parent directory and then re-run the ${chalk.cyanBright( + )} directory out of its parent directory and then re-run the ${ansis.cyanBright( 'link', )} command manually\n`, ) } else { log('A problem occurred linking the site') log('You can try again manually by running:') - log(chalk.cyanBright(`cd ${repoResp.name} && netlify link\n`)) + log(ansis.cyanBright(`cd ${repoResp.name} && netlify link\n`)) } } } else { log('To link the cloned directory manually, run:') - log(chalk.cyanBright(`cd ${repoResp.name} && netlify link\n`)) + log(ansis.cyanBright(`cd ${repoResp.name} && netlify link\n`)) } } diff --git a/src/commands/sites/sites-create.ts b/src/commands/sites/sites-create.ts index 644f068a208..bac06a94040 100644 --- a/src/commands/sites/sites-create.ts +++ b/src/commands/sites/sites-create.ts @@ -3,7 +3,7 @@ import inquirer from 'inquirer' import pick from 'lodash/pick.js' import prettyjson from 'prettyjson' -import { chalk, logAndThrowError, log, logJson, warn, type APIError } from '../../utils/command-helpers.js' +import { ansis, logAndThrowError, log, logJson, warn, type APIError } from '../../utils/command-helpers.js' import getRepoData from '../../utils/get-repo-data.js' import { configureRepo } from '../../utils/init/config.js' import { track } from '../../utils/telemetry/index.js' @@ -81,7 +81,7 @@ export const sitesCreate = async (options: OptionValues, command: BaseCommand) = await inputSiteName(options.name) log() - log(chalk.greenBright.bold.underline(`Site Created`)) + log(ansis.greenBright.bold.underline(`Site Created`)) log() const siteUrl = site.ssl_url || site.url diff --git a/src/commands/sites/sites-delete.ts b/src/commands/sites/sites-delete.ts index 9456fd5061c..02aae4a5c99 100644 --- a/src/commands/sites/sites-delete.ts +++ b/src/commands/sites/sites-delete.ts @@ -1,7 +1,7 @@ import type { OptionValues } from 'commander' import inquirer from 'inquirer' -import { chalk, logAndThrowError, exit, log, type APIError } from '../../utils/command-helpers.js' +import { ansis, logAndThrowError, exit, log, type APIError } from '../../utils/command-helpers.js' import type BaseCommand from '../base-command.js' export const sitesDelete = async (siteId: string, options: OptionValues, command: BaseCommand) => { @@ -28,11 +28,11 @@ export const sitesDelete = async (siteId: string, options: OptionValues, command /* Verify the user wants to delete the site */ if (noForce) { - log(`${chalk.redBright('Warning')}: You are about to permanently delete "${chalk.bold(siteData.name)}"`) + log(`${ansis.redBright('Warning')}: You are about to permanently delete "${ansis.bold(siteData.name)}"`) log(` Verify this siteID "${siteId}" supplied is correct and proceed.`) log(' To skip this prompt, pass a --force flag to the delete command') log() - log(chalk.bold('Be careful here. There is no undo!')) + log(ansis.bold('Be careful here. There is no undo!')) log() const { wantsToDelete } = await inquirer.prompt({ type: 'confirm', @@ -48,7 +48,7 @@ export const sitesDelete = async (siteId: string, options: OptionValues, command /* Validation logic if siteId passed in does not match current site ID */ if (noForce && cwdSiteId && cwdSiteId !== siteId) { - log(`${chalk.redBright('Warning')}: The siteId supplied does not match the current working directory siteId`) + log(`${ansis.redBright('Warning')}: The siteId supplied does not match the current working directory siteId`) log() log(`Supplied: "${siteId}"`) log(`Current Site: "${cwdSiteId}"`) diff --git a/src/commands/sites/sites-list.ts b/src/commands/sites/sites-list.ts index 0540a214f14..f1b16a754fe 100644 --- a/src/commands/sites/sites-list.ts +++ b/src/commands/sites/sites-list.ts @@ -2,7 +2,7 @@ import { OptionValues } from 'commander' import { listSites } from '../../lib/api.js' import { startSpinner, stopSpinner } from '../../lib/spinner.js' -import { chalk, log, logJson } from '../../utils/command-helpers.js' +import { ansis, log, logJson } from '../../utils/command-helpers.js' import { SiteInfo } from '../../utils/types.js' import BaseCommand from '../base-command.js' @@ -56,13 +56,13 @@ Count: ${logSites.length} `) logSites.forEach((logSite) => { - log(`${chalk.greenBright(logSite.name)} - ${logSite.id}`) - log(` ${chalk.whiteBright.bold('url:')} ${chalk.yellowBright(logSite.ssl_url)}`) + log(`${ansis.greenBright(logSite.name)} - ${logSite.id}`) + log(` ${ansis.whiteBright(ansis.bold('url:'))} ${ansis.yellowBright(logSite.ssl_url)}`) if (logSite.repo_url) { - log(` ${chalk.whiteBright.bold('repo:')} ${chalk.white(logSite.repo_url)}`) + log(` ${ansis.whiteBright(ansis.bold('repo:'))} ${ansis.white(logSite.repo_url)}`) } if (logSite.account_name) { - log(` ${chalk.whiteBright.bold('account:')} ${chalk.white(logSite.account_name)}`) + log(` ${ansis.whiteBright(ansis.bold('account:'))} ${ansis.white(logSite.account_name)}`) } log(`─────────────────────────────────────────────────`) }) diff --git a/src/commands/status/status.ts b/src/commands/status/status.ts index fbfb032c861..06b4e69d5e5 100644 --- a/src/commands/status/status.ts +++ b/src/commands/status/status.ts @@ -2,7 +2,7 @@ import clean from 'clean-deep' import { OptionValues } from 'commander' import prettyjson from 'prettyjson' -import { chalk, logAndThrowError, exit, getToken, log, logJson, warn, APIError } from '../../utils/command-helpers.js' +import { ansis, logAndThrowError, exit, getToken, log, logJson, warn, APIError } from '../../utils/command-helpers.js' import BaseCommand from '../base-command.js' export const status = async (options: OptionValues, command: BaseCommand) => { @@ -81,9 +81,9 @@ export const status = async (options: OptionValues, command: BaseCommand) => { prettyjson.render({ 'Current site': siteInfo.name, 'Netlify TOML': site.configPath, - 'Admin URL': chalk.magentaBright(siteInfo.admin_url), - 'Site URL': chalk.cyanBright(siteInfo.ssl_url || siteInfo.url), - 'Site Id': chalk.yellowBright(siteInfo.id), + 'Admin URL': ansis.magentaBright(siteInfo.admin_url), + 'Site URL': ansis.cyanBright(siteInfo.ssl_url || siteInfo.url), + 'Site Id': ansis.yellowBright(siteInfo.id), }), ) log() diff --git a/src/commands/switch/switch.ts b/src/commands/switch/switch.ts index b1052278ab5..4aaa03ff523 100644 --- a/src/commands/switch/switch.ts +++ b/src/commands/switch/switch.ts @@ -1,13 +1,13 @@ import { OptionValues } from 'commander' import inquirer from 'inquirer' -import { chalk, log } from '../../utils/command-helpers.js' +import { ansis, log } from '../../utils/command-helpers.js' import BaseCommand from '../base-command.js' import { login } from '../login/login.js' const LOGIN_NEW = 'I would like to login to a new account' -export const switchCommand = async (options: OptionValues, command: BaseCommand) => { +export const switchCommand = async (_options: OptionValues, command: BaseCommand) => { const availableUsersChoices = Object.values(command.netlify.globalConfig.get('users') || {}).reduce( (prev, current) => // @ts-expect-error TS(2769) FIXME: No overload matches this call. @@ -36,6 +36,6 @@ export const switchCommand = async (options: OptionValues, command: BaseCommand) command.netlify.globalConfig.set('userId', selectedAccount[0]) log('') // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - log(`You're now using ${chalk.bold(selectedAccount[1])}.`) + log(`You're now using ${ansis.bold(selectedAccount[1])}.`) } } diff --git a/src/commands/watch/watch.ts b/src/commands/watch/watch.ts index 290dbe441ba..b273aa31893 100644 --- a/src/commands/watch/watch.ts +++ b/src/commands/watch/watch.ts @@ -3,7 +3,7 @@ import prettyjson from 'prettyjson' import type { NetlifyAPI } from 'netlify' import { type Spinner, startSpinner, stopSpinner } from '../../lib/spinner.js' -import { chalk, logAndThrowError, log } from '../../utils/command-helpers.js' +import { ansis, logAndThrowError, log } from '../../utils/command-helpers.js' import type BaseCommand from '../base-command.js' import { init } from '../init/init.js' @@ -94,7 +94,7 @@ export const watch = async (_options: unknown, command: BaseCommand) => { const siteData = await client.getSite({ siteId }) - const message = chalk.cyanBright.bold.underline(noActiveBuilds ? 'Last build' : 'Deploy complete') + const message = ansis.cyanBright.bold.underline(noActiveBuilds ? 'Last build' : 'Deploy complete') log() log(message) log( diff --git a/src/lib/completion/generate-autocompletion.ts b/src/lib/completion/generate-autocompletion.ts index daaac81f645..652694d7524 100644 --- a/src/lib/completion/generate-autocompletion.ts +++ b/src/lib/completion/generate-autocompletion.ts @@ -1,30 +1,26 @@ import fs from 'fs' import { dirname } from 'path' -import { sortOptions, warn } from '../../utils/command-helpers.js' +import type { Command } from 'commander' + +import { compareOptions, warn } from '../../utils/command-helpers.js' import { AUTOCOMPLETION_FILE } from './constants.js' /** * Create or updates the autocompletion information for the CLI - * @param {import('../../commands/base-command.js').default} program - * @returns {void} */ -// @ts-expect-error TS(7006) FIXME: Parameter 'program' implicitly has an 'any' type. -const generateAutocompletion = (program) => { +const generateAutocompletion = (program: Command) => { try { const autocomplete = program.commands.reduce( - // @ts-expect-error TS(7006) FIXME: Parameter 'prev' implicitly has an 'any' type. (prev, cmd) => ({ ...prev, [cmd.name()]: { name: cmd.name(), description: cmd.description().split('\n')[0], options: cmd.options - // @ts-expect-error TS(7006) FIXME: Parameter 'option' implicitly has an 'any' type. .filter((option) => !option.hidden) - .sort(sortOptions) - // @ts-expect-error TS(7006) FIXME: Parameter 'opt' implicitly has an 'any' type. + .sort(compareOptions) .map((opt) => ({ name: `--${opt.name()}`, description: opt.description })), }, }), diff --git a/src/lib/edge-functions/editor-helper.ts b/src/lib/edge-functions/editor-helper.ts index e656000931a..cd09a5b3714 100644 --- a/src/lib/edge-functions/editor-helper.ts +++ b/src/lib/edge-functions/editor-helper.ts @@ -7,7 +7,7 @@ import { runRecipe } from '../../commands/recipes/recipes.js' const STATE_PROMPT_PROPERTY = 'promptVSCodeSettings' // @ts-expect-error TS(7031) FIXME: Binding element 'NETLIFYDEVLOG' implicitly has an ... Remove this comment to see the full error message -export const promptEditorHelper = async ({ NETLIFYDEVLOG, chalk, config, log, repositoryRoot, state }) => { +export const promptEditorHelper = async ({ NETLIFYDEVLOG, config, log, ansis, repositoryRoot, state }) => { // This prevents tests from hanging when running them inside the VS Code // terminal, as otherwise we'll show the prompt and wait for a response. if (env.NODE_ENV === 'test') return @@ -32,8 +32,8 @@ export const promptEditorHelper = async ({ NETLIFYDEVLOG, chalk, config, log, re if (!confirm) { log( - `${NETLIFYDEVLOG} You can start this configuration manually by running ${chalk.magenta.bold( - 'netlify recipes vscode', + `${NETLIFYDEVLOG} You can start this configuration manually by running ${ansis.magenta( + ansis.bold('netlify recipes vscode'), )}.`, ) diff --git a/src/lib/edge-functions/proxy.ts b/src/lib/edge-functions/proxy.ts index 057ec28f6bc..1241b97cd5e 100644 --- a/src/lib/edge-functions/proxy.ts +++ b/src/lib/edge-functions/proxy.ts @@ -11,7 +11,7 @@ import type { $TSFixMe } from '../../commands/types.js' import { NETLIFYDEVERR, type NormalizedCachedConfigConfig, - chalk, + ansis, logAndThrowError, } from '../../utils/command-helpers.js' import { FeatureFlags, getFeatureFlagsFromSiteInfo } from '../../utils/feature-flags.js' @@ -231,11 +231,11 @@ const prepareServer = async ({ distImportMapPath: join(projectDir, distImportMapPath), featureFlags, formatExportTypeError: (name) => - `${NETLIFYDEVERR} ${chalk.red('Failed')} to load Edge Function ${chalk.yellow( + `${NETLIFYDEVERR} ${ansis.red('Failed')} to load Edge Function ${ansis.yellow( name, )}. The file does not seem to have a function as the default export.`, formatImportError: (name) => - `${NETLIFYDEVERR} ${chalk.red('Failed')} to run Edge Function ${chalk.yellow(name)}:`, + `${NETLIFYDEVERR} ${ansis.red('Failed')} to run Edge Function ${ansis.yellow(name)}:`, inspectSettings, port, rootPath: repositoryRoot, diff --git a/src/lib/edge-functions/registry.ts b/src/lib/edge-functions/registry.ts index 6e05354c513..b2a2cf3178f 100644 --- a/src/lib/edge-functions/registry.ts +++ b/src/lib/edge-functions/registry.ts @@ -10,7 +10,7 @@ import { NETLIFYDEVLOG, NETLIFYDEVWARN, nonNullable, - chalk, + ansis, log, warn, watchDebounced, @@ -365,19 +365,19 @@ export class EdgeFunctionsRegistry { event: EdgeFunctionEvent, { buildError, functionName, warnings = [] }: { buildError?: Error; functionName?: string; warnings?: string[] }, ) { - const subject = functionName ? `edge function ${chalk.yellow(this.getDisplayName(functionName))}` : 'edge functions' + const subject = functionName ? `edge function ${ansis.yellow(this.getDisplayName(functionName))}` : 'edge functions' const warningsText = warnings.length === 0 ? '' : ` with warnings:\n${warnings.map((warning) => ` - ${warning}`).join('\n')}` if (event === 'buildError') { - log(`${NETLIFYDEVERR} ${chalk.red('Failed to load')} ${subject}: ${buildError}`) + log(`${NETLIFYDEVERR} ${ansis.red('Failed to load')} ${subject}: ${buildError}`) return } if (event === 'loaded') { const icon = warningsText ? NETLIFYDEVWARN : NETLIFYDEVLOG - const color = warningsText ? chalk.yellow : chalk.green + const color = warningsText ? ansis.yellow : ansis.green log(`${icon} ${color('Loaded')} ${subject}${warningsText}`) @@ -386,7 +386,7 @@ export class EdgeFunctionsRegistry { if (event === 'reloaded') { const icon = warningsText ? NETLIFYDEVWARN : NETLIFYDEVLOG - const color = warningsText ? chalk.yellow : chalk.green + const color = warningsText ? ansis.yellow : ansis.green log(`${icon} ${color('Reloaded')} ${subject}${warningsText}`) @@ -394,13 +394,13 @@ export class EdgeFunctionsRegistry { } if (event === 'reloading') { - log(`${NETLIFYDEVLOG} ${chalk.magenta('Reloading')} ${subject}...`) + log(`${NETLIFYDEVLOG} ${ansis.magenta('Reloading')} ${subject}...`) return } if (event === 'removed') { - log(`${NETLIFYDEVLOG} ${chalk.magenta('Removed')} ${subject}`) + log(`${NETLIFYDEVLOG} ${ansis.magenta('Removed')} ${subject}`) } } diff --git a/src/lib/functions/registry.ts b/src/lib/functions/registry.ts index 67c8aa24ca9..42de319eb00 100644 --- a/src/lib/functions/registry.ts +++ b/src/lib/functions/registry.ts @@ -7,7 +7,7 @@ import { type ListedFunction, listFunctions, type Manifest } from '@netlify/zip- import extractZip from 'extract-zip' import { - chalk, + ansis, log, getTerminalLink, NETLIFYDEVERR, @@ -210,7 +210,7 @@ export class FunctionsRegistry { const { filename } = func const newFilename = filename ? `${basename(filename, extname(filename))}${recommendedExtension}` : null const action = newFilename - ? `rename the function file to ${chalk.underline( + ? `rename the function file to ${ansis.underline( newFilename, )}. Refer to https://ntl.fyi/functions-runtime for more information` : `refer to https://ntl.fyi/functions-runtime` @@ -293,10 +293,10 @@ export class FunctionsRegistry { const { routes = [] } = (await func.getBuildData()) ?? {} if (routes.length !== 0) { - const paths = routes.map((route) => chalk.underline(route.pattern)).join(', ') + const paths = routes.map((route) => ansis.underline(route.pattern)).join(', ') warn( - `Function ${chalk.yellow(func.name)} cannot be invoked on ${chalk.underline( + `Function ${ansis.yellow(func.name)} cannot be invoked on ${ansis.underline( url.pathname, )}, because the function has the following URL paths defined: ${paths}`, ) @@ -331,7 +331,7 @@ export class FunctionsRegistry { if (event === 'buildError') { log( - `${NETLIFYDEVERR} ${chalk.red('Failed to load')} function ${chalk.yellow(func?.displayName)}: ${ + `${NETLIFYDEVERR} ${ansis.red('Failed to load')} function ${ansis.yellow(func?.displayName)}: ${ func?.buildError?.message ?? '' }`, ) @@ -339,7 +339,7 @@ export class FunctionsRegistry { if (event === 'extracted') { log( - `${NETLIFYDEVLOG} ${chalk.green('Extracted')} function ${chalk.yellow(func?.displayName)} from ${ + `${NETLIFYDEVLOG} ${ansis.green('Extracted')} function ${ansis.yellow(func?.displayName)} from ${ func?.mainFile ?? '' }.`, ) @@ -349,20 +349,20 @@ export class FunctionsRegistry { if (event === 'loaded') { const icon = warningsText ? NETLIFYDEVWARN : NETLIFYDEVLOG - const color = warningsText ? chalk.yellow : chalk.green + const color = warningsText ? ansis.yellow : ansis.green const mode = func?.runtimeAPIVersion === 1 && this.logLambdaCompat ? ` in ${getTerminalLink('Lambda compatibility mode', 'https://ntl.fyi/lambda-compat')}` : '' - log(`${icon} ${color('Loaded')} function ${chalk.yellow(func?.displayName)}${mode}${warningsText}`) + log(`${icon} ${color('Loaded')} function ${ansis.yellow(func?.displayName)}${mode}${warningsText}`) return } if (event === 'missing-types-package') { log( - `${NETLIFYDEVWARN} For a better experience with TypeScript functions, consider installing the ${chalk.underline( + `${NETLIFYDEVWARN} For a better experience with TypeScript functions, consider installing the ${ansis.underline( TYPES_PACKAGE, )} package. Refer to https://ntl.fyi/function-types for more information.`, ) @@ -370,21 +370,21 @@ export class FunctionsRegistry { if (event === 'reloaded') { const icon = warningsText ? NETLIFYDEVWARN : NETLIFYDEVLOG - const color = warningsText ? chalk.yellow : chalk.green + const color = warningsText ? ansis.yellow : ansis.green - log(`${icon} ${color('Reloaded')} function ${chalk.yellow(func?.displayName)}${warningsText}`) + log(`${icon} ${color('Reloaded')} function ${ansis.yellow(func?.displayName)}${warningsText}`) return } if (event === 'reloading') { - log(`${NETLIFYDEVLOG} ${chalk.magenta('Reloading')} function ${chalk.yellow(func?.displayName)}...`) + log(`${NETLIFYDEVLOG} ${ansis.magenta('Reloading')} function ${ansis.yellow(func?.displayName)}...`) return } if (event === 'removed') { - log(`${NETLIFYDEVLOG} ${chalk.magenta('Removed')} function ${chalk.yellow(func?.displayName)}`) + log(`${NETLIFYDEVLOG} ${ansis.magenta('Removed')} function ${ansis.yellow(func?.displayName)}`) } } diff --git a/src/lib/functions/synchronous.ts b/src/lib/functions/synchronous.ts index c94cea27dd2..502917b166a 100644 --- a/src/lib/functions/synchronous.ts +++ b/src/lib/functions/synchronous.ts @@ -5,7 +5,7 @@ import express from 'express' import { isReadableStream as baseIsReadableStream } from 'is-stream' import type { LambdaEvent } from 'lambda-local' -import { chalk, logPadded, NETLIFYDEVERR } from '../../utils/command-helpers.js' +import { ansis, logPadded, NETLIFYDEVERR } from '../../utils/command-helpers.js' import renderErrorTemplate from '../render-error-template.js' import { detectAwsSdkError } from './utils.js' @@ -42,9 +42,9 @@ export const handleSynchronousFunction = async function ({ const error = getNormalizedError(invocationError) logPadded( - `${NETLIFYDEVERR} Function ${chalk.yellow(functionName)} has returned an error: ${ + `${NETLIFYDEVERR} Function ${ansis.yellow(functionName)} has returned an error: ${ error.errorMessage - }\n${chalk.dim(error.stackTrace.join('\n'))}`, + }\n${ansis.dim(error.stackTrace.join('\n'))}`, ) await handleErr(invocationError, request, response) @@ -79,7 +79,7 @@ export const handleSynchronousFunction = async function ({ const normalizedError = getNormalizedError(wrappedHeadersError) logPadded( - `${NETLIFYDEVERR} Failed to set header in function ${chalk.yellow(functionName)}: ${ + `${NETLIFYDEVERR} Failed to set header in function ${ansis.yellow(functionName)}: ${ normalizedError.errorMessage }`, ) diff --git a/src/lib/functions/utils.ts b/src/lib/functions/utils.ts index fe81d6a906c..341882cd635 100644 --- a/src/lib/functions/utils.ts +++ b/src/lib/functions/utils.ts @@ -1,4 +1,4 @@ -import { chalk, warn } from '../../utils/command-helpers.js' +import { ansis, warn } from '../../utils/command-helpers.js' import { MISSING_AWS_SDK_WARNING } from '../log.js' import type { InvocationError } from './netlify-function.js' @@ -19,7 +19,7 @@ export const detectAwsSdkError = ({ error }: { error: Error | InvocationError | // XXX(serhalp): This appears to be a bug? In the background and scheduled function code paths this can receive plain // errors, but this is assuming normalized `InvocationError`s only. export const formatLambdaError = (err: Error | InvocationError): string => - chalk.red(`${'errorType' in err ? err.errorType : ''}: ${'errorMessage' in err ? err.errorMessage : ''}`) + ansis.red(`${'errorType' in err ? err.errorType : ''}: ${'errorMessage' in err ? err.errorMessage : ''}`) // should be equivalent to https://github.com/netlify/proxy/blob/main/pkg/functions/request.go#L105 const exceptionsList = new Set([ @@ -56,4 +56,4 @@ export const shouldBase64Encode = function (contentType?: string): boolean { return true } -export const styleFunctionName = (name: string): string => chalk.magenta(name) +export const styleFunctionName = (name: string): string => ansis.magenta(name) diff --git a/src/lib/log.ts b/src/lib/log.ts index 07bef3a31db..581cf3006a2 100644 --- a/src/lib/log.ts +++ b/src/lib/log.ts @@ -1,18 +1,18 @@ -import { chalk } from '../utils/command-helpers.js' +import { ansis } from '../utils/command-helpers.js' -const RED_BACKGROUND = chalk.red('-background') -const [PRO, BUSINESS, ENTERPRISE] = ['Pro', 'Business', 'Enterprise'].map((plan) => chalk.magenta(plan)) +const RED_BACKGROUND = ansis.red('-background') +const [PRO, BUSINESS, ENTERPRISE] = ['Pro', 'Business', 'Enterprise'].map((plan) => ansis.magenta(plan)) export const BACKGROUND_FUNCTIONS_WARNING = `A serverless function ending in \`${RED_BACKGROUND}\` was detected. Your team’s current plan doesn’t support Background Functions, which have names ending in \`${RED_BACKGROUND}\`. To be able to deploy this function successfully either: - change the function name to remove \`${RED_BACKGROUND}\` and execute it synchronously - upgrade your team plan to a level that supports Background Functions (${PRO}, ${BUSINESS}, or ${ENTERPRISE}) ` -export const MISSING_AWS_SDK_WARNING = `A function has thrown an error due to a missing dependency: ${chalk.yellow( +export const MISSING_AWS_SDK_WARNING = `A function has thrown an error due to a missing dependency: ${ansis.yellow( 'aws-sdk', )}. You should add this module to the project's dependencies, using your package manager of choice: -${chalk.yellow('npm install aws-sdk --save')} or ${chalk.yellow('yarn add aws-sdk')} +${ansis.yellow('npm install aws-sdk --save')} or ${ansis.yellow('yarn add aws-sdk')} For more information, see https://ntl.fyi/cli-aws-sdk.` diff --git a/src/recipes/ai-context/index.ts b/src/recipes/ai-context/index.ts index 5e1db71fa8f..2f70f8413d5 100644 --- a/src/recipes/ai-context/index.ts +++ b/src/recipes/ai-context/index.ts @@ -4,7 +4,7 @@ import inquirer from 'inquirer' import semver from 'semver' import type { RunRecipeOptions } from '../../commands/recipes/recipes.js' -import { chalk, logAndThrowError, log, version } from '../../utils/command-helpers.js' +import { ansis, logAndThrowError, log, version } from '../../utils/command-helpers.js' import { applyOverrides, @@ -68,7 +68,7 @@ export const run = async ({ args, command }: RunRecipeOptions) => { if (minimumCLIVersion && semver.lt(version, minimumCLIVersion)) { return logAndThrowError( - `This command requires version ${minimumCLIVersion} or above of the Netlify CLI. Refer to ${chalk.underline( + `This command requires version ${minimumCLIVersion} or above of the Netlify CLI. Refer to ${ansis.underline( 'https://ntl.fyi/update-cli', )} for information on how to update.`, ) @@ -87,7 +87,7 @@ export const run = async ({ args, command }: RunRecipeOptions) => { if (existing.provider?.toLowerCase() === NETLIFY_PROVIDER) { if (remote?.version === existing.version) { log( - `You're all up to date! ${chalk.underline( + `You're all up to date! ${ansis.underline( absoluteFilePath, )} contains the latest version of the context files.`, ) @@ -103,7 +103,7 @@ export const run = async ({ args, command }: RunRecipeOptions) => { const { confirm } = await inquirer.prompt({ type: 'confirm', name: 'confirm', - message: `A context file already exists at ${chalk.underline( + message: `A context file already exists at ${ansis.underline( absoluteFilePath, )}. It has not been created by the Netlify CLI, but we can update it while preserving its existing content. Can we proceed?`, default: true, @@ -120,5 +120,5 @@ export const run = async ({ args, command }: RunRecipeOptions) => { await writeFile(absoluteFilePath, contents) - log(`${existing ? 'Updated' : 'Created'} context files at ${chalk.underline(absoluteFilePath)}`) + log(`${existing ? 'Updated' : 'Created'} context files at ${ansis.underline(absoluteFilePath)}`) } diff --git a/src/recipes/vscode/index.ts b/src/recipes/vscode/index.ts index ef0e14ad6eb..344e7498ace 100644 --- a/src/recipes/vscode/index.ts +++ b/src/recipes/vscode/index.ts @@ -4,7 +4,7 @@ import { DenoBridge } from '@netlify/edge-bundler' import execa from 'execa' import inquirer from 'inquirer' -import { NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, logAndThrowError, log } from '../../utils/command-helpers.js' +import { NETLIFYDEVLOG, NETLIFYDEVWARN, ansis, logAndThrowError, log } from '../../utils/command-helpers.js' import { applySettings, getSettings, writeSettings } from './settings.js' @@ -12,7 +12,7 @@ export const description = 'Create VS Code settings for an optimal experience wi // @ts-expect-error TS(7031) FIXME: Binding element 'fileExists' implicitly has an 'an... Remove this comment to see the full error message const getPrompt = ({ fileExists, path }) => { - const formattedPath = chalk.underline(path) + const formattedPath = ansis.underline(path) const message = fileExists ? `There is a VS Code settings file at ${formattedPath}. Can we update it?` : `A new VS Code settings file will be created at ${formattedPath}` @@ -96,7 +96,7 @@ export const run = async ({ config, repositoryRoot }) => { } } catch { log( - `${NETLIFYDEVWARN} Unable to install Deno VS Code extension. To install it manually, visit ${chalk.blue( + `${NETLIFYDEVWARN} Unable to install Deno VS Code extension. To install it manually, visit ${ansis.blue( 'https://ntl.fyi/deno-vscode', )}.`, ) diff --git a/src/utils/banner.ts b/src/utils/banner.ts index bd8e735b084..0ae3325be44 100644 --- a/src/utils/banner.ts +++ b/src/utils/banner.ts @@ -1,9 +1,9 @@ import boxen from 'boxen' -import { chalk, log, NETLIFYDEVLOG } from './command-helpers.js' +import { ansis, log, NETLIFYDEVLOG } from './command-helpers.js' export const printBanner = (options: { url: string }): void => { - const banner = chalk.bold(`${NETLIFYDEVLOG} Server now ready on ${options.url}`) + const banner = ansis.bold(`${NETLIFYDEVLOG} Server now ready on ${options.url}`) log( boxen(banner, { diff --git a/src/utils/build-info.ts b/src/utils/build-info.ts index 6edcabaec99..7d08bb8092e 100644 --- a/src/utils/build-info.ts +++ b/src/utils/build-info.ts @@ -6,7 +6,7 @@ import inquirer from 'inquirer' import type BaseCommand from '../commands/base-command.js' import type { DefaultConfig } from '../lib/build.js' -import { chalk, log } from './command-helpers.js' +import { ansis, log } from './command-helpers.js' /** * Filters the inquirer settings based on the input @@ -31,7 +31,7 @@ const formatSettingsArrForInquirer = function (settings: Settings[], type = 'dev return settings.map((setting) => { const cmd = type === 'dev' ? setting.devCommand : setting.buildCommand return { - name: `[${chalk.yellow(setting.framework.name)}] '${cmd}'`, + name: `[${ansis.yellow(setting.framework.name)}] '${cmd}'`, value: { ...setting, commands: [cmd] }, short: `${setting.name}-${cmd}`, } diff --git a/src/utils/command-helpers.ts b/src/utils/command-helpers.ts index b272b6275ef..3d19f14c800 100644 --- a/src/utils/command-helpers.ts +++ b/src/utils/command-helpers.ts @@ -1,11 +1,11 @@ import { once } from 'events' -import os from 'os' import fs from 'fs' +import os from 'os' import process from 'process' import { format, inspect } from 'util' -import { Chalk } from 'chalk' import chokidar from 'chokidar' +import type { Option } from 'commander' import decache from 'decache' import WSL from 'is-wsl' import debounce from 'lodash/debounce.js' @@ -22,32 +22,21 @@ import type { CachedConfig } from '../lib/build.js' /** The parsed process argv without the binary only arguments and flags */ const argv = process.argv.slice(2) + /** - * Chalk instance for CLI that can be initialized with no colors mode + * ansis instance for CLI that can be initialized with no colors mode * needed for json outputs where we don't want to have colors - * @param {boolean} noColors - disable chalk colors - * @return {import('chalk').ChalkInstance} - default or custom chalk instance + * Strangely, mutating `process.env` seems to be the recommended approach. + * TODO(serhalp) Move this into `bin/run.js` maybe? */ -// @ts-expect-error TS(7006) FIXME: Parameter 'noColors' implicitly has an 'any' type. -const safeChalk = function (noColors) { - if (noColors) { - const colorlessChalk = new Chalk({ level: 0 }) - return colorlessChalk - } - return new Chalk() -} - -export const chalk = safeChalk(argv.includes('--json')) +if (argv.includes('--json')) process.env.NO_COLOR = '1' +const { default: ansisModule } = await import('ansis') +export const ansis = ansisModule /** * Adds the filler to the start of the string - * @param {string} str - * @param {number} count - * @param {string} [filler] - * @returns {string} */ -// @ts-expect-error TS(7006) FIXME: Parameter 'str' implicitly has an 'any' type. -export const padLeft = (str, count, filler = ' ') => str.padStart(str.length + count, filler) +export const padLeft = (str: string, count: number, filler = ' ') => str.padStart(str.length + count, filler) const platform = WSL ? 'wsl' : os.platform() const arch = os.arch() === 'ia32' ? 'x86' : os.arch() @@ -60,30 +49,29 @@ export const USER_AGENT = `${name}/${version} ${platform}-${arch} node-${process /** A list of base command flags that needs to be sorted down on documentation and on help pages */ const BASE_FLAGS = new Set(['--debug', '--http-proxy', '--http-proxy-certificate-filename']) -export const NETLIFY_CYAN = chalk.rgb(40, 180, 170) +export const NETLIFY_CYAN = ansis.rgb(40, 180, 170) -export const NETLIFYDEV = `${chalk.greenBright('◈')} ${NETLIFY_CYAN('Netlify Dev')} ${chalk.greenBright('◈')}` -export const NETLIFYDEVLOG = chalk.greenBright('◈') -export const NETLIFYDEVWARN = chalk.yellowBright('◈') -export const NETLIFYDEVERR = chalk.redBright('◈') +export const NETLIFYDEV = `${ansis.greenBright('◈')} ${NETLIFY_CYAN('Netlify Dev')} ${ansis.greenBright('◈')}` +export const NETLIFYDEVLOG = ansis.greenBright('◈') +export const NETLIFYDEVWARN = ansis.yellowBright('◈') +export const NETLIFYDEVERR = ansis.redBright('◈') export const BANG = process.platform === 'win32' ? '»' : '›' /** * Sorts two options so that the base flags are at the bottom of the list - * @param {import('commander').Option} optionA - * @param {import('commander').Option} optionB - * @returns {number} * @example - * options.sort(sortOptions) + * options.sort(compareOptions) */ -// @ts-expect-error TS(7006) FIXME: Parameter 'optionA' implicitly has an 'any' type. -export const sortOptions = (optionA, optionB) => { +export const compareOptions = (optionA: Option, optionB: Option): number => { + const longOptionA = optionA.long ?? '' + const longOptionB = optionB.long ?? '' // base flags should be always at the bottom - if (BASE_FLAGS.has(optionA.long) || BASE_FLAGS.has(optionB.long)) { + // FIXME(serhalp) This logic can't be right? Look at it... This must only happen to work. + if (BASE_FLAGS.has(longOptionA) || BASE_FLAGS.has(longOptionB)) { return -1 } - return optionA.long.localeCompare(optionB.long) + return longOptionA.localeCompare(longOptionB) } // Poll Token timeout 5 Minutes @@ -94,11 +82,18 @@ export const pollForToken = async ({ ticket, }: { api: NetlifyAPI - ticket: { id?: string; client_id?: string; authorized?: boolean; created_at?: string } + ticket: { + id?: string + client_id?: string + authorized?: boolean + created_at?: string + } }) => { const spinner = startSpinner({ text: 'Waiting for authorization...' }) try { - const accessToken = await api.getAccessToken(ticket, { timeout: TOKEN_TIMEOUT }) + const accessToken = await api.getAccessToken(ticket, { + timeout: TOKEN_TIMEOUT, + }) if (!accessToken) { return logAndThrowError('Could not retrieve access token') } @@ -107,11 +102,11 @@ export const pollForToken = async ({ // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'. if (error_.name === 'TimeoutError') { return logAndThrowError( - `Timed out waiting for authorization. If you do not have a ${chalk.bold.greenBright( + `Timed out waiting for authorization. If you do not have a ${ansis.bold.greenBright( 'Netlify', - )} account, please create one at ${chalk.magenta( + )} account, please create one at ${ansis.magenta( 'https://app.netlify.com/signup', - )}, then run ${chalk.cyanBright('netlify login')} again.`, + )}, then run ${ansis.cyanBright('netlify login')} again.`, ) } else { return logAndThrowError(error_) @@ -180,7 +175,7 @@ export const logPadded = (message = '', ...args: string[]) => { * logs a warning message */ export const warn = (message = '') => { - const bang = chalk.yellow(BANG) + const bang = ansis.yellow(BANG) log(` ${bang} Warning: ${message}`) } @@ -201,11 +196,11 @@ export const logAndThrowError = (message: unknown): never => { export const logError = (message: unknown): void => { const err = toError(message) - const bang = chalk.red(BANG) + const bang = ansis.red(BANG) if (process.env.DEBUG) { process.stderr.write(` ${bang} Warning: ${err.stack?.split('\n').join(`\n ${bang} `)}\n`) } else { - process.stderr.write(` ${bang} ${chalk.red(`${err.name}:`)} ${err.message}\n`) + process.stderr.write(` ${bang} ${ansis.red(`${err.name}:`)} ${err.message}\n`) } } @@ -250,7 +245,11 @@ export const watchDebounced = async ( { depth, ignored = [], onAdd = noOp, onChange = noOp, onUnlink = noOp }: WatchDebouncedOptions, ) => { const baseIgnores = [/\/(node_modules|.git)\//] - const watcher = chokidar.watch(target, { depth, ignored: [...baseIgnores, ...ignored], ignoreInitial: true }) + const watcher = chokidar.watch(target, { + depth, + ignored: [...baseIgnores, ...ignored], + ignoreInitial: true, + }) await once(watcher, 'ready') @@ -333,14 +332,14 @@ export interface GitHubRepoResponse { is_template?: boolean } -export const checkFileForLine = (filename: string, line: string) => { - let filecontent = '' +export const checkFileForLine = (filename: string, line: string): boolean => { + let fileContent = '' try { - filecontent = fs.readFileSync(filename, 'utf8') + fileContent = fs.readFileSync(filename, 'utf8') } catch (error_) { return logAndThrowError(error_) } - return !!filecontent.match(line) + return fileContent.includes(line) } export const TABTAB_CONFIG_LINE = '[[ -f ~/.config/tabtab/__tabtab.zsh ]] && . ~/.config/tabtab/__tabtab.zsh || true' diff --git a/src/utils/detect-server-settings.ts b/src/utils/detect-server-settings.ts index 02f7947fdb7..6eb389aaddb 100644 --- a/src/utils/detect-server-settings.ts +++ b/src/utils/detect-server-settings.ts @@ -10,14 +10,14 @@ import BaseCommand from '../commands/base-command.js' import { type DevConfig } from '../commands/dev/types.js' import { detectFrameworkSettings } from './build-info.js' -import { NETLIFYDEVWARN, chalk, log } from './command-helpers.js' +import { NETLIFYDEVWARN, ansis, log } from './command-helpers.js' import { acquirePort } from './dev.js' import { getPluginsToAutoInstall } from './init/utils.js' import { BaseServerSettings, ServerSettings } from './types.js' import { CachedConfig } from '../lib/build.js' -const formatProperty = (str: string) => chalk.magenta(`'${str}'`) -const formatValue = (str: string) => chalk.green(`'${str}'`) +const formatProperty = (str: string) => ansis.magenta(`'${str}'`) +const formatValue = (str: string) => ansis.green(`'${str}'`) const readHttpsSettings = async (options: { keyFile: string diff --git a/src/utils/dev.ts b/src/utils/dev.ts index 2d15958bc67..704189a23c4 100644 --- a/src/utils/dev.ts +++ b/src/utils/dev.ts @@ -5,34 +5,34 @@ import isEmpty from 'lodash/isEmpty.js' import { supportsBackgroundFunctions } from '../lib/account.js' -import { NETLIFYDEVLOG, chalk, logAndThrowError, log, warn, APIError } from './command-helpers.js' +import { NETLIFYDEVLOG, ansis, logAndThrowError, log, warn, APIError } from './command-helpers.js' import { loadDotEnvFiles } from './dot-env.js' // Possible sources of environment variables. For the purpose of printing log messages only. Order does not matter. const ENV_VAR_SOURCES = { account: { name: 'shared', - printFn: chalk.magenta, + printFn: ansis.magenta, }, addons: { name: 'addon', - printFn: chalk.yellow, + printFn: ansis.yellow, }, configFile: { name: 'netlify.toml file', - printFn: chalk.green, + printFn: ansis.green, }, general: { name: 'general context', - printFn: chalk.italic, + printFn: ansis.italic, }, process: { name: 'process', - printFn: chalk.red, + printFn: ansis.red, }, ui: { name: 'site settings', - printFn: chalk.blue, + printFn: ansis.blue, }, } @@ -43,7 +43,7 @@ const ERROR_CALL_TO_ACTION = const validateSiteInfo = ({ site, siteInfo }) => { if (isEmpty(siteInfo)) { return logAndThrowError( - `Failed retrieving site information for site ${chalk.yellow(site.id)}. ${ERROR_CALL_TO_ACTION}`, + `Failed retrieving site information for site ${ansis.yellow(site.id)}. ${ERROR_CALL_TO_ACTION}`, ) } } @@ -65,7 +65,7 @@ const getAddons = async ({ api, site }) => { return addons } catch (error_) { return logAndThrowError( - `Failed retrieving addons for site ${chalk.yellow(site.id)}: ${ + `Failed retrieving addons for site ${ansis.yellow(site.id)}: ${ (error_ as APIError).message }. ${ERROR_CALL_TO_ACTION}`, ) @@ -147,7 +147,7 @@ export const getSiteInformation = async ({ api, offline, site, siteInfo }) => { // @ts-expect-error TS(7006) FIXME: Parameter 'source' implicitly has an 'any' type. const getEnvSourceName = (source) => { // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message - const { name = source, printFn = chalk.green } = ENV_VAR_SOURCES[source] || {} + const { name = source, printFn = ansis.green } = ENV_VAR_SOURCES[source] || {} return printFn(name) } @@ -200,8 +200,8 @@ export const injectEnvVariables = (env) => { const sourceName = getEnvSourceName(source) log( - chalk.dim( - `${NETLIFYDEVLOG} Ignored ${chalk.bold(sourceName)} env var: ${chalk.yellow( + ansis.dim( + `${NETLIFYDEVLOG} Ignored ${ansis.bold(sourceName)} env var: ${ansis.yellow( key, )} (defined in ${usedSourceName})`, ), @@ -211,7 +211,7 @@ export const injectEnvVariables = (env) => { if (!existsInProcess || isInternal) { // Omitting `general` and `internal` env vars to reduce noise in the logs. if (usedSource !== 'general' && !isInternal) { - log(`${NETLIFYDEVLOG} Injected ${usedSourceName} env var: ${chalk.yellow(key)}`) + log(`${NETLIFYDEVLOG} Injected ${usedSourceName} env var: ${ansis.yellow(key)}`) } // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'. diff --git a/src/utils/init/config-github.ts b/src/utils/init/config-github.ts index fffb7b1fd6f..1f5f0e47964 100644 --- a/src/utils/init/config-github.ts +++ b/src/utils/init/config-github.ts @@ -1,7 +1,7 @@ import { Octokit } from '@octokit/rest' import type { NetlifyAPI } from 'netlify' -import { chalk, logAndThrowError, log } from '../command-helpers.js' +import { ansis, logAndThrowError, log } from '../command-helpers.js' import { getGitHubToken as ghauth, type Token } from '../gh-auth.js' import type { GlobalConfigStore } from '../types.js' import type { BaseCommand } from '../../commands/index.js' @@ -9,8 +9,8 @@ import type { BaseCommand } from '../../commands/index.js' import { createDeployKey, formatErrorMessage, getBuildSettings, saveNetlifyToml, setupSite } from './utils.js' const formatRepoAndOwner = ({ repoName, repoOwner }: { repoName: string; repoOwner: string }) => ({ - name: chalk.magenta(repoName), - owner: chalk.magenta(repoOwner), + name: ansis.magenta(repoName), + owner: ansis.magenta(repoOwner), }) const PAGE_SIZE = 100 @@ -21,7 +21,7 @@ const PAGE_SIZE = 100 export const getGitHubToken = async ({ globalConfig }: { globalConfig: GlobalConfigStore }): Promise => { const userId = globalConfig.get('userId') - const githubToken: Token = globalConfig.get(`users.${userId}.auth.github`) + const githubToken: Token | undefined = globalConfig.get(`users.${userId}.auth.github`) if (githubToken && githubToken.user && githubToken.token) { try { @@ -31,12 +31,12 @@ export const getGitHubToken = async ({ globalConfig }: { globalConfig: GlobalCon return githubToken.token } } catch { - log(chalk.yellow('Token is expired or invalid!')) + log(ansis.yellow('Token is expired or invalid!')) log('Generating a new Github token...') } } - const newToken = await ghauth() + const newToken: Token = await ghauth() globalConfig.set(`users.${userId}.auth.github`, newToken) return newToken.token } @@ -202,7 +202,7 @@ const addNotificationHooks = async ({ api, siteId, token }) => { try { await upsertHook({ ntlHooks, event, api, siteId, token }) } catch (error) { - const message = formatErrorMessage({ message: `Failed settings Netlify hook ${chalk.magenta(event)}`, error }) + const message = formatErrorMessage({ message: `Failed settings Netlify hook ${ansis.magenta(event)}`, error }) return logAndThrowError(message) } }), diff --git a/src/utils/init/config.ts b/src/utils/init/config.ts index 2d8a5405e2c..48e737e1626 100644 --- a/src/utils/init/config.ts +++ b/src/utils/init/config.ts @@ -1,5 +1,5 @@ import BaseCommand from '../../commands/base-command.js' -import { chalk, log } from '../command-helpers.js' +import { ansis, log } from '../command-helpers.js' import type { RepoData } from '../get-repo-data.js' import { configGithub } from './config-github.js' @@ -7,14 +7,14 @@ import configManual from './config-manual.js' const logSuccess = ({ provider }: RepoData): void => { log() - log(chalk.greenBright.bold.underline(`Success! Netlify CI/CD Configured!`)) + log(ansis.greenBright.bold.underline('Success! Netlify CI/CD Configured!')) log() log(`This site is now configured to automatically deploy from ${provider} branches & pull requests`) log() log(`Next steps: - ${chalk.cyanBright.bold('git push')} Push to your git repository to trigger new site builds - ${chalk.cyanBright.bold('netlify open')} Open the Netlify admin URL of your site + ${ansis.cyanBright.bold('git push')} Push to your git repository to trigger new site builds + ${ansis.cyanBright.bold('netlify open')} Open the Netlify admin URL of your site `) } diff --git a/src/utils/init/utils.ts b/src/utils/init/utils.ts index 95a39e33ae9..660e831fc73 100644 --- a/src/utils/init/utils.ts +++ b/src/utils/init/utils.ts @@ -10,12 +10,12 @@ import type BaseCommand from '../../commands/base-command.js' import { fileExistsAsync } from '../../lib/fs.js' import { normalizeBackslash } from '../../lib/path.js' import { detectBuildSettings } from '../build-info.js' -import { chalk, logAndThrowError, log, type NormalizedCachedConfigConfig, warn } from '../command-helpers.js' +import { ansis, logAndThrowError, log, type NormalizedCachedConfigConfig, warn } from '../command-helpers.js' import type { Plugin } from '../types.js' import { getRecommendPlugins, getUIPlugins } from './plugins.js' -const formatTitle = (title: string) => chalk.cyan(title) +const formatTitle = (title: string) => ansis.cyan(title) /** * Retrieve a list of plugins to auto install @@ -203,7 +203,7 @@ export const saveNetlifyToml = async ({ // @ts-expect-error TS(7031) FIXME: Binding element 'error' implicitly has an 'any' ty... Remove this comment to see the full error message export const formatErrorMessage = ({ error, message }) => { const errorMessage = error.json ? `${error.message} - ${JSON.stringify(error.json)}` : error.message - return `${message} with error: ${chalk.red(errorMessage)}` + return `${message} with error: ${ansis.red(errorMessage)}` } export type DeployKey = Awaited> diff --git a/src/utils/live-tunnel.ts b/src/utils/live-tunnel.ts index 74cb50e6806..42c7fae28a3 100644 --- a/src/utils/live-tunnel.ts +++ b/src/utils/live-tunnel.ts @@ -7,7 +7,7 @@ import { v4 as uuidv4 } from 'uuid' import { fetchLatestVersion, shouldFetchLatestVersion } from '../lib/exec-fetcher.js' import { getPathInHome } from '../lib/settings.js' -import { NETLIFYDEVERR, NETLIFYDEVLOG, chalk, exit, log } from './command-helpers.js' +import { NETLIFYDEVERR, NETLIFYDEVLOG, ansis, exit, log } from './command-helpers.js' import execa from './execa.js' import type CLIState from './cli-state.js' @@ -119,15 +119,15 @@ export const startLiveTunnel = async ({ }) => { if (!siteId) { console.error( - `${NETLIFYDEVERR} Error: no siteId defined, did you forget to run ${chalk.yellow( + `${NETLIFYDEVERR} Error: no siteId defined, did you forget to run ${ansis.yellow( 'netlify init', - )} or ${chalk.yellow('netlify link')}?`, + )} or ${ansis.yellow('netlify link')}?`, ) return exit(1) } if (!netlifyApiToken) { console.error( - `${NETLIFYDEVERR} Error: no Netlify auth token defined, did you forget to run ${chalk.yellow( + `${NETLIFYDEVERR} Error: no Netlify auth token defined, did you forget to run ${ansis.yellow( 'netlify login', )} or define 'NETLIFY_AUTH_TOKEN'?`, ) diff --git a/src/utils/open-browser.ts b/src/utils/open-browser.ts index 22eaebc7e53..cac3d48b928 100644 --- a/src/utils/open-browser.ts +++ b/src/utils/open-browser.ts @@ -1,9 +1,9 @@ import process from 'process' -import open from 'open' import isDockerContainer from 'is-docker' +import open from 'open' -import { chalk, log } from './command-helpers.js' +import { ansis, log } from './command-helpers.js' type BrowserUnableMessage = { message: string @@ -12,18 +12,18 @@ type BrowserUnableMessage = { const unableToOpenBrowserMessage = function ({ message, url }: BrowserUnableMessage) { log('---------------------------') - log(chalk.redBright(`Error: Unable to open browser automatically: ${message}`)) - log(chalk.cyan('Please open your browser and open the URL below:')) - log(chalk.bold(url)) + log(ansis.redBright(`Error: Unable to open browser automatically: ${message}`)) + log(ansis.cyan('Please open your browser and open the URL below:')) + log(ansis.bold(url)) log('---------------------------') } -type OpenBrowsrProps = { +type OpenBrowserProps = { silentBrowserNoneError?: boolean url: string } -const openBrowser = async function ({ silentBrowserNoneError, url }: OpenBrowsrProps) { +const openBrowser = async function ({ silentBrowserNoneError, url }: OpenBrowserProps) { if (isDockerContainer()) { unableToOpenBrowserMessage({ url, message: 'Running inside a docker container' }) return diff --git a/src/utils/prompts/prompt-messages.ts b/src/utils/prompts/prompt-messages.ts index 29014136499..486fabedab0 100644 --- a/src/utils/prompts/prompt-messages.ts +++ b/src/utils/prompts/prompt-messages.ts @@ -1,11 +1,11 @@ -import { chalk } from '../command-helpers.js' +import { ansis } from '../command-helpers.js' export const destructiveCommandMessages = { - overwriteNotice: `${chalk.yellowBright('Notice')}: To overwrite without this warning, you can use the --force flag.`, + overwriteNotice: `${ansis.yellowBright('Notice')}: To overwrite without this warning, you can use the --force flag.`, blobSet: { generateWarning: (key: string, storeName: string) => - `${chalk.redBright('Warning')}: The blob key ${chalk.cyan(key)} already exists in store ${chalk.cyan( + `${ansis.redBright('Warning')}: The blob key ${ansis.cyan(key)} already exists in store ${ansis.cyan( storeName, )}!`, overwriteConfirmation: 'Do you want to proceed with overwriting this blob key existing value?', @@ -13,7 +13,7 @@ export const destructiveCommandMessages = { blobDelete: { generateWarning: (key: string, storeName: string) => - `${chalk.redBright('Warning')}: The following blob key ${chalk.cyan(key)} will be deleted from store ${chalk.cyan( + `${ansis.redBright('Warning')}: The following blob key ${ansis.cyan(key)} will be deleted from store ${ansis.cyan( storeName, )}!`, overwriteConfirmation: 'Do you want to proceed with deleting the value at this key?', @@ -21,13 +21,13 @@ export const destructiveCommandMessages = { envSet: { generateWarning: (variableName: string) => - `${chalk.redBright('Warning')}: The environment variable ${chalk.bgBlueBright(variableName)} already exists!`, + `${ansis.redBright('Warning')}: The environment variable ${ansis.bgBlueBright(variableName)} already exists!`, overwriteConfirmation: 'Do you want to overwrite it?', }, envUnset: { generateWarning: (variableName: string) => - `${chalk.redBright('Warning')}: The environment variable ${chalk.bgBlueBright( + `${ansis.redBright('Warning')}: The environment variable ${ansis.bgBlueBright( variableName, )} will be removed from all contexts!`, overwriteConfirmation: 'Do you want to remove it?', @@ -35,12 +35,12 @@ export const destructiveCommandMessages = { envClone: { generateWarning: (siteId: string) => - `${chalk.redBright( + `${ansis.redBright( 'Warning', - )}: The following environment variables are already set on the site with ID ${chalk.bgBlueBright( + )}: The following environment variables are already set on the site with ID ${ansis.bgBlueBright( siteId, )}. They will be overwritten!`, - noticeEnvVars: `${chalk.yellowBright('Notice')}: The following variables will be overwritten:`, + noticeEnvVars: `${ansis.yellowBright('Notice')}: The following variables will be overwritten:`, overwriteConfirmation: 'The environment variables already exist. Do you want to overwrite them?', }, } diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts index 7f014646348..22d394eaaa3 100644 --- a/src/utils/proxy.ts +++ b/src/utils/proxy.ts @@ -39,7 +39,7 @@ import { DEFAULT_FUNCTION_URL_EXPRESSION } from '../lib/functions/registry.js' import { initializeProxy as initializeImageProxy, isImageRequest } from '../lib/images/proxy.js' import renderErrorTemplate from '../lib/render-error-template.js' -import { NETLIFYDEVLOG, NETLIFYDEVWARN, type NormalizedCachedConfigConfig, chalk, log } from './command-helpers.js' +import { NETLIFYDEVLOG, NETLIFYDEVWARN, type NormalizedCachedConfigConfig, ansis, log } from './command-helpers.js' import createStreamPromise from './create-stream-promise.js' import { NFFunctionName, NFFunctionRoute, NFRequestID, headersForPath, parseHeaders } from './headers.js' import { generateRequestID } from './request-id.js' @@ -154,7 +154,7 @@ const isEndpointExists = async function (endpoint: string, origin: string) { try { const res = await fetch(url, { method: 'HEAD' }) return res.status !== 404 - } catch (e) { + } catch { return false } } @@ -292,7 +292,7 @@ const serveRedirect = async function ({ } else { log( NETLIFYDEVWARN, - `Could not sign redirect because environment variable ${chalk.yellow(match.signingSecret)} is not set`, + `Could not sign redirect because environment variable ${ansis.yellow(match.signingSecret)} is not set`, ) } } diff --git a/src/utils/shell.ts b/src/utils/shell.ts index 6666f3202ae..fe57a3fb0d7 100644 --- a/src/utils/shell.ts +++ b/src/utils/shell.ts @@ -5,7 +5,7 @@ import execa from 'execa' import stripAnsiCc from 'strip-ansi-control-characters' import { stopSpinner, type Spinner } from '../lib/spinner.js' -import { chalk, log, NETLIFYDEVERR, NETLIFYDEVWARN } from './command-helpers.js' +import { ansis, log, NETLIFYDEVERR, NETLIFYDEVWARN } from './command-helpers.js' import { processOnExit } from './dev.js' /** @@ -86,7 +86,7 @@ export const runCommand = ( const [commandWithoutArgs] = command.split(' ') if (result.failed && isNonExistingCommandError({ command: commandWithoutArgs, error: result })) { log( - `${NETLIFYDEVERR} Failed running command: ${command}. Please verify ${chalk.magenta( + `${NETLIFYDEVERR} Failed running command: ${command}. Please verify ${ansis.magenta( `'${commandWithoutArgs}'`, )} exists`, ) diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 141ca0f4605..c75900195dc 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -1,17 +1,12 @@ -import { BANG, chalk } from './command-helpers.js' +import { BANG, ansis } from './command-helpers.js' -/** - * @param {string} exampleCommand - * @returns {(value:string, previous: unknown) => unknown} - */ -// @ts-expect-error TS(7006) FIXME: Parameter 'exampleCommand' implicitly has an 'any'... Remove this comment to see the full error message -export const getGeoCountryArgParser = (exampleCommand) => (arg) => { +export const getGeoCountryArgParser = (exampleCommand: string) => (arg: string) => { // Validate that the arg passed is two letters only for country // See https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes if (!/^[a-z]{2}$/i.test(arg)) { throw new Error( `The geo country code must use a two letter abbreviation. - ${chalk.red(BANG)} Example: + ${ansis.red(BANG)} Example: ${exampleCommand}`, ) } diff --git a/tests/integration/commands/blobs/blobs-delete.test.ts b/tests/integration/commands/blobs/blobs-delete.test.ts index 14e1aa8ded6..2ab512a9215 100644 --- a/tests/integration/commands/blobs/blobs-delete.test.ts +++ b/tests/integration/commands/blobs/blobs-delete.test.ts @@ -1,7 +1,7 @@ import process from 'process' import { getStore } from '@netlify/blobs' -import chalk from 'chalk' +import ansis from 'ansis' import { describe, expect, test, vi, beforeEach, afterAll } from 'vitest' import { log } from '../../../../src/utils/command-helpers.js' @@ -58,9 +58,9 @@ describe('blobs:delete command', () => { const warningMessage = generateWarning(key, storeName) - const successMessage = `${chalk.greenBright('Success')}: Blob ${chalk.yellow( + const successMessage = `${ansis.greenBright('Success')}: Blob ${ansis.yellow( key, - )} deleted from store ${chalk.yellow(storeName)}` + )} deleted from store ${ansis.yellow(storeName)}` beforeEach(() => { vi.resetModules() @@ -184,7 +184,7 @@ describe('blobs:delete command', () => { } catch (error) { expect(error).toBeInstanceOf(Error) expect((error as Error).message).toContain( - `Could not delete blob ${chalk.yellow(key)} from store ${chalk.yellow(storeName)}`, + `Could not delete blob ${ansis.yellow(key)} from store ${ansis.yellow(storeName)}`, ) } diff --git a/tests/integration/commands/blobs/blobs-set.test.ts b/tests/integration/commands/blobs/blobs-set.test.ts index 4b55fc1058e..4fba7f6fc8f 100644 --- a/tests/integration/commands/blobs/blobs-set.test.ts +++ b/tests/integration/commands/blobs/blobs-set.test.ts @@ -1,7 +1,7 @@ import process from 'process' import { getStore } from '@netlify/blobs' -import chalk from 'chalk' +import ansis from 'ansis' import inquirer from 'inquirer' import { describe, expect, test, vi, beforeEach, afterAll } from 'vitest' @@ -60,7 +60,7 @@ describe('blobs:set command', () => { const warningMessage = generateWarning(key, storeName) - const successMessage = `${chalk.greenBright('Success')}: Blob ${chalk.yellow(key)} set in store ${chalk.yellow( + const successMessage = `${ansis.greenBright('Success')}: Blob ${ansis.yellow(key)} set in store ${ansis.yellow( storeName, )}` @@ -216,7 +216,7 @@ describe('blobs:set command', () => { } catch (error) { expect(error).toBeInstanceOf(Error) expect((error as Error).message).toContain( - `Could not set blob ${chalk.yellow(key)} in store ${chalk.yellow(storeName)}`, + `Could not set blob ${ansis.yellow(key)} in store ${ansis.yellow(storeName)}`, ) } diff --git a/tests/integration/commands/build/build-program.test.ts b/tests/integration/commands/build/build-program.test.ts index 2d177b27ee6..9de65bec59d 100644 --- a/tests/integration/commands/build/build-program.test.ts +++ b/tests/integration/commands/build/build-program.test.ts @@ -36,7 +36,6 @@ const routes = [ response: [{ slug: siteInfo.account_slug }], }, ] -// eslint-disable-next-line no-restricted-properties const originalCwd = process.cwd const originalConsoleLog = console.log const originalEnv = process.env @@ -50,7 +49,6 @@ describe('command/build', () => { }) afterAll(() => { - // eslint-disable-next-line no-restricted-properties process.cwd = originalCwd console.log = originalConsoleLog process.env = originalEnv @@ -66,7 +64,6 @@ describe('command/build', () => { expect(code).toBe(0) }) await withSiteBuilder(t, async (builder) => { - // eslint-disable-next-line no-restricted-properties process.cwd = () => builder.directory await withMockApi(routes, async ({ apiUrl }) => { process.env = getEnvironmentVariables({ apiUrl }) diff --git a/tests/integration/commands/env/env-clone.test.ts b/tests/integration/commands/env/env-clone.test.ts index d0626ea822a..08d36270684 100644 --- a/tests/integration/commands/env/env-clone.test.ts +++ b/tests/integration/commands/env/env-clone.test.ts @@ -2,7 +2,7 @@ import process from 'process' import { describe, expect, test, vi, beforeEach, afterAll } from 'vitest' -import { chalk, log } from '../../../../src/utils/command-helpers.js' +import { ansis, log } from '../../../../src/utils/command-helpers.js' import { generateEnvVarsList } from '../.././../../src/utils/prompts/env-clone-prompt.js' import { destructiveCommandMessages } from '../.././../../src/utils/prompts/prompt-messages.js' import { getEnvironmentVariables, withMockApi, setTTYMode, setCI, setTestingPrompts } from '../../utils/mock-api.js' @@ -29,7 +29,7 @@ describe('env:clone command', () => { const envVarsList = generateEnvVarsList(sharedEnvVars) const warningMessage = generateWarning(siteIdTwo) - const successMessage = `Successfully cloned environment variables from ${chalk.green('site-name')} to ${chalk.green( + const successMessage = `Successfully cloned environment variables from ${ansis.green('site-name')} to ${ansis.green( 'site-name-2', )}` @@ -128,9 +128,9 @@ describe('env:clone command', () => { test('should not run prompts if sites have no enviroment variables in common', async () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const successMessageSite3 = `Successfully cloned environment variables from ${chalk.green( + const successMessageSite3 = `Successfully cloned environment variables from ${ansis.green( 'site-name', - )} to ${chalk.green('site-name-3')}` + )} to ${ansis.green('site-name-3')}` const promptSpy = spyOnMockPrompt() diff --git a/tests/integration/commands/env/env-set.test.ts b/tests/integration/commands/env/env-set.test.ts index 4e1c4fd2f0c..e5dca25d27b 100644 --- a/tests/integration/commands/env/env-set.test.ts +++ b/tests/integration/commands/env/env-set.test.ts @@ -1,15 +1,16 @@ import process from 'process' -import chalk from 'chalk' +import ansis from 'ansis' import { describe, expect, test, vi, beforeEach, afterAll } from 'vitest' import { log } from '../../../../src/utils/command-helpers.js' -import { destructiveCommandMessages } from '../.././../../src/utils/prompts/prompt-messages.js' +import { destructiveCommandMessages } from '../../../../src/utils/prompts/prompt-messages.js' import { FixtureTestContext, setupFixtureTests } from '../../utils/fixture.js' +import { mockPrompt, spyOnMockPrompt } from '../../utils/inquirer-mock-prompt.js' import { getEnvironmentVariables, withMockApi, setTTYMode, setCI, setTestingPrompts } from '../../utils/mock-api.js' import { runMockProgram } from '../../utils/mock-program.js' + import { routes } from './api-routes.js' -import { mockPrompt, spyOnMockPrompt } from '../../utils/inquirer-mock-prompt.js' vi.mock('../../../../src/utils/command-helpers.js', async () => ({ ...(await vi.importActual('../../../../src/utils/command-helpers.js')), @@ -281,9 +282,9 @@ describe('env:set command', async () => { const warningMessage = generateWarning(existingVar) - const successMessage = `Set environment variable ${chalk.yellow( + const successMessage = `Set environment variable ${ansis.yellow( `${existingVar}=${newEnvValue}`, - )} in the ${chalk.magenta('all')} context` + )} in the ${ansis.magenta('all')} context` beforeEach(() => { vi.resetModules() @@ -339,7 +340,7 @@ describe('env:set command', async () => { expect(log).not.toHaveBeenCalledWith(warningMessage) expect(log).not.toHaveBeenCalledWith(overwriteNotice) expect(log).toHaveBeenCalledWith( - `Set environment variable ${chalk.yellow(`NEW_ENV_VAR=NEW_VALUE`)} in the ${chalk.magenta('all')} context`, + `Set environment variable ${ansis.yellow(`NEW_ENV_VAR=NEW_VALUE`)} in the ${ansis.magenta('all')} context`, ) }) }) diff --git a/tests/integration/commands/env/env-unset.test.ts b/tests/integration/commands/env/env-unset.test.ts index b85d790ff9e..82c44fe79ad 100644 --- a/tests/integration/commands/env/env-unset.test.ts +++ b/tests/integration/commands/env/env-unset.test.ts @@ -2,14 +2,14 @@ import process from 'process' import { describe, expect, test, vi, beforeEach, afterAll } from 'vitest' -import { chalk, log } from '../../../../src/utils/command-helpers.js' +import { ansis, log } from '../../../../src/utils/command-helpers.js' import { destructiveCommandMessages } from '../.././../../src/utils/prompts/prompt-messages.js' import { FixtureTestContext, setupFixtureTests } from '../../utils/fixture.js' +import { mockPrompt, spyOnMockPrompt } from '../../utils/inquirer-mock-prompt.js' import { getEnvironmentVariables, withMockApi, setTTYMode, setCI, setTestingPrompts } from '../../utils/mock-api.js' +import { runMockProgram } from '../../utils/mock-program.js' import { routes } from './api-routes.js' -import { runMockProgram } from '../../utils/mock-program.js' -import { mockPrompt, spyOnMockPrompt } from '../../utils/inquirer-mock-prompt.js' vi.mock('../../../../src/utils/command-helpers.js', async () => ({ ...(await vi.importActual('../../../../src/utils/command-helpers.js')), @@ -83,7 +83,7 @@ describe('env:unset command', async () => { // already exists as value in withMockApi const existingVar = 'EXISTING_VAR' const warningMessage = generateWarning(existingVar) - const expectedSuccessMessage = `Unset environment variable ${chalk.yellow(existingVar)} in the ${chalk.magenta( + const expectedSuccessMessage = `Unset environment variable ${ansis.yellow(existingVar)} in the ${ansis.magenta( 'all', )} context` diff --git a/tests/integration/commands/sites/sites-create-template.test.ts b/tests/integration/commands/sites/sites-create-template.test.ts index ec34a514dcf..8abee180c73 100644 --- a/tests/integration/commands/sites/sites-create-template.test.ts +++ b/tests/integration/commands/sites/sites-create-template.test.ts @@ -6,6 +6,7 @@ import { beforeEach, afterEach, describe, expect, test, vi, afterAll } from 'vit import BaseCommand from '../../../../src/commands/base-command.js' import { createSitesFromTemplateCommand } from '../../../../src/commands/sites/sites.js' import { deployedSiteExists, fetchTemplates, getTemplateName } from '../../../../src/utils/sites/create-template.js' +import { ansis } from '../../../../src/utils/command-helpers.js' import { getTemplatesFromGitHub, validateTemplate, @@ -13,7 +14,6 @@ import { callLinkSite, } from '../../../../src/utils/sites/utils.js' import { getEnvironmentVariables, withMockApi } from '../../utils/mock-api.js' -import { chalk } from '../../../../src/utils/command-helpers.js' vi.mock('../../../../src/utils/init/config-github.ts') vi.mock('../../../../src/utils/sites/utils.ts') @@ -197,7 +197,7 @@ describe('sites:create-template', () => { }) expect(stdoutwriteSpy).toHaveBeenCalledWith( - `\nDirectory ${chalk.cyanBright('repoName')} linked to site ${chalk.cyanBright( + `\nDirectory ${ansis.cyanBright('repoName')} linked to site ${ansis.cyanBright( 'https://site-name.netlify.app', )}\n\n`, ) @@ -243,7 +243,7 @@ describe('sites:create-template', () => { }) expect(stdoutwriteSpy).toHaveBeenCalledWith( - `\nThis directory appears to be linked to ${chalk.cyanBright(`"site-name"`)}\n`, + `\nThis directory appears to be linked to ${ansis.cyanBright(`"site-name"`)}\n`, ) }) }) diff --git a/tests/integration/utils/call-cli.js b/tests/integration/utils/call-cli.js index f0b967a712e..959c3ce92ff 100644 --- a/tests/integration/utils/call-cli.js +++ b/tests/integration/utils/call-cli.js @@ -13,10 +13,16 @@ const CLI_TIMEOUT = 3e5 * @returns {Promise} */ export const callCli = async function (args, execOptions = {}, parseJson = false) { + const { env = {}, ...execOptionsWithoutEnv } = execOptions const { stdout } = await execa.node(cliPath, args, { timeout: CLI_TIMEOUT, nodeOptions: [], - ...execOptions, + env: { + ...env, + // TODO(serhalp) Why not exercise colorization in integration tests? Remove and update snapshots? + NO_COLOR: '1', + }, + ...execOptionsWithoutEnv, }) if (parseJson) { return JSON.parse(stdout) diff --git a/tests/integration/utils/dev-server.ts b/tests/integration/utils/dev-server.ts index 344dd1bd92c..f944b830ccb 100644 --- a/tests/integration/utils/dev-server.ts +++ b/tests/integration/utils/dev-server.ts @@ -17,7 +17,8 @@ export const getExecaOptions = ({ cwd, env }: { cwd: string; env: NodeJS.Process return { cwd, extendEnv: false, - env: { ...baseEnv, BROWSER: 'none', ...env }, + // TODO(serhalp) Why not exercise colorization in integration tests? Remove and update snapshots? + env: { ...baseEnv, BROWSER: 'none', NO_COLOR: '1', ...env }, encoding: 'utf8', } } diff --git a/tests/integration/utils/handle-questions.js b/tests/integration/utils/handle-questions.js index 56ac0f1dbc3..3bac3463050 100644 --- a/tests/integration/utils/handle-questions.js +++ b/tests/integration/utils/handle-questions.js @@ -1,5 +1,7 @@ import { Buffer } from 'buffer' +import ansis from 'ansis' + /** * Utility to mock the stdin of the cli. You must provide the correct number of * questions correctly typed or the process will keep waiting for input. @@ -11,7 +13,7 @@ import { Buffer } from 'buffer' export const handleQuestions = (process, questions, prompts = []) => { let buffer = '' process.stdout.on('data', (data) => { - buffer = (buffer + data).replace(/\n/g, '') + buffer = (buffer + ansis.strip(data.toString())).replace(/\n/g, '') const index = questions.findIndex( ({ question }, questionIndex) => buffer.includes(question) && !prompts.includes(questionIndex), ) diff --git a/tests/integration/utils/mock-api.ts b/tests/integration/utils/mock-api.ts index 67c3e8bcac8..e4702206d31 100644 --- a/tests/integration/utils/mock-api.ts +++ b/tests/integration/utils/mock-api.ts @@ -145,6 +145,8 @@ export const getEnvironmentVariables = ({ apiUrl }: { apiUrl?: string }) => ({ * This function is used to establish flexible testing environments. * Falsey value is for noninteractive shell (-force flags overide user prompts) * Truthy value is for interactive shell + * + * TODO(serhalp) This has nothing to do with mocking the Netlify API. Move elsewhere. */ export const setTTYMode = (bool: boolean) => { process.stdin.isTTY = bool @@ -156,6 +158,8 @@ export const setTTYMode = (bool: boolean) => { * If this is set to 'true', then prompts will be shown in for destructive commands even in non-interactive shells * or CI/CD enviroment * + * TODO(serhalp) This has nothing to do with mocking the Netlify API. Move elsewhere. + * * @param {string} value - The value to set for the `TESTING_PROMPTS` environment variable. */ export const setTestingPrompts = (value: string) => { @@ -165,6 +169,8 @@ export const setTestingPrompts = (value: string) => { /** * Simulates a Continuous Integration environment by toggling the `CI` * environment variable. Truthy value is + * + * TODO(serhalp) This has nothing to do with mocking the Netlify API. Move elsewhere. */ export const setCI = (value: string) => { process.env.CI = value diff --git a/tests/integration/utils/snapshots.js b/tests/integration/utils/snapshots.js index f8eecb5c556..63d75a1eab9 100644 --- a/tests/integration/utils/snapshots.js +++ b/tests/integration/utils/snapshots.js @@ -1,4 +1,9 @@ +import ansis from 'ansis' + const baseNormalizers = [ + // Strip ansis color control characters + // TODO(serhalp) Why not exercise colorization in integration tests? Remove and update snapshots? + { pattern: /^.+$/g, value: ansis.strip }, // Information about the package and the OS { pattern: /netlify-cli\/.+node-.+/g, value: 'netlify-cli/test-version test-os test-node-version' }, { pattern: /@netlify\/build (\d+\.\d+\.\d+)/g, value: '@netlify/build 0.0.0' },