mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-09 21:07:46 +00:00
[cli] Add --no-color mode (#8826)
Co-authored-by: Sean Massa <EndangeredMassa@gmail.com> Co-authored-by: Chris Barber <chris.barber@vercel.com>
This commit is contained in:
@@ -32,6 +32,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
|
||||
@@ -33,6 +33,7 @@ const help = () => {
|
||||
|
||||
-h, --help Output usage information
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-b, --bad Known bad URL
|
||||
-g, --good Known good URL
|
||||
-o, --open Automatically open each URL in the browser
|
||||
|
||||
@@ -118,6 +118,7 @@ const help = () => {
|
||||
--output [path] Directory where built assets should be written to
|
||||
--prod Build a production deployment
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-y, --yes Skip the confirmation prompt about pulling environment variables and project settings when not found locally
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -37,6 +37,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
|
||||
@@ -53,6 +53,7 @@ export const help = () => `
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-f, --force Force a new deployment even if nothing has changed
|
||||
--with-cache Retain build cache when using "--force"
|
||||
-t ${chalk.underline('TOKEN')}, --token=${chalk.underline(
|
||||
|
||||
@@ -31,6 +31,7 @@ const help = () => {
|
||||
|
||||
-h, --help Output usage information
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-l, --listen [uri] Specify a URI endpoint on which to listen [0.0.0.0:3000]
|
||||
-t, --token [token] Specify an Authorization Token
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
@@ -33,6 +33,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
|
||||
@@ -33,6 +33,7 @@ const help = () => {
|
||||
|
||||
-h, --help Output usage information
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-f, --force Force a domain on a project and remove it from an existing one
|
||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||
'FILE'
|
||||
|
||||
1
packages/cli/src/commands/env/index.ts
vendored
1
packages/cli/src/commands/env/index.ts
vendored
@@ -40,6 +40,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
|
||||
@@ -21,6 +21,7 @@ const help = () => {
|
||||
|
||||
-h, --help Output usage information
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-f, --force Overwrite destination directory if exists [off]
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -32,6 +32,7 @@ const help = () => {
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-S, --scope Set a custom scope
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -19,6 +19,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
|
||||
@@ -36,6 +36,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
|
||||
@@ -23,6 +23,7 @@ const help = () => {
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
--no-color No color mode [off]
|
||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||
'FILE'
|
||||
)} Path to the local ${'`vercel.json`'} file
|
||||
|
||||
@@ -23,6 +23,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-f, --follow Wait for additional data [off]
|
||||
-n ${chalk.bold.underline(
|
||||
'NUMBER'
|
||||
|
||||
@@ -31,6 +31,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
--environment [environment] Deployment environment [development]
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
|
||||
@@ -28,6 +28,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
|
||||
@@ -30,6 +30,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-N, --next Show next page of results
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -19,6 +19,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
|
||||
@@ -103,7 +103,12 @@ const main = async () => {
|
||||
}
|
||||
|
||||
const isDebugging = argv['--debug'];
|
||||
const output = new Output(process.stderr, { debug: isDebugging });
|
||||
const isNoColor = argv['--no-color'];
|
||||
|
||||
const output = new Output(process.stderr, {
|
||||
debug: isDebugging,
|
||||
noColor: isNoColor,
|
||||
});
|
||||
|
||||
debug = output.debug;
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ const ARG_COMMON = {
|
||||
'--debug': Boolean,
|
||||
'-d': '--debug',
|
||||
|
||||
'--no-color': Boolean,
|
||||
|
||||
'--token': String,
|
||||
'-t': '--token',
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const emojiLabels = {
|
||||
const emojiLabels = {
|
||||
notice: '📝',
|
||||
tip: '💡',
|
||||
warning: '❗️',
|
||||
@@ -8,6 +8,8 @@ export const emojiLabels = {
|
||||
locked: '🔒',
|
||||
} as const;
|
||||
|
||||
const stripEmojiRegex = new RegExp(Object.values(emojiLabels).join('|'), 'gi');
|
||||
|
||||
export type EmojiLabel = keyof typeof emojiLabels;
|
||||
|
||||
export function emoji(label: EmojiLabel) {
|
||||
@@ -21,3 +23,9 @@ export function prependEmoji(message: string, emoji?: string): string {
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
export function removeEmoji(message: string): string {
|
||||
const result = message.replace(stripEmojiRegex, '').trimStart();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,14 @@ import renderLink from './link';
|
||||
import wait, { StopSpinner } from './wait';
|
||||
import type { WritableTTY } from '../../types';
|
||||
import { errorToString } from '@vercel/error-utils';
|
||||
import { removeEmoji } from '../emoji';
|
||||
|
||||
const IS_TEST = process.env.NODE_ENV === 'test';
|
||||
|
||||
export interface OutputOptions {
|
||||
debug?: boolean;
|
||||
supportsHyperlink?: boolean;
|
||||
noColor?: boolean;
|
||||
}
|
||||
|
||||
export interface LogOptions {
|
||||
@@ -25,6 +27,7 @@ export class Output {
|
||||
stream: WritableTTY;
|
||||
debugEnabled: boolean;
|
||||
supportsHyperlink: boolean;
|
||||
colorDisabled: boolean;
|
||||
private spinnerMessage: string;
|
||||
private _spinner: StopSpinner | null;
|
||||
|
||||
@@ -33,6 +36,7 @@ export class Output {
|
||||
{
|
||||
debug: debugEnabled = false,
|
||||
supportsHyperlink = detectSupportsHyperlink(stream),
|
||||
noColor = false,
|
||||
}: OutputOptions = {}
|
||||
) {
|
||||
this.stream = stream;
|
||||
@@ -40,6 +44,11 @@ export class Output {
|
||||
this.supportsHyperlink = supportsHyperlink;
|
||||
this.spinnerMessage = '';
|
||||
this._spinner = null;
|
||||
|
||||
this.colorDisabled = getNoColor(noColor);
|
||||
if (this.colorDisabled) {
|
||||
chalk.level = 0;
|
||||
}
|
||||
}
|
||||
|
||||
isDebugEnabled = () => {
|
||||
@@ -47,6 +56,9 @@ export class Output {
|
||||
};
|
||||
|
||||
print = (str: string) => {
|
||||
if (this.colorDisabled) {
|
||||
str = removeEmoji(str);
|
||||
}
|
||||
this.stopSpinner();
|
||||
this.stream.write(str);
|
||||
};
|
||||
@@ -203,3 +215,14 @@ export class Output {
|
||||
return ansiEscapes.link(chalk.cyan(text), url);
|
||||
};
|
||||
}
|
||||
|
||||
function getNoColor(noColorArg: boolean | undefined): boolean {
|
||||
// FORCE_COLOR: the standard supported by chalk https://github.com/chalk/chalk#supportscolor
|
||||
// NO_COLOR: the standard we want to support https://no-color.org/
|
||||
// noColorArg: the `--no-color` arg passed to the CLI command
|
||||
const noColor =
|
||||
process.env.FORCE_COLOR === '0' ||
|
||||
process.env.NO_COLOR === '1' ||
|
||||
noColorArg;
|
||||
return !!noColor;
|
||||
}
|
||||
|
||||
@@ -132,6 +132,14 @@ export class MockClient extends Client {
|
||||
|
||||
setArgv(...argv: string[]) {
|
||||
this.argv = [process.execPath, 'cli.js', ...argv];
|
||||
this.output = new Output(this.stderr, {
|
||||
debug: argv.includes('--debug') || argv.includes('-d'),
|
||||
noColor: argv.includes('--no-color'),
|
||||
});
|
||||
}
|
||||
|
||||
resetOutput() {
|
||||
this.output = new Output(this.stderr);
|
||||
}
|
||||
|
||||
useScenario(scenario: Scenario) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import login from '../../../src/commands/login';
|
||||
import { emoji } from '../../../src/util/emoji';
|
||||
import { client } from '../../mocks/client';
|
||||
import { useUser } from '../../mocks/user';
|
||||
|
||||
@@ -45,5 +46,115 @@ describe('login', () => {
|
||||
|
||||
await expect(exitCodePromise).resolves.toEqual(0);
|
||||
});
|
||||
|
||||
it('should allow the `--no-color` flag', async () => {
|
||||
const user = useUser();
|
||||
client.setArgv('login', '--no-color');
|
||||
const exitCodePromise = login(client);
|
||||
await expect(client.stderr).toOutput(`> Log in to Vercel`);
|
||||
|
||||
// Move down to "Email" option
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\r'); // Return key
|
||||
|
||||
await expect(client.stderr).toOutput('> Enter your email address:');
|
||||
|
||||
client.stdin.write(`${user.email}\n`);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Success! Email authentication complete for ${user.email}`
|
||||
);
|
||||
|
||||
await expect(client.stderr).not.toOutput(emoji('tip'));
|
||||
|
||||
await expect(exitCodePromise).resolves.toEqual(0);
|
||||
});
|
||||
|
||||
describe('with NO_COLOR="1" env var', () => {
|
||||
let previousNoColor: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
previousNoColor = process.env.NO_COLOR;
|
||||
process.env.NO_COLOR = '1';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.NO_COLOR;
|
||||
if (previousNoColor) {
|
||||
process.env.NO_COLOR = previousNoColor;
|
||||
}
|
||||
});
|
||||
|
||||
it('should remove emoji the `NO_COLOR` env var with 1', async () => {
|
||||
client.resetOutput();
|
||||
|
||||
const user = useUser();
|
||||
client.setArgv('login');
|
||||
const exitCodePromise = login(client);
|
||||
await expect(client.stderr).toOutput(`> Log in to Vercel`);
|
||||
|
||||
// Move down to "Email" option
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\r'); // Return key
|
||||
|
||||
await expect(client.stderr).toOutput('> Enter your email address:');
|
||||
|
||||
client.stdin.write(`${user.email}\n`);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Success! Email authentication complete for ${user.email}`
|
||||
);
|
||||
|
||||
await expect(client.stderr).not.toOutput(emoji('tip'));
|
||||
|
||||
await expect(exitCodePromise).resolves.toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with FORCE_COLOR="0" env var', () => {
|
||||
let previousForceColor: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
previousForceColor = process.env.FORCE_COLOR;
|
||||
process.env.FORCE_COLOR = '0';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.FORCE_COLOR;
|
||||
if (previousForceColor) {
|
||||
process.env.FORCE_COLOR = previousForceColor;
|
||||
}
|
||||
});
|
||||
|
||||
it('should remove emoji the `FORCE_COLOR` env var with 0', async () => {
|
||||
client.resetOutput();
|
||||
|
||||
const user = useUser();
|
||||
client.setArgv('login');
|
||||
const exitCodePromise = login(client);
|
||||
await expect(client.stderr).toOutput(`> Log in to Vercel`);
|
||||
|
||||
// Move down to "Email" option
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\r'); // Return key
|
||||
|
||||
await expect(client.stderr).toOutput('> Enter your email address:');
|
||||
|
||||
client.stdin.write(`${user.email}\n`);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Success! Email authentication complete for ${user.email}`
|
||||
);
|
||||
|
||||
await expect(client.stderr).not.toOutput(emoji('tip'));
|
||||
await expect(exitCodePromise).resolves.toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user