Compare commits

..

5 Commits

Author SHA1 Message Date
luc
0ee88366ff Publish Stable
- vercel@20.1.4
2020-11-12 17:16:29 +01:00
luc
9ae42c9e92 Publish Canary
- vercel@20.1.4-canary.1
2020-11-12 16:59:28 +01:00
Luc Leray
62b8df4a8d [cli] Fix vc env rm with advanced env variables (#5411) 2020-11-12 16:58:41 +01:00
luc
73ec7f3018 Publish Canary
- vercel@20.1.4-canary.0
2020-11-12 15:31:54 +01:00
Luc Leray
2d24a75ca6 Revert "[cli] (major) Update vercel env (#5372)" (#5410)
* Revert "[cli] (major) Update `vercel env` (#5372)"

This reverts commit 9a57cc72dd.

* fix test

* do not change prompt UI
2020-11-12 15:30:47 +01:00
13 changed files with 176 additions and 359 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "20.1.3",
"version": "20.1.4",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -146,6 +146,7 @@
"minimatch": "3.0.4",
"mri": "1.1.5",
"ms": "2.1.2",
"nanoid": "3.0.2",
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"nyc": "13.2.0",

View File

@@ -1,6 +1,6 @@
import chalk from 'chalk';
import inquirer from 'inquirer';
import { ProjectEnvTarget, Project, Secret, ProjectEnvType } from '../../types';
import { ProjectEnvTarget, Project } from '../../types';
import { Output } from '../../util/output';
import Client from '../../util/client';
import stamp from '../../util/output/stamp';
@@ -11,14 +11,12 @@ import {
getEnvTargetPlaceholder,
getEnvTargetChoices,
} from '../../util/env/env-target';
import { isValidEnvType, getEnvTypePlaceholder } from '../../util/env/env-type';
import readStandardInput from '../../util/input/read-standard-input';
import param from '../../util/output/param';
import withSpinner from '../../util/with-spinner';
import { emoji, prependEmoji } from '../../util/emoji';
import { isKnownError } from '../../util/env/known-error';
import { getCommandName } from '../../util/pkg-name';
import { SYSTEM_ENV_VALUES } from '../../util/env/system-env';
type Options = {
'--debug': boolean;
@@ -31,71 +29,38 @@ export default async function add(
args: string[],
output: Output
) {
// improve the way we show inquirer prompts
require('../../util/input/patch-inquirer');
const stdInput = await readStandardInput();
let [envTypeArg, envName, envTargetArg] = args;
let [envName, envTarget] = args;
if (args.length > 3) {
if (args.length > 2) {
output.error(
`Invalid number of arguments. Usage: ${getCommandName(
`env add ${getEnvTypePlaceholder()} <name> ${getEnvTargetPlaceholder()}`
`env add <name> ${getEnvTargetPlaceholder()}`
)}`
);
return 1;
}
if (stdInput && (!envTypeArg || !envName || !envTargetArg)) {
if (stdInput && (!envName || !envTarget)) {
output.error(
`Invalid number of arguments. Usage: ${getCommandName(
`env add ${getEnvTypePlaceholder()} <name> <target> < <file>`
`env add <name> <target> < <file>`
)}`
);
return 1;
}
let envTargets: ProjectEnvTarget[] = [];
if (envTargetArg) {
if (!isValidEnvTarget(envTargetArg)) {
if (envTarget) {
if (!isValidEnvTarget(envTarget)) {
output.error(
`The Environment ${param(
envTargetArg
envTarget
)} is invalid. It must be one of: ${getEnvTargetPlaceholder()}.`
);
return 1;
}
envTargets.push(envTargetArg);
}
let envType: ProjectEnvType;
if (envTypeArg) {
if (!isValidEnvType(envTypeArg)) {
output.error(
`The Environment Variable type ${param(
envTypeArg
)} is invalid. It must be one of: ${getEnvTypePlaceholder()}.`
);
return 1;
}
envType = envTypeArg;
} else {
const answers = (await inquirer.prompt({
name: 'inputEnvType',
type: 'list',
message: `Which type of Environment Variable do you want to add?`,
choices: [
{ name: 'Plaintext', value: ProjectEnvType.Plaintext },
{
name: `Secret (can be created using ${getCommandName('secret add')})`,
value: ProjectEnvType.Secret,
},
{ name: 'Provided by System', value: ProjectEnvType.System },
],
})) as { inputEnvType: ProjectEnvType };
envType = answers.inputEnvType;
envTargets.push(envTarget);
}
while (!envName) {
@@ -133,59 +98,15 @@ export default async function add(
if (stdInput) {
envValue = stdInput;
} else if (envType === ProjectEnvType.Plaintext) {
} else if (isSystemEnvVariable(envName)) {
envValue = '';
} else {
const { inputValue } = await inquirer.prompt({
type: 'input',
type: 'password',
name: 'inputValue',
message: `Whats the value of ${envName}?`,
});
envValue = inputValue || '';
} else if (envType === ProjectEnvType.Secret) {
let secretId: string | null = null;
while (!secretId) {
let { secretName } = await inquirer.prompt({
type: 'input',
name: 'secretName',
message: `Whats the value of ${envName}?`,
});
secretName = secretName || '';
if (secretName[0] === '@') {
secretName = secretName.slice(1);
}
try {
const secret = await client.fetch<Secret>(
`/v2/now/secrets/${encodeURIComponent(secretName)}`
);
secretId = secret.uid;
} catch (error) {
if (error.status === 404) {
output.error(
`Please enter the name of an existing Secret (can be created with ${getCommandName(
'secret add'
)}).`
);
} else {
throw error;
}
}
}
envValue = secretId;
} else {
const { systemEnvValue } = await inquirer.prompt({
name: 'systemEnvValue',
type: 'list',
message: `Whats the value of ${envName}?`,
choices: SYSTEM_ENV_VALUES.map(value => ({ name: value, value })),
});
envValue = systemEnvValue;
}
while (envTargets.length === 0) {
@@ -206,15 +127,7 @@ export default async function add(
const addStamp = stamp();
try {
await withSpinner('Saving', () =>
addEnvRecord(
output,
client,
project.id,
envType,
envName,
envValue,
envTargets
)
addEnvRecord(output, client, project.id, envName, envValue, envTargets)
);
} catch (error) {
if (isKnownError(error) && error.serverMessage) {
@@ -235,3 +148,7 @@ export default async function add(
return 0;
}
function isSystemEnvVariable(envName: string) {
return envName.startsWith('VERCEL_');
}

View File

@@ -6,7 +6,6 @@ import getArgs from '../../util/get-args';
import getSubcommand from '../../util/get-subcommand';
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
import { getEnvTargetPlaceholder } from '../../util/env/env-target';
import { getEnvTypePlaceholder } from '../../util/env/env-type';
import { getLinkedProject } from '../../util/projects/link';
import Client from '../../util/client';
import handleError from '../../util/handle-error';
@@ -19,17 +18,16 @@ import ls from './ls';
import rm from './rm';
const help = () => {
const typePlaceholder = getEnvTypePlaceholder();
const targetPlaceholder = getEnvTargetPlaceholder();
const placeholder = getEnvTargetPlaceholder();
console.log(`
${chalk.bold(`${logo} ${getPkgName()} env`)} [options] <command>
${chalk.dim('Commands:')}
ls [environment] List all variables for the specified Environment
add [type] [name] [environment] Add an Environment Variable (see examples below)
rm [name] [environment] Remove an Environment Variable (see examples below)
pull [filename] Pull all Development Environment Variables from the cloud and write to a file [.env]
ls [environment] List all variables for the specified Environment
add [name] [environment] Add an Environment Variable (see examples below)
rm [name] [environment] Remove an Environment Variable (see examples below)
pull [filename] Pull all Development Environment Variables from the cloud and write to a file [.env]
${chalk.dim('Options:')}
@@ -49,27 +47,21 @@ const help = () => {
${chalk.gray('')} Add a new variable to multiple Environments
${chalk.cyan(`$ ${getPkgName()} env add ${typePlaceholder} <name>`)}
${chalk.cyan(`$ ${getPkgName()} env add secret API_TOKEN`)}
${chalk.cyan(`$ ${getPkgName()} env add <name>`)}
${chalk.cyan(`$ ${getPkgName()} env add API_TOKEN`)}
${chalk.gray('')} Add a new variable for a specific Environment
${chalk.cyan(
`$ ${getPkgName()} env add ${typePlaceholder} <name> ${targetPlaceholder}`
)}
${chalk.cyan(`$ ${getPkgName()} env add secret DB_PASS production`)}
${chalk.cyan(`$ ${getPkgName()} env add <name> ${placeholder}`)}
${chalk.cyan(`$ ${getPkgName()} env add DB_CONNECTION production`)}
${chalk.gray('')} Add a new Environment Variable from stdin
${chalk.cyan(
`$ cat <file> | ${getPkgName()} env add ${typePlaceholder} <name> ${targetPlaceholder}`
)}
${chalk.cyan(
`$ cat ~/.npmrc | ${getPkgName()} env add plain NPM_RC preview`
)}
${chalk.cyan(
`$ ${getPkgName()} env add plain API_URL production < url.txt`
`$ cat <file> | ${getPkgName()} env add <name> ${placeholder}`
)}
${chalk.cyan(`$ cat ~/.npmrc | ${getPkgName()} env add NPM_RC preview`)}
${chalk.cyan(`$ ${getPkgName()} env add DB_PASS production < secret.txt`)}
${chalk.gray('')} Remove an variable from multiple Environments
@@ -78,7 +70,7 @@ const help = () => {
${chalk.gray('')} Remove a variable from a specific Environment
${chalk.cyan(`$ ${getPkgName()} env rm <name> ${targetPlaceholder}`)}
${chalk.cyan(`$ ${getPkgName()} env rm <name> ${placeholder}`)}
${chalk.cyan(`$ ${getPkgName()} env rm NPM_RC preview`)}
`);
};

View File

@@ -1,12 +1,7 @@
import chalk from 'chalk';
import ms from 'ms';
import { Output } from '../../util/output';
import {
ProjectEnvTarget,
Project,
ProjectEnvVariable,
ProjectEnvType,
} from '../../types';
import { ProjectEnvTarget, Project, ProjectEnvVariableV5 } from '../../types';
import Client from '../../util/client';
import formatTable from '../../util/format-table';
import getEnvVariables from '../../util/env/get-env-records';
@@ -18,8 +13,6 @@ import stamp from '../../util/output/stamp';
import param from '../../util/output/param';
import { getCommandName } from '../../util/pkg-name';
import ellipsis from '../../util/output/ellipsis';
// @ts-ignore
import title from 'title';
type Options = {
'--debug': boolean;
@@ -54,7 +47,19 @@ export default async function ls(
const lsStamp = stamp();
const { envs } = await getEnvVariables(output, client, project.id, envTarget);
const data = await getEnvVariables(output, client, project.id, envTarget);
// we expand env vars with multiple targets
const envs: ProjectEnvVariableV5[] = [];
for (let env of data.envs) {
if (Array.isArray(env.target)) {
for (let target of env.target) {
envs.push({ ...env, target });
}
} else {
envs.push({ ...env, target: env.target });
}
}
output.log(
`${
@@ -66,9 +71,9 @@ export default async function ls(
return 0;
}
function getTable(records: ProjectEnvVariable[]) {
function getTable(records: ProjectEnvVariableV5[]) {
return formatTable(
['name', 'value', 'environments', 'created'],
['name', 'value', 'environment', 'created'],
['l', 'l', 'l', 'l', 'l'],
[
{
@@ -79,16 +84,16 @@ function getTable(records: ProjectEnvVariable[]) {
);
}
function getRow(env: ProjectEnvVariable) {
function getRow(env: ProjectEnvVariableV5) {
let value: string;
if (env.type === ProjectEnvType.Plaintext) {
if (env.type === 'plain') {
// replace space characters (line-break, etc.) with simple spaces
// to make sure the displayed value is a single line
const singleLineValue = env.value.replace(/\s/g, ' ');
value = chalk.gray(ellipsis(singleLineValue, 19));
} else if (env.type === ProjectEnvType.System) {
value = chalk.gray.italic(env.value);
} else if (env.type === 'system') {
value = chalk.gray.italic('Populated by System');
} else {
value = chalk.gray.italic('Encrypted');
}
@@ -97,9 +102,7 @@ function getRow(env: ProjectEnvVariable) {
return [
chalk.bold(env.key),
value,
(Array.isArray(env.target) ? env.target : [env.target || ''])
.map(title)
.join(', '),
env.target || '',
env.createdAt ? `${ms(now - env.createdAt)} ago` : '',
];
}

View File

@@ -30,9 +30,6 @@ export default async function rm(
args: string[],
output: Output
) {
// improve the way we show inquirer prompts
require('../../util/input/patch-inquirer');
if (args.length > 2) {
output.error(
`Invalid number of arguments. Usage: ${getCommandName(

View File

@@ -203,16 +203,12 @@ export enum ProjectEnvTarget {
Development = 'development',
}
export enum ProjectEnvType {
Plaintext = 'plain',
Secret = 'secret',
System = 'system',
}
export type ProjectEnvVariableType = 'system' | 'secret' | 'plain';
export interface ProjectEnvVariable {
key: string;
value: string;
type: ProjectEnvType;
type: ProjectEnvVariableType;
configurationId?: string | null;
createdAt?: number;
updatedAt?: number;

View File

@@ -1,39 +1,60 @@
import { Output } from '../output';
import Client from '../client';
import {
Secret,
ProjectEnvTarget,
ProjectEnvVariableV5,
ProjectEnvType,
} from '../../types';
import { Secret, ProjectEnvTarget, ProjectEnvVariableV5 } from '../../types';
import { customAlphabet } from 'nanoid';
import slugify from '@sindresorhus/slugify';
export default async function addEnvRecord(
output: Output,
client: Client,
projectId: string,
type: ProjectEnvType,
key: string,
envValue: string,
envName: string,
envValue: string | undefined,
targets: ProjectEnvTarget[]
): Promise<void> {
output.debug(
`Adding ${type} Environment Variable ${key} to ${targets.length} targets`
`Adding Environment Variable ${envName} to ${targets.length} targets`
);
let value = envValue;
let values: string[] | undefined;
if (type === ProjectEnvType.Secret) {
const secret = await client.fetch<Secret>(
`/v2/now/secrets/${encodeURIComponent(envValue)}`
if (envValue) {
const secrets = await Promise.all(
targets.map(target =>
client.fetch<Secret>('/v2/now/secrets', {
method: 'POST',
body: JSON.stringify({
name: generateSecretName(envName, target),
value: envValue,
projectId: projectId,
decryptable: target === ProjectEnvTarget.Development,
}),
})
)
);
value = secret.uid;
values = secrets.map(secret => secret.uid);
}
const body = { type, key, value, target: targets };
const body = targets.map((target, i) => ({
key: envName,
value: values ? values[i] : '',
target,
}));
const urlProject = `/v6/projects/${projectId}/env`;
const urlProject = `/v4/projects/${projectId}/env`;
await client.fetch<ProjectEnvVariableV5>(urlProject, {
method: 'POST',
body: JSON.stringify(body),
});
}
const randomSecretSuffix = customAlphabet(
'123456789abcdefghijklmnopqrstuvwxyz',
4
);
function generateSecretName(envName: string, target: ProjectEnvTarget) {
return `${
slugify(envName).substring(0, 80) // we truncate because the max secret length is 100
}-${target}-${randomSecretSuffix()}`;
}

View File

@@ -1,15 +0,0 @@
import { ProjectEnvType } from '../../types';
function envTypes(): string[] {
return Object.values(ProjectEnvType);
}
export function isValidEnvType(
type?: string
): type is ProjectEnvType | undefined {
return typeof type === 'undefined' || envTypes().includes(type);
}
export function getEnvTypePlaceholder() {
return `<${envTypes().join(' | ')}>`;
}

View File

@@ -1,32 +0,0 @@
export const SYSTEM_ENV_VALUES = [
'VERCEL_URL',
'VERCEL_GITHUB_COMMIT_ORG',
'VERCEL_GITHUB_COMMIT_REF',
'VERCEL_GITHUB_ORG',
'VERCEL_GITHUB_DEPLOYMENT',
'VERCEL_GITHUB_COMMIT_REPO',
'VERCEL_GITHUB_REPO',
'VERCEL_GITHUB_COMMIT_AUTHOR_LOGIN',
'VERCEL_GITHUB_COMMIT_AUTHOR_NAME',
'VERCEL_GITHUB_COMMIT_SHA',
'VERCEL_GITLAB_DEPLOYMENT',
'VERCEL_GITLAB_PROJECT_NAMESPACE',
'VERCEL_GITLAB_PROJECT_NAME',
'VERCEL_GITLAB_PROJECT_ID',
'VERCEL_GITLAB_PROJECT_PATH',
'VERCEL_GITLAB_COMMIT_REF',
'VERCEL_GITLAB_COMMIT_SHA',
'VERCEL_GITLAB_COMMIT_MESSAGE',
'VERCEL_GITLAB_COMMIT_AUTHOR_LOGIN',
'VERCEL_GITLAB_COMMIT_AUTHOR_NAME',
'VERCEL_BITBUCKET_DEPLOYMENT',
'VERCEL_BITBUCKET_REPO_OWNER',
'VERCEL_BITBUCKET_REPO_SLUG',
'VERCEL_BITBUCKET_REPO_NAME',
'VERCEL_BITBUCKET_COMMIT_REF',
'VERCEL_BITBUCKET_COMMIT_SHA',
'VERCEL_BITBUCKET_COMMIT_MESSAGE',
'VERCEL_BITBUCKET_COMMIT_AUTHOR_NAME',
'VERCEL_BITBUCKET_COMMIT_AUTHOR_URL',
'VERCEL_BITBUCKET_COMMIT_AUTHOR_AVATAR',
];

View File

@@ -2,7 +2,7 @@ import getEnvVariables from './env/get-env-records';
import getDecryptedSecret from './env/get-decrypted-secret';
import Client from './client';
import { Output } from './output/create-output';
import { ProjectEnvTarget, Project, ProjectEnvType } from '../types';
import { ProjectEnvTarget, Project } from '../types';
import { Env } from '@vercel/build-utils';
@@ -15,9 +15,9 @@ export default async function getDecryptedEnvRecords(
const { envs } = await getEnvVariables(output, client, project.id, target);
const decryptedValues = await Promise.all(
envs.map(async env => {
if (env.type === ProjectEnvType.System) {
if (env.type === 'system') {
return { value: '', found: true };
} else if (env.type === ProjectEnvType.Plaintext) {
} else if (env.type === 'plain') {
return { value: env.value, found: true };
}

View File

@@ -10,7 +10,7 @@ import chalk from 'chalk';
*/
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/base.js#L126
const getQuestion = function () {
const getQuestion = function() {
let message = `${chalk.gray('?')} ${this.opt.message} `;
if (this.opt.type === 'confirm') {
@@ -35,7 +35,7 @@ inquirer.prompt.prompts.input.prototype.getQuestion = getQuestion;
inquirer.prompt.prompts.confirm.prototype.getQuestion = getQuestion;
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/list.js#L80
inquirer.prompt.prompts.list.prototype.render = function () {
inquirer.prompt.prompts.list.prototype.render = function() {
// Render question
let message = this.getQuestion();
@@ -89,22 +89,11 @@ function listRender(choices, pointer) {
}
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/checkbox.js#L84
inquirer.prompt.prompts.checkbox.prototype.render = function (error) {
inquirer.prompt.prompts.checkbox.prototype.render = function(error) {
// Render question
let message = this.getQuestion();
let bottomContent = '';
if (!this.spaceKeyPressed) {
message +=
'(Press ' +
chalk.cyan.bold('<space>') +
' to select, ' +
chalk.cyan.bold('<a>') +
' to toggle all, ' +
chalk.cyan.bold('<i>') +
' to invert selection)';
}
// Render choices or answer depending on the state
if (this.status === 'answered') {
message += this.selection.length > 0 ? this.selection.join(', ') : 'None';
@@ -129,7 +118,7 @@ function renderChoices(choices, pointer) {
let output = '';
let separatorOffset = 0;
choices.forEach(function (choice, i) {
choices.forEach(function(choice, i) {
if (choice.type === 'separator') {
separatorOffset++;
output += '' + choice + '\n';
@@ -162,7 +151,7 @@ function renderChoices(choices, pointer) {
}
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/input.js#L44
inquirer.prompt.prompts.input.prototype.render = function (error) {
inquirer.prompt.prompts.input.prototype.render = function(error) {
let bottomContent = '';
let appendContent = '';
let message = this.getQuestion();
@@ -189,7 +178,7 @@ inquirer.prompt.prompts.input.prototype.render = function (error) {
};
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/confirm.js#L64
inquirer.prompt.prompts.confirm.prototype.render = function (answer) {
inquirer.prompt.prompts.confirm.prototype.render = function(answer) {
let message = this.getQuestion();
if (this.status === 'answered') {

View File

@@ -422,17 +422,32 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
}
async function createSecret() {
const name = `my-secret${Math.floor(Math.random() * 10000)}`;
function withPlainTextEnv(fn) {
return async function (...args) {
const link = require(path.join(target, '.vercel/project.json'));
const postRes = await apiFetch(`/v6/projects/${link.projectId}/env`, {
method: 'POST',
body: JSON.stringify({
type: 'plain',
key: 'MY_PLAIN_VAR',
value: 'hello',
target: ['development'],
}),
});
t.is(postRes.status, 200);
const res = await apiFetch('/v2/now/secrets', {
method: 'POST',
body: JSON.stringify({ name, value: 'my secret' }),
});
t.is(res.status, 200);
return name;
try {
return await fn(...args);
} finally {
const deleteRes = await apiFetch(
`/v4/projects/${link.projectId}/env/MY_PLAIN_VAR?target=development`,
{
method: 'DELETE',
}
);
t.is(deleteRes.status, 200);
}
};
}
async function nowEnvLsIsEmpty() {
@@ -449,34 +464,26 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
t.regex(stderr, /No Environment Variables found in Project/gm);
}
async function nowEnvAddPlaintext() {
async function nowEnvAdd() {
const now = execa(binaryPath, ['env', 'add', ...defaultArgs], {
reject: false,
cwd: target,
});
await waitForPrompt(now, chunk =>
chunk.includes('Which type of Environment Variable do you want to add?')
);
now.stdin.write('\n'); // select plaintext
await waitForPrompt(now, chunk =>
chunk.includes('Whats the name of the variable?')
);
now.stdin.write('MY_PLAINTEXT_ENV_VAR\n');
now.stdin.write('MY_ENV_VAR\n');
await waitForPrompt(
now,
chunk =>
chunk.includes('Whats the value of') &&
chunk.includes('MY_PLAINTEXT_ENV_VAR')
chunk.includes('Whats the value of') && chunk.includes('MY_ENV_VAR')
);
now.stdin.write('my plaintext value\n');
now.stdin.write('MY_VALUE\n');
await waitForPrompt(
now,
chunk =>
chunk.includes('which Environments') &&
chunk.includes('MY_PLAINTEXT_ENV_VAR')
chunk.includes('which Environments') && chunk.includes('MY_ENV_VAR')
);
now.stdin.write('a\n'); // select all
@@ -485,47 +492,10 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
}
async function nowEnvAddSecret(secretName) {
const now = execa(binaryPath, ['env', 'add', ...defaultArgs], {
reject: false,
cwd: target,
});
await waitForPrompt(now, chunk =>
chunk.includes('Which type of Environment Variable do you want to add?')
);
now.stdin.write('j\n'); // select secret
await waitForPrompt(now, chunk =>
chunk.includes('Whats the name of the variable?')
);
now.stdin.write('MY_SECRET_ENV_VAR\n');
await waitForPrompt(
now,
chunk =>
chunk.includes('Whats the value of') &&
chunk.includes('MY_SECRET_ENV_VAR')
);
now.stdin.write(`@${secretName}\n`);
await waitForPrompt(
now,
chunk =>
chunk.includes('which Environments') &&
chunk.includes('MY_SECRET_ENV_VAR')
);
now.stdin.write('j \n'); // select preview
const { exitCode, stderr, stdout } = await now;
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
}
async function nowEnvAddFromStdin() {
const now = execa(
binaryPath,
['env', 'add', 'plain', 'MY_STDIN_VAR', 'development', ...defaultArgs],
['env', 'add', 'MY_STDIN_VAR', 'development', ...defaultArgs],
{
reject: false,
cwd: target,
@@ -539,20 +509,13 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
async function nowEnvAddSystemEnv() {
const now = execa(
binaryPath,
['env', 'add', 'system', 'VERCEL_URL', ...defaultArgs],
['env', 'add', 'VERCEL_URL', ...defaultArgs],
{
reject: false,
cwd: target,
}
);
await waitForPrompt(
now,
chunk =>
chunk.includes('Whats the value of') && chunk.includes('VERCEL_URL')
);
now.stdin.write(`\n`); // select VERCEL_URL
await waitForPrompt(
now,
chunk =>
@@ -578,28 +541,28 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
t.regex(stderr, /Environment Variables found in Project/gm);
console.log(stdout);
const lines = stdout.split('\n');
const plaintextEnvs = lines.filter(line =>
line.includes('MY_PLAINTEXT_ENV_VAR')
);
t.is(plaintextEnvs.length, 1);
t.regex(plaintextEnvs[0], /Production, Preview, Development/gm);
const myEnvVars = lines.filter(line => line.includes('MY_ENV_VAR'));
t.is(myEnvVars.length, 3);
t.regex(myEnvVars.join('\n'), /development/gm);
t.regex(myEnvVars.join('\n'), /preview/gm);
t.regex(myEnvVars.join('\n'), /production/gm);
const secretEnvs = lines.filter(line => line.includes('MY_SECRET_ENV_VAR'));
t.is(secretEnvs.length, 1);
t.regex(secretEnvs[0], /Preview/gm);
const myStdinVars = lines.filter(line => line.includes('MY_STDIN_VAR'));
t.is(myStdinVars.length, 1);
t.regex(myStdinVars.join('\n'), /development/gm);
const stdinEnvs = lines.filter(line => line.includes('MY_STDIN_VAR'));
t.is(stdinEnvs.length, 1);
t.regex(stdinEnvs[0], /Development/gm);
const vercelVars = lines.filter(line => line.includes('VERCEL_URL'));
t.is(vercelVars.length, 3);
t.regex(vercelVars.join('\n'), /development/gm);
t.regex(vercelVars.join('\n'), /preview/gm);
t.regex(vercelVars.join('\n'), /production/gm);
const systemEnvs = lines.filter(line => line.includes('VERCEL_URL'));
t.is(systemEnvs.length, 1);
t.regex(systemEnvs[0], /VERCEL_URL/gm);
t.regex(systemEnvs[0], /Production, Preview, Development/gm);
const myPlainVars = lines.filter(line => line.includes('MY_PLAIN_VAR'));
t.is(myPlainVars.length, 1);
t.regex(myPlainVars.join('\n'), /development/gm);
t.regex(myPlainVars.join('\n'), /hello/gm);
}
async function nowEnvPull() {
@@ -619,9 +582,10 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
t.true(contents.startsWith('# Created by Vercel CLI\n'));
const lines = new Set(contents.split('\n'));
t.true(lines.has('MY_PLAINTEXT_ENV_VAR="my plaintext value"'));
t.true(lines.has('MY_ENV_VAR="MY_VALUE"'));
t.true(lines.has('MY_STDIN_VAR="{"expect":"quotes"}"'));
t.true(lines.has('VERCEL_URL=""'));
t.true(lines.has('MY_PLAIN_VAR="hello"'));
}
async function nowEnvPullOverwrite() {
@@ -673,8 +637,7 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
const apiRes = await fetch(apiUrl);
t.is(apiRes.status, 200, formatOutput({ stderr, stdout }));
const apiJson = await apiRes.json();
t.is(apiJson['MY_PLAINTEXT_ENV_VAR'], 'my plaintext value');
t.is(apiJson['MY_SECRET_ENV_VAR'], 'my secret');
t.is(apiJson['MY_ENV_VAR'], 'MY_VALUE');
t.is(apiJson['VERCEL_URL'], host);
const homeUrl = `https://${host}`;
@@ -682,8 +645,7 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
const homeRes = await fetch(homeUrl);
t.is(homeRes.status, 200, formatOutput({ stderr, stdout }));
const homeJson = await homeRes.json();
t.is(homeJson['MY_PLAINTEXT_ENV_VAR'], 'my plaintext value');
t.is(homeJson['MY_SECRET_ENV_VAR'], 'my secret');
t.is(homeJson['MY_ENV_VAR'], 'MY_VALUE');
t.is(homeJson['VERCEL_URL'], host);
}
@@ -711,14 +673,14 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
const apiJson = await apiRes.json();
t.is(apiJson['MY_PLAINTEXT_ENV_VAR'], 'my plaintext value');
t.is(apiJson['MY_ENV_VAR'], 'MY_VALUE');
t.is(apiJson['VERCEL_URL'], localhostNoProtocol);
const homeUrl = localhost[0];
const homeRes = await fetch(homeUrl);
const homeJson = await homeRes.json();
t.is(homeJson['MY_PLAINTEXT_ENV_VAR'], 'my plaintext value');
t.is(homeJson['MY_ENV_VAR'], 'MY_VALUE');
t.is(homeJson['VERCEL_URL'], localhostNoProtocol);
vc.kill('SIGTERM', { forceKillAfterTimeout: 2000 });
@@ -752,15 +714,15 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
const apiJson = await apiRes.json();
t.is(apiJson['VERCEL_URL'], localhostNoProtocol);
t.is(apiJson['MY_PLAINTEXT_ENV_VAR'], 'my plaintext value');
t.is(apiJson['MY_STDIN_VAR'], '{"expect":"quotes"}');
t.is(apiJson['MY_ENV_VAR'], 'MY_VALUE');
t.is(apiJson['MY_PLAIN_VAR'], 'hello');
const homeUrl = localhost[0];
const homeRes = await fetch(homeUrl);
const homeJson = await homeRes.json();
t.is(homeJson['MY_PLAINTEXT_ENV_VAR'], 'my plaintext value');
t.is(homeJson['MY_ENV_VAR'], 'MY_VALUE');
t.is(homeJson['VERCEL_URL'], localhostNoProtocol);
t.is(homeJson['MY_STDIN_VAR'], '{"expect":"quotes"}');
t.is(homeJson['MY_PLAIN_VAR'], 'hello');
vc.kill('SIGTERM', { forceKillAfterTimeout: 2000 });
@@ -776,14 +738,13 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
await waitForPrompt(now, chunk =>
chunk.includes('Whats the name of the variable?')
);
now.stdin.write('MY_PLAINTEXT_ENV_VAR\n');
now.stdin.write('MY_ENV_VAR\n');
// expect error if no environment is selected
await waitForPrompt(
now,
chunk =>
chunk.includes('which Environments') &&
chunk.includes('MY_PLAINTEXT_ENV_VAR')
chunk.includes('which Environments') && chunk.includes('MY_ENV_VAR')
);
now.stdin.write('\n'); // select none
await waitForPrompt(now, chunk =>
@@ -795,8 +756,7 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
await waitForPrompt(
now,
chunk =>
chunk.includes('which Environments') &&
chunk.includes('MY_PLAINTEXT_ENV_VAR')
chunk.includes('which Environments') && chunk.includes('MY_ENV_VAR')
);
now.stdin.write('a\n'); // select all
@@ -807,21 +767,6 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
async function nowEnvRemoveWithArgs() {
const { exitCode, stderr, stdout } = await execa(
binaryPath,
['env', 'rm', 'MY_SECRET_ENV_VAR', 'preview', '-y', ...defaultArgs],
{
reject: false,
cwd: target,
}
);
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
const {
exitCode: exitCode2,
stderr: stderr2,
stdout: stdout2,
} = await execa(
binaryPath,
['env', 'rm', 'MY_STDIN_VAR', 'development', '-y', ...defaultArgs],
{
@@ -830,7 +775,7 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
}
);
t.is(exitCode2, 0, formatOutput({ stderr2, stdout2 }));
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
}
async function nowEnvRemoveWithNameOnly() {
@@ -855,20 +800,18 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
}
await nowDeploy();
const secretName = await createSecret();
await nowEnvLsIsEmpty();
await nowEnvAddPlaintext();
await nowEnvAddSecret(secretName);
await nowEnvAdd();
await nowEnvAddFromStdin();
await nowEnvAddSystemEnv();
await nowEnvLsIncludesVar();
await nowEnvPull();
await withPlainTextEnv(nowEnvLsIncludesVar)();
await withPlainTextEnv(nowEnvPull)();
await nowEnvPullOverwrite();
await nowEnvPullConfirm();
await nowDeployWithVar();
await nowDevWithEnv();
fs.unlinkSync(path.join(target, '.env'));
await nowDevAndFetchCloudVars();
await withPlainTextEnv(nowDevAndFetchCloudVars)();
await nowEnvRemove();
await nowEnvRemoveWithArgs();
await nowEnvRemoveWithNameOnly();

View File

@@ -8257,6 +8257,11 @@ nan@^2.12.1:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
nanoid@3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.0.2.tgz#37e91e6f5277ce22335be473e2a5db1bd96dd026"
integrity sha512-WOjyy/xu3199NlQiQWlx7VbspSFlGtOxa1bRX9ebmXOnp1fje4bJfjPs1wLQ8jZbJUfD+yceJmw879ZSaVJkdQ==
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"