mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-09 21:07:46 +00:00
https://vercel.com/blog/zeit-is-now-vercel * Updates all org packages from `@now` to `@vercel` * Updates Now CLI package name from `now` to `vercel` * Packages contains `"bin"` entries for _both_ `vercel` and `now` in the package.json * Updates `now-client` package name to `@vercel/client` (org scoped, for authenticity) There is also a new `publish-legacy.sh` script which ensures that all the legacy package names (i.e. `now`, `now-client`, `@now/node`, etc.) will still be published as well. We will remove this legacy publishing logic on Jan 1, 2021.
2754 lines
69 KiB
JavaScript
Vendored
2754 lines
69 KiB
JavaScript
Vendored
import ms from 'ms';
|
||
import path from 'path';
|
||
import { URL } from 'url';
|
||
import test from 'ava';
|
||
import semVer from 'semver';
|
||
import { homedir } from 'os';
|
||
import _execa from 'execa';
|
||
import XDGAppPaths from 'xdg-app-paths';
|
||
import fetch from 'node-fetch';
|
||
import tmp from 'tmp-promise';
|
||
import retry from 'async-retry';
|
||
import fs, {
|
||
writeFile,
|
||
readFile,
|
||
remove,
|
||
copy,
|
||
ensureDir,
|
||
exists,
|
||
} from 'fs-extra';
|
||
import logo from '../src/util/output/logo';
|
||
import sleep from '../src/util/sleep';
|
||
import pkg from '../package';
|
||
import prepareFixtures from './helpers/prepare';
|
||
import { fetchTokenWithRetry } from '../../../test/lib/deployment/now-deploy';
|
||
|
||
// log command when running `execa`
|
||
function execa(file, args, options) {
|
||
console.log(`$ now ${args.join(' ')}`);
|
||
return _execa(file, args, options);
|
||
}
|
||
|
||
const binaryPath = path.resolve(__dirname, `../scripts/start.js`);
|
||
const fixture = name => path.join(__dirname, 'fixtures', 'integration', name);
|
||
const deployHelpMessage = `${logo} vercel [options] <command | path>`;
|
||
let session = 'temp-session';
|
||
|
||
const isCanary = pkg.version.includes('canary');
|
||
|
||
const pickUrl = stdout => {
|
||
const lines = stdout.split('\n');
|
||
return lines[lines.length - 1];
|
||
};
|
||
|
||
const createFile = dest => fs.closeSync(fs.openSync(dest, 'w'));
|
||
|
||
const waitForDeployment = async href => {
|
||
console.log(`waiting for ${href} to become ready...`);
|
||
const start = Date.now();
|
||
const max = ms('4m');
|
||
const inspectorText = '<title>Deployment Overview';
|
||
|
||
// eslint-disable-next-line
|
||
while (true) {
|
||
const response = await fetch(href, { redirect: 'manual' });
|
||
const text = await response.text();
|
||
if (response.status === 200 && !text.includes(inspectorText)) {
|
||
break;
|
||
}
|
||
|
||
const current = Date.now();
|
||
|
||
if (current - start > max || response.status >= 500) {
|
||
throw new Error(
|
||
`Waiting for "${href}" failed since it took longer than 4 minutes.\n` +
|
||
`Received status ${response.status}:\n"${text}"`
|
||
);
|
||
}
|
||
|
||
await sleep(2000);
|
||
}
|
||
};
|
||
|
||
function fetchTokenInformation(token, retries = 3) {
|
||
const url = `https://api.vercel.com/www/user`;
|
||
const headers = { Authorization: `Bearer ${token}` };
|
||
|
||
return retry(
|
||
async () => {
|
||
const res = await fetch(url, { headers });
|
||
|
||
if (!res.ok) {
|
||
throw new Error(
|
||
`Failed to fetch ${url}, received status ${res.status}`
|
||
);
|
||
}
|
||
|
||
const data = await res.json();
|
||
|
||
return data.user;
|
||
},
|
||
{ retries, factor: 1 }
|
||
);
|
||
}
|
||
|
||
function formatOutput({ stderr, stdout }) {
|
||
return `Received:\n"${stderr}"\n"${stdout}"`;
|
||
}
|
||
|
||
// AVA's `t.context` can only be set before the tests,
|
||
// but we want to set it within as well
|
||
const context = {};
|
||
|
||
const defaultOptions = { reject: false };
|
||
const defaultArgs = [];
|
||
let token;
|
||
let email;
|
||
let contextName;
|
||
|
||
let tmpDir;
|
||
|
||
let globalDir = XDGAppPaths('com.vercel.cli').dataDirs()[0];
|
||
|
||
if (!process.env.CI) {
|
||
tmpDir = tmp.dirSync({
|
||
// This ensures the directory gets
|
||
// deleted even if it has contents
|
||
unsafeCleanup: true,
|
||
});
|
||
|
||
globalDir = path.join(tmpDir.name, 'com.vercel.tests');
|
||
|
||
defaultArgs.push('-Q', globalDir);
|
||
console.log(
|
||
'No CI detected, adding defaultArgs to avoid polluting user settings',
|
||
defaultArgs
|
||
);
|
||
}
|
||
|
||
const execute = (args, options) =>
|
||
execa(binaryPath, [...defaultArgs, ...args], {
|
||
...defaultOptions,
|
||
...options,
|
||
});
|
||
|
||
const apiFetch = (url, { headers, ...options } = {}) => {
|
||
return fetch(`https://api.vercel.com${url}`, {
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
...(headers || {}),
|
||
},
|
||
...options,
|
||
});
|
||
};
|
||
|
||
const waitForPrompt = (cp, assertion) =>
|
||
new Promise((resolve, reject) => {
|
||
console.log('Waiting for prompt...');
|
||
setTimeout(() => reject(new Error('timeout in waitForPrompt')), 60000);
|
||
const listener = chunk => {
|
||
console.log('> ' + chunk);
|
||
if (assertion(chunk)) {
|
||
cp.stdout.off && cp.stdout.off('data', listener);
|
||
cp.stderr.off && cp.stderr.off('data', listener);
|
||
resolve();
|
||
}
|
||
};
|
||
|
||
cp.stdout.on('data', listener);
|
||
cp.stderr.on('data', listener);
|
||
});
|
||
|
||
const getDeploymentBuildsByUrl = async url => {
|
||
const hostRes = await apiFetch(`/v10/now/deployments/get?url=${url}`);
|
||
const { id } = await hostRes.json();
|
||
const buildsRes = await apiFetch(`/v10/now/deployments/${id}/builds`);
|
||
const { builds } = await buildsRes.json();
|
||
return builds;
|
||
};
|
||
|
||
const createUser = async () => {
|
||
await retry(
|
||
async () => {
|
||
if (!fs.existsSync(globalDir)) {
|
||
console.log('Creating global config directory ', globalDir);
|
||
await ensureDir(globalDir);
|
||
} else {
|
||
console.log('Found global config directory ', globalDir);
|
||
}
|
||
|
||
token = await fetchTokenWithRetry();
|
||
|
||
await fs.writeJSON(getConfigAuthPath(), { token });
|
||
|
||
const user = await fetchTokenInformation(token);
|
||
|
||
email = user.email;
|
||
contextName = user.email.split('@')[0];
|
||
session = Math.random()
|
||
.toString(36)
|
||
.split('.')[1];
|
||
},
|
||
{ retries: 3, factor: 1 }
|
||
);
|
||
};
|
||
|
||
const getConfigAuthPath = () => path.join(globalDir, 'auth.json');
|
||
|
||
test.before(async () => {
|
||
try {
|
||
await createUser();
|
||
await prepareFixtures(contextName);
|
||
} catch (err) {
|
||
console.log('Failed `test.before`');
|
||
console.log(err);
|
||
}
|
||
});
|
||
|
||
test.after.always(async () => {
|
||
// Make sure the token gets revoked
|
||
await execa(binaryPath, ['logout', ...defaultArgs]);
|
||
|
||
if (!tmpDir) {
|
||
return;
|
||
}
|
||
|
||
// Remove config directory entirely
|
||
tmpDir.removeCallback();
|
||
});
|
||
|
||
test('login', async t => {
|
||
t.timeout(ms('1m'));
|
||
|
||
// Delete the current token
|
||
const logoutOutput = await execute(['logout']);
|
||
t.is(logoutOutput.exitCode, 0, formatOutput(logoutOutput));
|
||
|
||
const loginOutput = await execa(binaryPath, ['login', email, ...defaultArgs]);
|
||
|
||
console.log(loginOutput.stderr);
|
||
console.log(loginOutput.stdout);
|
||
console.log(loginOutput.exitCode);
|
||
|
||
t.is(loginOutput.exitCode, 0, formatOutput(loginOutput));
|
||
t.regex(
|
||
loginOutput.stdout,
|
||
/You are now logged in\./gm,
|
||
formatOutput(loginOutput)
|
||
);
|
||
|
||
// Save the new token
|
||
const auth = await fs.readJSON(getConfigAuthPath());
|
||
|
||
token = auth.token;
|
||
|
||
t.is(typeof token, 'string');
|
||
});
|
||
|
||
test('deploy using only now.json with `redirects` defined', async t => {
|
||
const target = fixture('redirects-v2');
|
||
|
||
const { exitCode, stderr, stdout } = await execa(
|
||
binaryPath,
|
||
[target, ...defaultArgs, '--confirm'],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||
|
||
const url = stdout;
|
||
const res = await fetch(`${url}/foo/bar`, { redirect: 'manual' });
|
||
const location = res.headers.get('location');
|
||
t.is(location, 'https://example.com/foo/bar');
|
||
});
|
||
|
||
test('deploy using --local-config flag v2', async t => {
|
||
const target = fixture('local-config-v2');
|
||
const configPath = path.join(target, 'now-test.json');
|
||
|
||
const { exitCode, stderr, stdout } = await execa(
|
||
binaryPath,
|
||
[
|
||
'deploy',
|
||
target,
|
||
'--local-config',
|
||
configPath,
|
||
...defaultArgs,
|
||
'--confirm',
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||
|
||
const { host } = new URL(stdout);
|
||
t.regex(host, /secondary/gm, `Expected "secondary" but received "${host}"`);
|
||
|
||
const testRes = await fetch(`https://${host}/test-${contextName}.html`);
|
||
const testText = await testRes.text();
|
||
t.is(testText, '<h1>hello test</h1>');
|
||
|
||
const anotherTestRes = await fetch(`https://${host}/another-test`);
|
||
const anotherTestText = await anotherTestRes.text();
|
||
t.is(anotherTestText, testText);
|
||
|
||
const mainRes = await fetch(`https://${host}/main-${contextName}.html`);
|
||
t.is(mainRes.status, 404, 'Should not deploy/build main now.json');
|
||
|
||
const anotherMainRes = await fetch(`https://${host}/another-main`);
|
||
t.is(anotherMainRes.status, 404, 'Should not deploy/build main now.json');
|
||
});
|
||
|
||
test('deploy using --local-config flag above target', async t => {
|
||
const root = fixture('local-config-above-target');
|
||
const target = path.join(root, 'dir');
|
||
|
||
const { exitCode, stderr, stdout } = await execa(
|
||
binaryPath,
|
||
[
|
||
'deploy',
|
||
target,
|
||
'--local-config',
|
||
'./now-root.json',
|
||
...defaultArgs,
|
||
'--confirm',
|
||
],
|
||
{
|
||
cwd: root,
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||
|
||
const { host } = new URL(stdout);
|
||
|
||
const testRes = await fetch(`https://${host}/index.html`);
|
||
const testText = await testRes.text();
|
||
t.is(testText, '<h1>hello index</h1>');
|
||
|
||
const anotherTestRes = await fetch(`https://${host}/another.html`);
|
||
const anotherTestText = await anotherTestRes.text();
|
||
t.is(anotherTestText, '<h1>hello another</h1>');
|
||
|
||
t.regex(host, /root-level/gm, `Expected "root-level" but received "${host}"`);
|
||
});
|
||
|
||
test('Deploy `api-env` fixture and test `now env` command', async t => {
|
||
const target = fixture('api-env');
|
||
|
||
async function nowDeploy() {
|
||
const { exitCode, stderr, stdout } = await execa(
|
||
binaryPath,
|
||
[...defaultArgs, '--confirm'],
|
||
{
|
||
reject: false,
|
||
cwd: target,
|
||
}
|
||
);
|
||
console.log({ stdout });
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||
}
|
||
|
||
async function nowEnvLsIsEmpty() {
|
||
const { exitCode, stderr, stdout } = await execa(
|
||
binaryPath,
|
||
['env', 'ls', ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
cwd: target,
|
||
}
|
||
);
|
||
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||
t.regex(stderr, /No Environment Variables found in Project/gm);
|
||
}
|
||
|
||
async function nowEnvAdd() {
|
||
const now = execa(binaryPath, ['env', 'add', ...defaultArgs], {
|
||
reject: false,
|
||
cwd: target,
|
||
});
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('What’s the name of the variable?')
|
||
);
|
||
now.stdin.write('MY_ENV_VAR\n');
|
||
await waitForPrompt(
|
||
now,
|
||
chunk =>
|
||
chunk.includes('What’s the value of') && chunk.includes('MY_ENV_VAR')
|
||
);
|
||
now.stdin.write('MY_VALUE\n');
|
||
|
||
await waitForPrompt(
|
||
now,
|
||
chunk =>
|
||
chunk.includes('which Environments') && chunk.includes('MY_ENV_VAR')
|
||
);
|
||
now.stdin.write('a\n'); // select all
|
||
|
||
const { exitCode, stderr, stdout } = await now;
|
||
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||
}
|
||
|
||
async function nowEnvAddFromStdin() {
|
||
const now = execa(
|
||
binaryPath,
|
||
['env', 'add', 'MY_STDIN_VAR', 'preview', ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
cwd: target,
|
||
}
|
||
);
|
||
now.stdin.end('MY_STDIN_VALUE');
|
||
const { exitCode, stderr, stdout } = await now;
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||
}
|
||
|
||
async function nowEnvLsIncludesVar() {
|
||
const { exitCode, stderr, stdout } = await execa(
|
||
binaryPath,
|
||
['env', 'ls', ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
cwd: target,
|
||
}
|
||
);
|
||
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||
t.regex(stderr, /Environment Variables found in Project/gm);
|
||
|
||
const lines = stdout.split('\n');
|
||
|
||
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 myStdinVars = lines.filter(line => line.includes('MY_STDIN_VAR'));
|
||
t.is(myStdinVars.length, 1);
|
||
t.regex(myStdinVars.join('\n'), /preview/gm);
|
||
}
|
||
|
||
async function nowEnvPull() {
|
||
const { exitCode, stderr, stdout } = await execa(
|
||
binaryPath,
|
||
['env', 'pull', '-y', ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
cwd: target,
|
||
}
|
||
);
|
||
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||
t.regex(stderr, /Created .env file/gm);
|
||
|
||
const contents = fs.readFileSync(path.join(target, '.env'), 'utf8');
|
||
t.is(contents, 'MY_ENV_VAR="MY_VALUE"\n');
|
||
}
|
||
|
||
async function nowDeployWithVar() {
|
||
const { exitCode, stderr, stdout } = await execa(
|
||
binaryPath,
|
||
[...defaultArgs],
|
||
{
|
||
reject: false,
|
||
cwd: target,
|
||
}
|
||
);
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||
const { host } = new URL(stdout);
|
||
|
||
const apiUrl = `https://${host}/api/get-env`;
|
||
console.log({ apiUrl });
|
||
const apiRes = await fetch(apiUrl);
|
||
t.is(apiRes.status, 200, formatOutput({ stderr, stdout }));
|
||
const apiJson = await apiRes.json();
|
||
t.is(apiJson['MY_ENV_VAR'], 'MY_VALUE');
|
||
t.is(apiJson['MY_STDIN_VAR'], 'MY_STDIN_VALUE');
|
||
|
||
const homeUrl = `https://${host}`;
|
||
console.log({ homeUrl });
|
||
const homeRes = await fetch(homeUrl);
|
||
t.is(homeRes.status, 200, formatOutput({ stderr, stdout }));
|
||
const homeJson = await homeRes.json();
|
||
t.is(homeJson['MY_ENV_VAR'], 'MY_VALUE');
|
||
t.is(homeJson['MY_STDIN_VAR'], 'MY_STDIN_VALUE');
|
||
}
|
||
|
||
async function nowEnvRemove() {
|
||
const now = execa(binaryPath, ['env', 'rm', '-y', ...defaultArgs], {
|
||
reject: false,
|
||
cwd: target,
|
||
});
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('What’s the name of the variable?')
|
||
);
|
||
now.stdin.write('MY_ENV_VAR\n');
|
||
|
||
await waitForPrompt(
|
||
now,
|
||
chunk =>
|
||
chunk.includes('which Environments') && chunk.includes('MY_ENV_VAR')
|
||
);
|
||
now.stdin.write('a\n'); // select all
|
||
|
||
const { exitCode, stderr, stdout } = await now;
|
||
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||
}
|
||
|
||
async function nowEnvRemoveWithArgs() {
|
||
const { exitCode, stderr, stdout } = await execa(
|
||
binaryPath,
|
||
['env', 'rm', 'MY_STDIN_VAR', 'preview', '-y', ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
cwd: target,
|
||
}
|
||
);
|
||
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||
}
|
||
|
||
await nowDeploy();
|
||
await nowEnvLsIsEmpty();
|
||
await nowEnvAdd();
|
||
await nowEnvAddFromStdin();
|
||
await nowEnvLsIncludesVar();
|
||
await nowEnvPull();
|
||
await nowDeployWithVar();
|
||
await nowEnvRemove();
|
||
await nowEnvRemoveWithArgs();
|
||
await nowEnvLsIsEmpty();
|
||
});
|
||
|
||
test('deploy with metadata containing "=" in the value', async t => {
|
||
const target = fixture('static-v2-meta');
|
||
|
||
const { exitCode, stderr, stdout } = await execa(
|
||
binaryPath,
|
||
[target, ...defaultArgs, '--confirm', '--meta', 'someKey=='],
|
||
{ reject: false }
|
||
);
|
||
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||
|
||
const { host } = new URL(stdout);
|
||
const res = await fetch(
|
||
`https://api.vercel.com/v12/now/deployments/get?url=${host}`,
|
||
{ headers: { authorization: `Bearer ${token}` } }
|
||
);
|
||
const deployment = await res.json();
|
||
t.is(deployment.meta.someKey, '=');
|
||
});
|
||
|
||
test('print the deploy help message', async t => {
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
['help', ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 2);
|
||
t.true(stderr.includes(deployHelpMessage), `Received:\n${stderr}\n${stdout}`);
|
||
t.false(
|
||
stderr.includes('ExperimentalWarning'),
|
||
`Received:\n${stderr}\n${stdout}`
|
||
);
|
||
});
|
||
|
||
test('output the version', async t => {
|
||
const { stdout, stderr, exitCode } = await execa(
|
||
binaryPath,
|
||
['--version', ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
const version = stdout.trim();
|
||
|
||
t.is(exitCode, 0);
|
||
t.truthy(semVer.valid(version));
|
||
t.is(version, pkg.version);
|
||
});
|
||
|
||
test('should error with suggestion for secrets subcommand', async t => {
|
||
const target = fixture('subdirectory-secret');
|
||
|
||
const { exitCode, stderr, stdout } = await execa(
|
||
binaryPath,
|
||
['secret', 'add', 'key', 'value', ...defaultArgs],
|
||
{
|
||
cwd: target,
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 1);
|
||
t.regex(
|
||
stderr,
|
||
/secrets/gm,
|
||
`Expected "secrets" suggestion but received "${stderr}"`
|
||
);
|
||
});
|
||
|
||
test('should add secret with hyphen prefix', async t => {
|
||
const target = fixture('build-secret');
|
||
const key = 'mysecret';
|
||
const value = '-foo_bar';
|
||
|
||
let secretCall = await execa(
|
||
binaryPath,
|
||
['secrets', 'add', ...defaultArgs, key, value],
|
||
{
|
||
cwd: target,
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
t.is(
|
||
secretCall.exitCode,
|
||
0,
|
||
formatOutput({ stderr: secretCall.stderr, stdout: secretCall.stdout })
|
||
);
|
||
|
||
let targetCall = await execa(binaryPath, [...defaultArgs, '--confirm'], {
|
||
cwd: target,
|
||
reject: false,
|
||
});
|
||
|
||
t.is(
|
||
targetCall.exitCode,
|
||
0,
|
||
formatOutput({ stderr: targetCall.stderr, stdout: targetCall.stdout })
|
||
);
|
||
const { host } = new URL(targetCall.stdout);
|
||
const response = await fetch(`https://${host}`);
|
||
t.is(
|
||
response.status,
|
||
200,
|
||
formatOutput({ stderr: targetCall.stderr, stdout: targetCall.stdout })
|
||
);
|
||
t.is(
|
||
await response.text(),
|
||
`${value}\n`,
|
||
formatOutput({ stderr: targetCall.stderr, stdout: targetCall.stdout })
|
||
);
|
||
});
|
||
|
||
test('login with unregistered user', async t => {
|
||
const { stdout, stderr, exitCode } = await execa(
|
||
binaryPath,
|
||
['login', `${session}@${session}.com`, ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
const goal = `Error! Please sign up: https://vercel.com/signup`;
|
||
const lines = stdout.trim().split('\n');
|
||
const last = lines[lines.length - 1];
|
||
|
||
t.is(exitCode, 1);
|
||
t.is(last, goal);
|
||
});
|
||
|
||
test('test invalid json alias rules', async t => {
|
||
const fixturePath = fixture('alias-rules');
|
||
const output = await execute(['alias', '-r', 'invalid-json-rules.json'], {
|
||
cwd: fixturePath,
|
||
});
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.regex(output.stderr, /Error parsing/, formatOutput(output));
|
||
});
|
||
|
||
test('test invalid alias rules', async t => {
|
||
const fixturePath = fixture('alias-rules');
|
||
const output = await execute(['alias', '-r', 'invalid-rules.json'], {
|
||
cwd: fixturePath,
|
||
});
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.regex(output.stderr, /Path Alias validation error/, formatOutput(output));
|
||
});
|
||
|
||
test('test invalid type for alias rules', async t => {
|
||
const fixturePath = fixture('alias-rules');
|
||
const output = await execute(['alias', '-r', 'invalid-type-rules.json'], {
|
||
cwd: fixturePath,
|
||
});
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.regex(output.stderr, /Path Alias validation error/, formatOutput(output));
|
||
});
|
||
|
||
test('ignore files specified in .nowignore', async t => {
|
||
const directory = fixture('nowignore');
|
||
|
||
const args = [
|
||
'--debug',
|
||
'--public',
|
||
'--name',
|
||
session,
|
||
...defaultArgs,
|
||
'--confirm',
|
||
];
|
||
const targetCall = await execa(binaryPath, args, {
|
||
cwd: directory,
|
||
reject: false,
|
||
});
|
||
|
||
console.log(targetCall.stderr);
|
||
console.log(targetCall.stdout);
|
||
console.log(targetCall.exitCode);
|
||
|
||
const { host } = new URL(targetCall.stdout);
|
||
const ignoredFile = await fetch(`https://${host}/ignored.txt`);
|
||
t.is(ignoredFile.status, 404);
|
||
|
||
const presentFile = await fetch(`https://${host}/index.txt`);
|
||
t.is(presentFile.status, 200);
|
||
});
|
||
|
||
test('ignore files specified in .nowignore via allowlist', async t => {
|
||
const directory = fixture('nowignore-allowlist');
|
||
|
||
const args = [
|
||
'--debug',
|
||
'--public',
|
||
'--name',
|
||
session,
|
||
...defaultArgs,
|
||
'--confirm',
|
||
];
|
||
const targetCall = await execa(binaryPath, args, {
|
||
cwd: directory,
|
||
reject: false,
|
||
});
|
||
|
||
console.log(targetCall.stderr);
|
||
console.log(targetCall.stdout);
|
||
console.log(targetCall.exitCode);
|
||
|
||
const { host } = new URL(targetCall.stdout);
|
||
const ignoredFile = await fetch(`https://${host}/ignored.txt`);
|
||
t.is(ignoredFile.status, 404);
|
||
|
||
const presentFile = await fetch(`https://${host}/index.txt`);
|
||
t.is(presentFile.status, 200);
|
||
});
|
||
|
||
test('list the scopes', async t => {
|
||
const { stdout, stderr, exitCode } = await execa(
|
||
binaryPath,
|
||
['teams', 'ls', ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 0);
|
||
|
||
const include = `✔ ${contextName} ${email}`;
|
||
t.true(
|
||
stdout.includes(include),
|
||
`Expected: ${include}\n\nReceived instead:\n${stdout}\n${stderr}`
|
||
);
|
||
});
|
||
|
||
test('list the payment methods', async t => {
|
||
const { stdout, stderr, exitCode } = await execa(
|
||
binaryPath,
|
||
['billing', 'ls', ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 0);
|
||
t.true(stdout.startsWith(`> 0 cards found under ${contextName}`));
|
||
});
|
||
|
||
test('domains inspect', async t => {
|
||
const domainName = `inspect-${contextName}.org`;
|
||
|
||
const addRes = await execa(
|
||
binaryPath,
|
||
[`domains`, `add`, domainName, ...defaultArgs],
|
||
{ reject: false }
|
||
);
|
||
t.is(addRes.exitCode, 0);
|
||
|
||
const { stderr, exitCode } = await execa(
|
||
binaryPath,
|
||
['domains', 'inspect', domainName, ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
const rmRes = await execa(
|
||
binaryPath,
|
||
[`domains`, `rm`, domainName, ...defaultArgs],
|
||
{ reject: false, input: 'y' }
|
||
);
|
||
t.is(rmRes.exitCode, 0);
|
||
|
||
t.is(exitCode, 0);
|
||
t.true(!stderr.includes(`Renewal Price`));
|
||
});
|
||
|
||
test('try to purchase a domain', async t => {
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
['domains', 'buy', `${session}-test.org`, ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
input: 'y',
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 1);
|
||
t.true(
|
||
stderr.includes(
|
||
`Error! Could not purchase domain. Please add a payment method using \`vercel billing add\`.`
|
||
)
|
||
);
|
||
});
|
||
|
||
test('try to transfer-in a domain with "--code" option', async t => {
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
[
|
||
'domains',
|
||
'transfer-in',
|
||
'--code',
|
||
'xyz',
|
||
`${session}-test.org`,
|
||
...defaultArgs,
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.true(
|
||
stderr.includes(
|
||
`Error! The domain "${session}-test.org" is not transferable.`
|
||
)
|
||
);
|
||
t.is(exitCode, 1);
|
||
});
|
||
|
||
test('try to move an invalid domain', async t => {
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
[
|
||
'domains',
|
||
'move',
|
||
`${session}-invalid-test.org`,
|
||
`${session}-invalid-user`,
|
||
...defaultArgs,
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.true(stderr.includes(`Error! Domain not found under `));
|
||
t.is(exitCode, 1);
|
||
});
|
||
|
||
test('try to set default without existing payment method', async t => {
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
['billing', 'set-default', ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 0);
|
||
t.true(stderr.includes('You have no credit cards to choose from'));
|
||
});
|
||
|
||
test('try to remove a non-existing payment method', async t => {
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
['billing', 'rm', 'card_d2j32d9382jr928rd', ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 0);
|
||
t.true(
|
||
stderr.includes(
|
||
`You have no credit cards to choose from to delete under ${contextName}`
|
||
)
|
||
);
|
||
});
|
||
|
||
test('set platform version using `-V` to invalid number', async t => {
|
||
const directory = fixture('builds');
|
||
const goal =
|
||
'Error! The "--platform-version" option must be either `1` or `2`.';
|
||
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
[directory, '--public', '--name', session, ...defaultArgs, '-V', 3],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 1);
|
||
|
||
// Ensure the error message shows up
|
||
t.true(stderr.includes(goal));
|
||
});
|
||
|
||
test('set platform version using `--platform-version` to invalid number', async t => {
|
||
const directory = fixture('builds');
|
||
const goal =
|
||
'Error! The "--platform-version" option must be either `1` or `2`.';
|
||
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
[
|
||
directory,
|
||
'--public',
|
||
'--name',
|
||
session,
|
||
...defaultArgs,
|
||
'--platform-version',
|
||
3,
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 1);
|
||
|
||
// Ensure the error message shows up
|
||
t.true(stderr.includes(goal));
|
||
});
|
||
|
||
test('set platform version using `-V` to `2`', async t => {
|
||
const directory = fixture('builds');
|
||
|
||
const { stdout, stderr, exitCode } = await execa(
|
||
binaryPath,
|
||
[
|
||
directory,
|
||
'--public',
|
||
'--name',
|
||
session,
|
||
...defaultArgs,
|
||
'-V',
|
||
2,
|
||
'--force',
|
||
'--confirm',
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
const output = `Received:\n"${stderr}"\n"${stdout}"`;
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 0, output);
|
||
|
||
// Test if the output is really a URL
|
||
const { href, host } = new URL(stdout);
|
||
t.is(host.split('-')[0], session, output);
|
||
|
||
if (host) {
|
||
context.deployment = host;
|
||
}
|
||
|
||
// Send a test request to the deployment
|
||
const response = await fetch(href);
|
||
const contentType = response.headers.get('content-type');
|
||
|
||
t.is(contentType, 'text/html; charset=utf-8');
|
||
});
|
||
|
||
test('output logs of a 2.0 deployment', async t => {
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
['logs', context.deployment, ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.true(stderr.includes(`Fetched deployment "${context.deployment}"`));
|
||
t.is(exitCode, 0);
|
||
});
|
||
|
||
test('output logs of a 2.0 deployment without annotate', async t => {
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
['logs', context.deployment, ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.true(!stderr.includes('[now-builder-debug]'));
|
||
t.true(!stderr.includes('START RequestId'));
|
||
t.true(!stderr.includes('END RequestId'));
|
||
t.true(!stderr.includes('REPORT RequestId'));
|
||
t.true(!stderr.includes('Init Duration'));
|
||
t.true(!stderr.includes('XRAY TraceId'));
|
||
t.is(exitCode, 0);
|
||
});
|
||
|
||
/*
|
||
* Disabled 2 tests because these temp users don't have certs
|
||
test('create wildcard alias for deployment', async t => {
|
||
const hosts = {
|
||
deployment: context.deployment,
|
||
alias: `*.${contextName}.now.sh`,
|
||
};
|
||
|
||
const { stdout, stderr, exitCode } = await execa(
|
||
binaryPath,
|
||
['alias', hosts.deployment, hosts.alias, ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
const goal = `> Success! ${hosts.alias} now points to https://${hosts.deployment}`;
|
||
|
||
t.is(exitCode, 0);
|
||
t.true(stdout.startsWith(goal));
|
||
|
||
// Send a test request to the alias
|
||
// Retries to make sure we consider the time it takes to update
|
||
const response = await retry(
|
||
async () => {
|
||
const response = await fetch(`https://test.${contextName}.now.sh`);
|
||
|
||
if (response.ok) {
|
||
return response;
|
||
}
|
||
|
||
throw new Error(`Error: Returned code ${response.status}`);
|
||
},
|
||
{ retries: 3 }
|
||
);
|
||
const content = await response.text();
|
||
|
||
t.true(response.ok);
|
||
t.true(content.includes(contextName));
|
||
|
||
context.wildcardAlias = hosts.alias;
|
||
});
|
||
|
||
test('remove the wildcard alias', async t => {
|
||
const goal = `> Success! Alias ${context.wildcardAlias} removed`;
|
||
|
||
const { stdout, stderr, exitCode } = await execa(
|
||
binaryPath,
|
||
['alias', 'rm', context.wildcardAlias, '--yes', ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 0);
|
||
t.true(stdout.startsWith(goal));
|
||
});
|
||
*/
|
||
|
||
test('ensure username in list is right', async t => {
|
||
const { stdout, stderr, exitCode } = await execa(
|
||
binaryPath,
|
||
['ls', ...defaultArgs],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 0);
|
||
|
||
const line = stdout.split('\n').find(line => line.includes('.now.sh'));
|
||
const columns = line.split(/\s+/);
|
||
|
||
// Ensure username column have username
|
||
t.truthy(columns.pop().includes(contextName));
|
||
});
|
||
|
||
test('set platform version using `--platform-version` to `2`', async t => {
|
||
const directory = fixture('builds');
|
||
|
||
const { stdout, stderr, exitCode } = await execa(
|
||
binaryPath,
|
||
[
|
||
directory,
|
||
'--public',
|
||
'--name',
|
||
session,
|
||
...defaultArgs,
|
||
'--platform-version',
|
||
2,
|
||
'--force',
|
||
'--confirm',
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 0);
|
||
|
||
// Test if the output is really a URL
|
||
const { href, host } = new URL(stdout);
|
||
t.is(host.split('-')[0], session);
|
||
|
||
// Send a test request to the deployment
|
||
const response = await fetch(href);
|
||
const contentType = response.headers.get('content-type');
|
||
|
||
t.is(contentType, 'text/html; charset=utf-8');
|
||
});
|
||
|
||
test('ensure we render a warning for deployments with no files', async t => {
|
||
const directory = fixture('single-dotfile');
|
||
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
[
|
||
directory,
|
||
'--public',
|
||
'--name',
|
||
session,
|
||
...defaultArgs,
|
||
'--confirm',
|
||
'--force',
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// Ensure the warning is printed
|
||
t.true(
|
||
stderr.includes(
|
||
'There are no files (or only files starting with a dot) inside your deployment.'
|
||
)
|
||
);
|
||
|
||
// Test if the output is really a URL
|
||
const { href, host } = new URL(stdout);
|
||
t.is(host.split('-')[0], session);
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 0);
|
||
|
||
// Send a test request to the deployment
|
||
const response = await fetch(href);
|
||
const contentType = response.headers.get('content-type');
|
||
|
||
t.is(contentType, 'text/plain; charset=utf-8');
|
||
});
|
||
|
||
test('ensure we render a prompt when deploying home directory', async t => {
|
||
const directory = homedir();
|
||
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
[directory, '--public', '--name', session, ...defaultArgs, '--force'],
|
||
{
|
||
reject: false,
|
||
input: 'N',
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 0);
|
||
|
||
t.true(
|
||
stdout.includes(
|
||
'You are deploying your home directory. Do you want to continue? [y/N]'
|
||
)
|
||
);
|
||
t.true(stderr.includes('Aborted'));
|
||
});
|
||
|
||
test('ensure the `scope` property works with email', async t => {
|
||
const directory = fixture('config-scope-property-email');
|
||
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
[
|
||
directory,
|
||
'--public',
|
||
'--name',
|
||
session,
|
||
...defaultArgs,
|
||
'--force',
|
||
'--confirm',
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// Ensure we're deploying under the right scope
|
||
t.true(stderr.includes(session));
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 0);
|
||
|
||
// Test if the output is really a URL
|
||
const { href, host } = new URL(stdout);
|
||
t.is(host.split('-')[0], session);
|
||
|
||
// Send a test request to the deployment
|
||
const response = await fetch(href);
|
||
const contentType = response.headers.get('content-type');
|
||
|
||
t.is(contentType, 'text/html; charset=utf-8');
|
||
});
|
||
|
||
test('ensure the `scope` property works with username', async t => {
|
||
const directory = fixture('config-scope-property-username');
|
||
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
[
|
||
directory,
|
||
'--public',
|
||
'--name',
|
||
session,
|
||
...defaultArgs,
|
||
'--force',
|
||
'--confirm',
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// Ensure we're deploying under the right scope
|
||
t.true(stderr.includes(contextName));
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 0);
|
||
|
||
// Test if the output is really a URL
|
||
const { href, host } = new URL(stdout);
|
||
t.is(host.split('-')[0], session);
|
||
|
||
// Send a test request to the deployment
|
||
const response = await fetch(href);
|
||
const contentType = response.headers.get('content-type');
|
||
|
||
t.is(contentType, 'text/html; charset=utf-8');
|
||
});
|
||
|
||
test('try to create a builds deployments with wrong config', async t => {
|
||
const directory = fixture('builds-wrong');
|
||
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
[
|
||
directory,
|
||
'--public',
|
||
'--name',
|
||
session,
|
||
...defaultArgs,
|
||
'--force',
|
||
'--confirm',
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 1);
|
||
t.true(
|
||
stderr.includes(
|
||
'Error! The property `builder` is not allowed in vercel.json – please remove it.'
|
||
)
|
||
);
|
||
});
|
||
|
||
test('create a builds deployments with no actual builds', async t => {
|
||
const directory = fixture('builds-no-list');
|
||
|
||
const { stdout, stderr, exitCode } = await execa(
|
||
binaryPath,
|
||
[
|
||
directory,
|
||
'--public',
|
||
'--name',
|
||
session,
|
||
...defaultArgs,
|
||
'--force',
|
||
'--confirm',
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 0);
|
||
|
||
// Test if the output is really a URL
|
||
const { host } = new URL(stdout);
|
||
t.is(host.split('-')[0], session);
|
||
});
|
||
|
||
test('create a builds deployments without platform version flag', async t => {
|
||
const directory = fixture('builds');
|
||
|
||
const { stdout, stderr, exitCode } = await execa(
|
||
binaryPath,
|
||
[
|
||
directory,
|
||
'--public',
|
||
'--name',
|
||
session,
|
||
...defaultArgs,
|
||
'--force',
|
||
'--confirm',
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 0);
|
||
|
||
// Test if the output is really a URL
|
||
const { href, host } = new URL(stdout);
|
||
t.is(host.split('-')[0], session);
|
||
|
||
// Send a test request to the deployment
|
||
const response = await fetch(href);
|
||
const contentType = response.headers.get('content-type');
|
||
|
||
t.is(contentType, 'text/html; charset=utf-8');
|
||
});
|
||
|
||
test('create a staging deployment', async t => {
|
||
const directory = fixture('static-deployment');
|
||
|
||
const args = ['--debug', '--public', '--name', session, ...defaultArgs];
|
||
const targetCall = await execa(binaryPath, [
|
||
directory,
|
||
'--target=staging',
|
||
...args,
|
||
'--confirm',
|
||
]);
|
||
|
||
console.log(targetCall.stderr);
|
||
console.log(targetCall.stdout);
|
||
console.log(targetCall.exitCode);
|
||
|
||
t.regex(
|
||
targetCall.stderr,
|
||
/Setting target to staging/gm,
|
||
formatOutput(targetCall)
|
||
);
|
||
|
||
t.is(targetCall.exitCode, 0, formatOutput(targetCall));
|
||
|
||
const { host } = new URL(targetCall.stdout);
|
||
const deployment = await apiFetch(
|
||
`/v10/now/deployments/unknown?url=${host}`
|
||
).then(resp => resp.json());
|
||
t.is(deployment.target, 'staging', JSON.stringify(deployment, null, 2));
|
||
});
|
||
|
||
test('create a production deployment', async t => {
|
||
const directory = fixture('static-deployment');
|
||
|
||
const args = ['--debug', '--public', '--name', session, ...defaultArgs];
|
||
const targetCall = await execa(binaryPath, [
|
||
directory,
|
||
'--target=production',
|
||
...args,
|
||
'--confirm',
|
||
]);
|
||
|
||
console.log(targetCall.stderr);
|
||
console.log(targetCall.stdout);
|
||
console.log(targetCall.exitCode);
|
||
|
||
t.is(targetCall.exitCode, 0, formatOutput(targetCall));
|
||
t.regex(
|
||
targetCall.stderr,
|
||
/`--prod` option instead/gm,
|
||
formatOutput(targetCall)
|
||
);
|
||
t.regex(
|
||
targetCall.stderr,
|
||
/Setting target to production/gm,
|
||
formatOutput(targetCall)
|
||
);
|
||
|
||
const { host: targetHost } = new URL(targetCall.stdout);
|
||
const targetDeployment = await apiFetch(
|
||
`/v10/now/deployments/unknown?url=${targetHost}`
|
||
).then(resp => resp.json());
|
||
t.is(
|
||
targetDeployment.target,
|
||
'production',
|
||
JSON.stringify(targetDeployment, null, 2)
|
||
);
|
||
|
||
const call = await execa(binaryPath, [directory, '--prod', ...args]);
|
||
|
||
console.log(call.stderr);
|
||
console.log(call.stdout);
|
||
console.log(call.exitCode);
|
||
|
||
t.is(call.exitCode, 0, formatOutput(call));
|
||
t.regex(
|
||
call.stderr,
|
||
/Setting target to production/gm,
|
||
formatOutput(targetCall)
|
||
);
|
||
|
||
const { host } = new URL(call.stdout);
|
||
const deployment = await apiFetch(
|
||
`/v10/now/deployments/unknown?url=${host}`
|
||
).then(resp => resp.json());
|
||
t.is(deployment.target, 'production', JSON.stringify(deployment, null, 2));
|
||
});
|
||
|
||
test('deploying a file should not show prompts and display deprecation', async t => {
|
||
const file = fixture('static-single-file/first.png');
|
||
|
||
const output = await execute([file], {
|
||
reject: false,
|
||
});
|
||
|
||
const { stdout, stderr, exitCode } = output;
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 0, formatOutput(output));
|
||
t.true(stderr.includes('Deploying files with Vercel is deprecated'));
|
||
|
||
// Ensure `.vercel` was not created
|
||
t.is(
|
||
await exists(path.join(path.dirname(file), '.vercel')),
|
||
false,
|
||
'.vercel should not exists'
|
||
);
|
||
|
||
// Test if the output is really a URL
|
||
const { href, host } = new URL(stdout);
|
||
t.is(host.split('-')[0], 'files');
|
||
|
||
// Send a test request to the deployment
|
||
const response = await fetch(href);
|
||
const contentType = response.headers.get('content-type');
|
||
|
||
t.is(contentType, 'image/png');
|
||
t.deepEqual(await readFile(file), await response.buffer());
|
||
});
|
||
|
||
test('deploying more than 1 path should fail', async t => {
|
||
const file1 = fixture('static-multiple-files/first.png');
|
||
const file2 = fixture('static-multiple-files/second.png');
|
||
|
||
const { stdout, stderr, exitCode } = await execa(
|
||
binaryPath,
|
||
[file1, file2, '--public', '--name', session, ...defaultArgs, '--confirm'],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 1);
|
||
t.true(stderr.trim().endsWith(`Can't deploy more than one path.`));
|
||
});
|
||
|
||
test('use `--debug` CLI flag', async t => {
|
||
const directory = fixture('build-env-debug');
|
||
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
[
|
||
directory,
|
||
'--public',
|
||
'--name',
|
||
session,
|
||
'--debug',
|
||
...defaultArgs,
|
||
'--confirm',
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// Ensure the exit code is right
|
||
t.is(exitCode, 0, `Received:\n"${stderr}"\n"${stdout}"`);
|
||
|
||
// Test if the output is really a URL
|
||
const deploymentUrl = pickUrl(stdout);
|
||
const { href, host } = new URL(deploymentUrl);
|
||
t.is(host.split('-')[0], session);
|
||
|
||
await waitForDeployment(href);
|
||
|
||
// get the content
|
||
const response = await fetch(href);
|
||
const content = await response.text();
|
||
t.is(content.trim(), 'off');
|
||
});
|
||
|
||
test('try to deploy non-existing path', async t => {
|
||
const goal = `Error! The specified file or directory "${session}" does not exist.`;
|
||
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
[session, ...defaultArgs, '--confirm'],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 1);
|
||
t.true(stderr.trim().endsWith(goal));
|
||
});
|
||
|
||
test('try to deploy with non-existing team', async t => {
|
||
const target = fixture('node');
|
||
const goal = `Error! The specified scope does not exist`;
|
||
|
||
const { stderr, stdout, exitCode } = await execa(
|
||
binaryPath,
|
||
[target, '--scope', session, ...defaultArgs, '--confirm'],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 1);
|
||
t.true(stderr.includes(goal));
|
||
});
|
||
|
||
const verifyExampleAngular = (cwd, dir) =>
|
||
fs.existsSync(path.join(cwd, dir, 'package.json')) &&
|
||
fs.existsSync(path.join(cwd, dir, 'tsconfig.json')) &&
|
||
fs.existsSync(path.join(cwd, dir, 'angular.json'));
|
||
|
||
const verifyExampleAmp = (cwd, dir) =>
|
||
fs.existsSync(path.join(cwd, dir, 'index.html')) &&
|
||
fs.existsSync(path.join(cwd, dir, 'logo.png')) &&
|
||
fs.existsSync(path.join(cwd, dir, 'favicon.png'));
|
||
|
||
test('initialize example "angular"', async t => {
|
||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||
const cwd = tmpDir.name;
|
||
const goal = '> Success! Initialized "angular" example in';
|
||
|
||
const { stdout, stderr, exitCode } = await execute(['init', 'angular'], {
|
||
cwd,
|
||
});
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 0, formatOutput({ stdout, stderr }));
|
||
t.true(stdout.includes(goal), formatOutput({ stdout, stderr }));
|
||
t.true(
|
||
verifyExampleAngular(cwd, 'angular'),
|
||
formatOutput({ stdout, stderr })
|
||
);
|
||
});
|
||
|
||
test('initialize example ("angular") to specified directory', async t => {
|
||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||
const cwd = tmpDir.name;
|
||
const goal = '> Success! Initialized "angular" example in';
|
||
|
||
const { stdout, stderr, exitCode } = await execute(
|
||
['init', 'angular', 'ang'],
|
||
{
|
||
cwd,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 0);
|
||
t.true(stdout.includes(goal));
|
||
t.true(verifyExampleAngular(cwd, 'ang'));
|
||
});
|
||
|
||
test('initialize selected example ("amp")', async t => {
|
||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||
const cwd = tmpDir.name;
|
||
const goal = '> Success! Initialized "amp" example in';
|
||
|
||
const { stdout, stderr, exitCode } = await execute(['init'], {
|
||
cwd,
|
||
input: '\n',
|
||
});
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 0, formatOutput({ stdout, stderr }));
|
||
t.true(stdout.includes(goal), formatOutput({ stdout, stderr }));
|
||
t.true(verifyExampleAmp(cwd, 'amp'), formatOutput({ stdout, stderr }));
|
||
});
|
||
|
||
test('initialize example to existing directory with "-f"', async t => {
|
||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||
const cwd = tmpDir.name;
|
||
const goal = '> Success! Initialized "angular" example in';
|
||
|
||
await ensureDir(path.join(cwd, 'angular'));
|
||
createFile(path.join(cwd, 'angular', '.gitignore'));
|
||
const { stdout, stderr, exitCode } = await execute(
|
||
['init', 'angular', '-f'],
|
||
{
|
||
cwd,
|
||
}
|
||
);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 0);
|
||
t.true(stdout.includes(goal), formatOutput({ stdout, stderr }));
|
||
t.true(verifyExampleAngular(cwd, 'angular'));
|
||
});
|
||
|
||
test('try to initialize example to existing directory', async t => {
|
||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||
const cwd = tmpDir.name;
|
||
const goal =
|
||
'Error! Destination path "angular" already exists and is not an empty directory. You may use `--force` or `-f` to override it.';
|
||
|
||
await ensureDir(path.join(cwd, 'angular'));
|
||
createFile(path.join(cwd, 'angular', '.gitignore'));
|
||
const { stdout, stderr, exitCode } = await execute(['init', 'angular'], {
|
||
cwd,
|
||
input: '\n',
|
||
});
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 1);
|
||
t.true(stdout.includes(goal), formatOutput({ stdout, stderr }));
|
||
});
|
||
|
||
test('try to initialize misspelled example (noce) in non-tty', async t => {
|
||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||
const cwd = tmpDir.name;
|
||
const goal =
|
||
'Error! No example found for noce, run `vercel init` to see the list of available examples.';
|
||
|
||
const { stdout, stderr, exitCode } = await execute(['init', 'noce'], { cwd });
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 1);
|
||
t.true(stdout.includes(goal), formatOutput({ stdout, stderr }));
|
||
});
|
||
|
||
test('try to initialize example "example-404"', async t => {
|
||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||
const cwd = tmpDir.name;
|
||
const goal =
|
||
'Error! No example found for example-404, run `vercel init` to see the list of available examples.';
|
||
|
||
const { stdout, stderr, exitCode } = await execute(['init', 'example-404'], {
|
||
cwd,
|
||
});
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 1);
|
||
t.true(stdout.includes(goal), formatOutput({ stdout, stderr }));
|
||
});
|
||
|
||
test('try to revert a deployment and assign the automatic aliases', async t => {
|
||
const firstDeployment = fixture('now-revert-alias-1');
|
||
const secondDeployment = fixture('now-revert-alias-2');
|
||
|
||
const { name } = JSON.parse(
|
||
fs.readFileSync(path.join(firstDeployment, 'now.json'))
|
||
);
|
||
const url = `https://${name}.user.now.sh`;
|
||
|
||
{
|
||
const { stdout: deploymentUrl, stderr, exitCode } = await execute([
|
||
firstDeployment,
|
||
'--confirm',
|
||
]);
|
||
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout: deploymentUrl }));
|
||
|
||
await waitForDeployment(deploymentUrl);
|
||
await sleep(20000);
|
||
|
||
const result = await fetch(url).then(r => r.json());
|
||
|
||
t.is(
|
||
result.name,
|
||
'now-revert-alias-1',
|
||
`[First run] Received ${result.name} instead on ${url} (${deploymentUrl})`
|
||
);
|
||
}
|
||
|
||
{
|
||
const { stdout: deploymentUrl, stderr, exitCode } = await execute([
|
||
secondDeployment,
|
||
'--confirm',
|
||
]);
|
||
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout: deploymentUrl }));
|
||
|
||
await waitForDeployment(deploymentUrl);
|
||
await sleep(20000);
|
||
await fetch(url);
|
||
await sleep(5000);
|
||
|
||
const result = await fetch(url).then(r => r.json());
|
||
|
||
t.is(
|
||
result.name,
|
||
'now-revert-alias-2',
|
||
`[Second run] Received ${result.name} instead on ${url} (${deploymentUrl})`
|
||
);
|
||
}
|
||
|
||
{
|
||
const { stdout: deploymentUrl, stderr, exitCode } = await execute([
|
||
firstDeployment,
|
||
'--confirm',
|
||
]);
|
||
|
||
t.is(exitCode, 0, formatOutput({ stderr, stdout: deploymentUrl }));
|
||
|
||
await waitForDeployment(deploymentUrl);
|
||
await sleep(20000);
|
||
await fetch(url);
|
||
await sleep(5000);
|
||
|
||
const result = await fetch(url).then(r => r.json());
|
||
|
||
t.is(
|
||
result.name,
|
||
'now-revert-alias-1',
|
||
`[Third run] Received ${result.name} instead on ${url} (${deploymentUrl})`
|
||
);
|
||
}
|
||
});
|
||
|
||
test('whoami', async t => {
|
||
const { exitCode, stdout, stderr } = await execute(['whoami']);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
t.is(exitCode, 0);
|
||
t.is(stdout, contextName, formatOutput({ stdout, stderr }));
|
||
});
|
||
|
||
test('fail `now dev` dev script without now.json', async t => {
|
||
const deploymentPath = fixture('now-dev-fail-dev-script');
|
||
const { exitCode, stderr } = await execute(['dev', deploymentPath]);
|
||
|
||
t.is(exitCode, 1);
|
||
t.true(
|
||
stderr.includes('must not contain `now dev`'),
|
||
`Received instead: "${stderr}"`
|
||
);
|
||
});
|
||
|
||
test('print correct link in legacy warning', async t => {
|
||
const deploymentPath = fixture('v1-warning-link');
|
||
const { exitCode, stderr, stdout } = await execute([
|
||
deploymentPath,
|
||
'--confirm',
|
||
]);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
// It is expected to fail,
|
||
// since the package.json does not have a start script
|
||
t.is(exitCode, 1);
|
||
t.regex(stderr, /migrate-to-vercel/);
|
||
});
|
||
|
||
test('`now rm` removes a deployment', async t => {
|
||
const directory = fixture('builds');
|
||
|
||
const { stdout } = await execa(
|
||
binaryPath,
|
||
[
|
||
directory,
|
||
'--public',
|
||
'--name',
|
||
session,
|
||
...defaultArgs,
|
||
'-V',
|
||
2,
|
||
'--force',
|
||
'--confirm',
|
||
],
|
||
{
|
||
reject: false,
|
||
}
|
||
);
|
||
|
||
const { host } = new URL(stdout);
|
||
const { exitCode, stdout: stdoutRemove } = await execute([
|
||
'rm',
|
||
host,
|
||
'--yes',
|
||
]);
|
||
|
||
t.truthy(stdoutRemove.includes(host));
|
||
t.is(exitCode, 0);
|
||
});
|
||
|
||
test('`now rm` 404 exits quickly', async t => {
|
||
const start = Date.now();
|
||
const { exitCode, stderr, stdout } = await execute([
|
||
'rm',
|
||
'this.is.a.deployment.that.does.not.exist.example.com',
|
||
]);
|
||
|
||
console.log(stderr);
|
||
console.log(stdout);
|
||
console.log(exitCode);
|
||
|
||
const delta = Date.now() - start;
|
||
|
||
// "does not exist" case is exit code 1, similar to Unix `rm`
|
||
t.is(exitCode, 1);
|
||
t.truthy(
|
||
stderr.includes(
|
||
'Could not find any deployments or projects matching "this.is.a.deployment.that.does.not.exist.example.com"'
|
||
)
|
||
);
|
||
|
||
// "quickly" meaning < 5 seconds, because it used to hang from a previous bug
|
||
t.truthy(delta < 5000);
|
||
});
|
||
|
||
test('render build errors', async t => {
|
||
const deploymentPath = fixture('failing-build');
|
||
const output = await execute([deploymentPath, '--confirm']);
|
||
|
||
console.log(output.stderr);
|
||
console.log(output.stdout);
|
||
console.log(output.exitCode);
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.regex(output.stderr, /Build failed/gm, formatOutput(output));
|
||
});
|
||
|
||
test('invalid deployment, projects and alias names', async t => {
|
||
const check = async (...args) => {
|
||
const output = await execute(args);
|
||
|
||
console.log(output.stderr);
|
||
console.log(output.stdout);
|
||
console.log(output.exitCode);
|
||
|
||
const print = `\`${args.join(' ')}\`\n${formatOutput(output)}`;
|
||
t.is(output.exitCode, 1, print);
|
||
t.regex(output.stderr, /The provided argument/gm, print);
|
||
};
|
||
|
||
await Promise.all([
|
||
check('alias', '/', 'test'),
|
||
check('alias', 'test', '/'),
|
||
check('rm', '/'),
|
||
check('ls', '/'),
|
||
]);
|
||
});
|
||
|
||
test('now certs ls', async t => {
|
||
const output = await execute(['certs', 'ls']);
|
||
|
||
console.log(output.stderr);
|
||
console.log(output.stdout);
|
||
console.log(output.exitCode);
|
||
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
t.regex(output.stderr, /certificates? found under/gm, formatOutput(output));
|
||
});
|
||
|
||
test('now certs ls --next=123456', async t => {
|
||
const output = await execute(['certs', 'ls', '--next=123456']);
|
||
|
||
console.log(output.stderr);
|
||
console.log(output.stdout);
|
||
console.log(output.exitCode);
|
||
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
t.regex(output.stderr, /No certificates found under/gm, formatOutput(output));
|
||
});
|
||
|
||
test('now hasOwnProperty not a valid subcommand', async t => {
|
||
const output = await execute(['hasOwnProperty']);
|
||
|
||
console.log(output.stderr);
|
||
console.log(output.stdout);
|
||
console.log(output.exitCode);
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.regex(
|
||
output.stderr,
|
||
/The specified file or directory "hasOwnProperty" does not exist/gm,
|
||
formatOutput(output)
|
||
);
|
||
});
|
||
|
||
test('create zero-config deployment', async t => {
|
||
const fixturePath = fixture('zero-config-next-js');
|
||
const output = await execute([
|
||
fixturePath,
|
||
'--force',
|
||
'--public',
|
||
'--confirm',
|
||
]);
|
||
|
||
console.log('isCanary', isCanary);
|
||
console.log(output.stderr);
|
||
console.log(output.stdout);
|
||
console.log(output.exitCode);
|
||
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
|
||
const { host } = new URL(output.stdout);
|
||
const response = await apiFetch(`/v10/now/deployments/unkown?url=${host}`);
|
||
|
||
const text = await response.text();
|
||
|
||
t.is(response.status, 200, text);
|
||
const data = JSON.parse(text);
|
||
|
||
t.is(data.error, undefined, JSON.stringify(data, null, 2));
|
||
|
||
const validBuilders = data.builds.every(build =>
|
||
isCanary ? build.use.endsWith('@canary') : !build.use.endsWith('@canary')
|
||
);
|
||
|
||
t.true(
|
||
validBuilders,
|
||
'Builders are not valid: ' + JSON.stringify(data, null, 2)
|
||
);
|
||
});
|
||
|
||
test('now secret add', async t => {
|
||
context.secretName = `my-secret-${Date.now().toString(36)}`;
|
||
const value = 'https://my-secret-endpoint.com';
|
||
|
||
const output = await execute(['secret', 'add', context.secretName, value]);
|
||
|
||
console.log(output.stderr);
|
||
console.log(output.stdout);
|
||
console.log(output.exitCode);
|
||
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
});
|
||
|
||
test('now secret ls', async t => {
|
||
const output = await execute(['secret', 'ls']);
|
||
|
||
console.log(output.stderr);
|
||
console.log(output.stdout);
|
||
console.log(output.exitCode);
|
||
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
t.regex(output.stdout, /Secrets found under/gm, formatOutput(output));
|
||
t.regex(output.stdout, new RegExp(), formatOutput(output));
|
||
});
|
||
|
||
test('now secret rename', async t => {
|
||
const nextName = `renamed-secret-${Date.now().toString(36)}`;
|
||
const output = await execute([
|
||
'secret',
|
||
'rename',
|
||
context.secretName,
|
||
nextName,
|
||
]);
|
||
|
||
console.log(output.stderr);
|
||
console.log(output.stdout);
|
||
console.log(output.exitCode);
|
||
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
|
||
context.secretName = nextName;
|
||
});
|
||
|
||
test('now secret rm', async t => {
|
||
const output = await execute(['secret', 'rm', context.secretName, '-y']);
|
||
|
||
console.log(output.stderr);
|
||
console.log(output.stdout);
|
||
console.log(output.exitCode);
|
||
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
});
|
||
|
||
test('deploy a Lambda with 128MB of memory', async t => {
|
||
const directory = fixture('lambda-with-128-memory');
|
||
const output = await execute([directory, '--confirm']);
|
||
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
|
||
const { host: url } = new URL(output.stdout);
|
||
const response = await fetch('https://' + url + '/api/memory');
|
||
|
||
t.is(response.status, 200, url);
|
||
|
||
// It won't be exactly 128MB,
|
||
// so we just compare if it is lower than 450MB
|
||
const { memory } = await response.json();
|
||
t.is(memory, 128, `Lambda has ${memory} bytes of memory`);
|
||
});
|
||
|
||
test('fail to deploy a Lambda with an incorrect value for of memory', async t => {
|
||
const directory = fixture('lambda-with-200-memory');
|
||
const output = await execute([directory, '--confirm']);
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.regex(output.stderr, /steps of 64/gm, formatOutput(output));
|
||
t.regex(output.stderr, /More details/gm, formatOutput(output));
|
||
});
|
||
|
||
test('deploy a Lambda with 3 seconds of maxDuration', async t => {
|
||
const directory = fixture('lambda-with-3-second-timeout');
|
||
const output = await execute([directory, '--confirm']);
|
||
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
|
||
const { host: url } = new URL(output.stdout);
|
||
|
||
const [response1, response2] = await Promise.all([
|
||
fetch('https://' + url + '/api/wait-for/2'),
|
||
fetch('https://' + url + '/api/wait-for/4'),
|
||
]);
|
||
|
||
t.is(response1.status, 200, url);
|
||
t.is(response2.status, 502, url);
|
||
});
|
||
|
||
test('fail to deploy a Lambda with an incorrect value for maxDuration', async t => {
|
||
const directory = fixture('lambda-with-1000-second-timeout');
|
||
const output = await execute([directory, '--confirm']);
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.regex(
|
||
output.stderr,
|
||
/maxDuration must be between 1 second and 10 seconds/gm,
|
||
formatOutput(output)
|
||
);
|
||
});
|
||
|
||
test('invalid `--token`', async t => {
|
||
const output = await execute(['--token', 'he\nl,o.']);
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.true(
|
||
output.stderr.includes(
|
||
'Error! You defined "--token", but its contents are invalid. Must not contain: "\\n", ",", "."'
|
||
)
|
||
);
|
||
});
|
||
|
||
// We need to skip this test until `now-php` supports Runtime version 3
|
||
test('deploy a Lambda with a specific runtime', async t => {
|
||
const directory = fixture('lambda-with-php-runtime');
|
||
const output = await execute([directory, '--public', '--confirm']);
|
||
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
|
||
const { host: url } = new URL(output.stdout);
|
||
|
||
const [build] = await getDeploymentBuildsByUrl(url);
|
||
t.is(build.use, 'now-php@0.0.8', JSON.stringify(build, null, 2));
|
||
});
|
||
|
||
test('fail to deploy a Lambda with a specific runtime but without a locked version', async t => {
|
||
const directory = fixture('lambda-with-invalid-runtime');
|
||
const output = await execute([directory, '--confirm']);
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.regex(
|
||
output.stderr,
|
||
/Function Runtimes must have a valid version/gim,
|
||
formatOutput(output)
|
||
);
|
||
});
|
||
|
||
test('ensure `github` and `scope` are not sent to the API', async t => {
|
||
const directory = fixture('github-and-scope-config');
|
||
const output = await execute([directory, '--confirm']);
|
||
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
});
|
||
|
||
test('change user', async t => {
|
||
t.timeout(ms('1m'));
|
||
|
||
const { stdout: prevUser } = await execute(['whoami']);
|
||
|
||
// Delete the current token
|
||
await execute(['logout', '--debug'], { stdio: 'inherit' });
|
||
|
||
await createUser();
|
||
|
||
await execute(['login', email, '--debug'], { stdio: 'inherit' });
|
||
|
||
const auth = await fs.readJSON(getConfigAuthPath());
|
||
|
||
token = auth.token;
|
||
|
||
const { stdout: nextUser } = await execute(['whoami']);
|
||
|
||
console.log('prev user', prevUser);
|
||
console.log('next user', nextUser);
|
||
|
||
t.is(typeof prevUser, 'string', prevUser);
|
||
t.is(typeof nextUser, 'string', nextUser);
|
||
t.not(prevUser, nextUser, JSON.stringify({ prevUser, nextUser }));
|
||
});
|
||
|
||
test('should show prompts to set up project', async t => {
|
||
const directory = fixture('project-link');
|
||
const projectName = `project-link-${
|
||
Math.random()
|
||
.toString(36)
|
||
.split('.')[1]
|
||
}`;
|
||
|
||
// remove previously linked project if it exists
|
||
await remove(path.join(directory, '.vercel'));
|
||
|
||
const now = execa(binaryPath, [directory, ...defaultArgs]);
|
||
|
||
await waitForPrompt(now, chunk => /Set up and deploy [^?]+\?/.test(chunk));
|
||
now.stdin.write('yes\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('Which scope do you want to deploy to?')
|
||
);
|
||
now.stdin.write('\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('Link to existing project?')
|
||
);
|
||
now.stdin.write('no\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('What’s your project’s name?')
|
||
);
|
||
now.stdin.write(`${projectName}\n`);
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('In which directory is your code located?')
|
||
);
|
||
now.stdin.write('\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('Want to override the settings?')
|
||
);
|
||
now.stdin.write('yes\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes(
|
||
'Which settings would you like to overwrite (select multiple)?'
|
||
)
|
||
);
|
||
now.stdin.write('a\n'); // 'a' means select all
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes(`What's your Build Command?`)
|
||
);
|
||
now.stdin.write(`mkdir o && echo '<h1>custom hello</h1>' > o/index.html\n`);
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes(`What's your Output Directory?`)
|
||
);
|
||
now.stdin.write(`o\n`);
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes(`What's your Development Command?`)
|
||
);
|
||
now.stdin.write(`yarn dev\n`);
|
||
|
||
await waitForPrompt(now, chunk => chunk.includes('Linked to'));
|
||
|
||
const output = await now;
|
||
|
||
// Ensure the exit code is right
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
|
||
// Ensure .gitignore is created
|
||
t.is(
|
||
(await readFile(path.join(directory, '.gitignore'))).toString(),
|
||
'.vercel'
|
||
);
|
||
|
||
// Ensure .vercel/project.json and .vercel/README.txt are created
|
||
t.is(
|
||
await exists(path.join(directory, '.vercel', 'project.json')),
|
||
true,
|
||
'project.json should be created'
|
||
);
|
||
t.is(
|
||
await exists(path.join(directory, '.vercel', 'README.txt')),
|
||
true,
|
||
'README.txt should be created'
|
||
);
|
||
|
||
// Send a test request to the deployment
|
||
const response = await fetch(new URL(output.stdout).href);
|
||
const text = await response.text();
|
||
t.is(text.includes('<h1>custom hello</h1>'), true, text);
|
||
});
|
||
|
||
test('should prefill "project name" prompt with folder name', async t => {
|
||
const projectName = `static-deployment-${
|
||
Math.random()
|
||
.toString(36)
|
||
.split('.')[1]
|
||
}`;
|
||
|
||
const src = fixture('static-deployment');
|
||
|
||
// remove previously linked project if it exists
|
||
await remove(path.join(src, '.vercel'));
|
||
|
||
const directory = path.join(src, '../', projectName);
|
||
await copy(src, directory);
|
||
|
||
const now = execa(binaryPath, [directory, ...defaultArgs]);
|
||
|
||
await waitForPrompt(now, chunk => /Set up and deploy [^?]+\?/.test(chunk));
|
||
now.stdin.write('yes\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('Which scope do you want to deploy to?')
|
||
);
|
||
now.stdin.write('\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('Link to existing project?')
|
||
);
|
||
now.stdin.write('no\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes(`What’s your project’s name? (${projectName})`)
|
||
);
|
||
now.stdin.write(`\n`);
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('In which directory is your code located?')
|
||
);
|
||
now.stdin.write('\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('Want to override the settings?')
|
||
);
|
||
now.stdin.write('no\n');
|
||
|
||
const output = await now;
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
});
|
||
|
||
test('should prefill "project name" prompt with --name', async t => {
|
||
const directory = fixture('static-deployment');
|
||
const projectName = `static-deployment-${
|
||
Math.random()
|
||
.toString(36)
|
||
.split('.')[1]
|
||
}`;
|
||
|
||
// remove previously linked project if it exists
|
||
await remove(path.join(directory, '.vercel'));
|
||
|
||
const now = execa(binaryPath, [
|
||
directory,
|
||
'--name',
|
||
projectName,
|
||
...defaultArgs,
|
||
]);
|
||
|
||
let isDeprecated = false;
|
||
|
||
await waitForPrompt(now, chunk => {
|
||
if (chunk.includes('The "--name" flag is deprecated')) {
|
||
isDeprecated = true;
|
||
}
|
||
|
||
return /Set up and deploy [^?]+\?/.test(chunk);
|
||
});
|
||
now.stdin.write('yes\n');
|
||
|
||
t.is(isDeprecated, true);
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('Which scope do you want to deploy to?')
|
||
);
|
||
now.stdin.write('\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('Link to existing project?')
|
||
);
|
||
now.stdin.write('no\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes(`What’s your project’s name? (${projectName})`)
|
||
);
|
||
now.stdin.write(`\n`);
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('In which directory is your code located?')
|
||
);
|
||
now.stdin.write('\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('Want to override the settings?')
|
||
);
|
||
now.stdin.write('no\n');
|
||
|
||
const output = await now;
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
});
|
||
|
||
test('should prefill "project name" prompt with now.json `name`', async t => {
|
||
const directory = fixture('static-deployment');
|
||
const projectName = `static-deployment-${
|
||
Math.random()
|
||
.toString(36)
|
||
.split('.')[1]
|
||
}`;
|
||
|
||
// remove previously linked project if it exists
|
||
await remove(path.join(directory, '.vercel'));
|
||
await fs.writeFile(
|
||
path.join(directory, 'vercel.json'),
|
||
JSON.stringify({
|
||
name: projectName,
|
||
})
|
||
);
|
||
|
||
const now = execa(binaryPath, [directory, ...defaultArgs]);
|
||
|
||
let isDeprecated = false;
|
||
|
||
now.stderr.on('data', data => {
|
||
if (
|
||
data
|
||
.toString()
|
||
.includes('The `name` property in vercel.json is deprecated')
|
||
) {
|
||
isDeprecated = true;
|
||
}
|
||
});
|
||
|
||
await waitForPrompt(now, chunk => {
|
||
return /Set up and deploy [^?]+\?/.test(chunk);
|
||
});
|
||
now.stdin.write('yes\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('Which scope do you want to deploy to?')
|
||
);
|
||
now.stdin.write('\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('Link to existing project?')
|
||
);
|
||
now.stdin.write('no\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes(`What’s your project’s name? (${projectName})`)
|
||
);
|
||
now.stdin.write(`\n`);
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('In which directory is your code located?')
|
||
);
|
||
now.stdin.write('\n');
|
||
|
||
await waitForPrompt(now, chunk =>
|
||
chunk.includes('Want to override the settings?')
|
||
);
|
||
now.stdin.write('no\n');
|
||
|
||
const output = await now;
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
|
||
t.is(isDeprecated, true);
|
||
|
||
// clean up
|
||
await remove(path.join(directory, 'vercel.json'));
|
||
});
|
||
|
||
test('deploy with unknown `VERCEL_PROJECT_ID` should fail', async t => {
|
||
const directory = fixture('static-deployment');
|
||
const user = await fetchTokenInformation(token);
|
||
|
||
const output = await execute([directory], {
|
||
env: {
|
||
VERCEL_ORG_ID: user.uid,
|
||
VERCEL_PROJECT_ID: 'asdf',
|
||
},
|
||
});
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.is(output.stderr.includes('Project not found'), true, formatOutput(output));
|
||
});
|
||
|
||
test('deploy with `VERCEL_ORG_ID` but without `VERCEL_PROJECT_ID` should fail', async t => {
|
||
const directory = fixture('static-deployment');
|
||
const user = await fetchTokenInformation(token);
|
||
|
||
const output = await execute([directory], {
|
||
env: { VERCEL_ORG_ID: user.uid },
|
||
});
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.is(
|
||
output.stderr.includes(
|
||
'You specified `VERCEL_ORG_ID` but you forgot to specify `VERCEL_PROJECT_ID`. You need to specify both to deploy to a custom project.'
|
||
),
|
||
true,
|
||
formatOutput(output)
|
||
);
|
||
});
|
||
|
||
test('deploy with `VERCEL_PROJECT_ID` but without `VERCEL_ORG_ID` should fail', async t => {
|
||
const directory = fixture('static-deployment');
|
||
|
||
const output = await execute([directory], {
|
||
env: { VERCEL_PROJECT_ID: 'asdf' },
|
||
});
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.is(
|
||
output.stderr.includes(
|
||
'You specified `VERCEL_PROJECT_ID` but you forgot to specify `VERCEL_ORG_ID`. You need to specify both to deploy to a custom project.'
|
||
),
|
||
true,
|
||
formatOutput(output)
|
||
);
|
||
});
|
||
|
||
test('deploy with `VERCEL_ORG_ID` and `VERCEL_PROJECT_ID`', async t => {
|
||
const directory = fixture('static-deployment');
|
||
|
||
// generate `.vercel`
|
||
await execute([directory, '--confirm']);
|
||
|
||
const link = require(path.join(directory, '.vercel/project.json'));
|
||
await remove(path.join(directory, '.vercel'));
|
||
|
||
const output = await execute([directory], {
|
||
env: {
|
||
VERCEL_ORG_ID: link.orgId,
|
||
VERCEL_PROJECT_ID: link.projectId,
|
||
},
|
||
});
|
||
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
t.is(output.stdout.includes('Linked to'), false);
|
||
});
|
||
|
||
test('deploy shows notice when project in `.vercel` does not exists', async t => {
|
||
const directory = fixture('static-deployment');
|
||
|
||
// overwrite .vercel with unexisting project
|
||
await ensureDir(path.join(directory, '.vercel'));
|
||
await writeFile(
|
||
path.join(directory, '.vercel/project.json'),
|
||
JSON.stringify({
|
||
orgId: 'asdf',
|
||
projectId: 'asdf',
|
||
})
|
||
);
|
||
|
||
const now = execute([directory]);
|
||
|
||
let detectedNotice = false;
|
||
|
||
// kill after first prompt
|
||
await waitForPrompt(now, chunk => {
|
||
detectedNotice =
|
||
detectedNotice ||
|
||
chunk.includes(
|
||
'Your project was either removed from Vercel or you’re not a member of it anymore'
|
||
);
|
||
|
||
return /Set up and deploy [^?]+\?/.test(chunk);
|
||
});
|
||
now.stdin.write('no\n');
|
||
|
||
t.is(detectedNotice, true, 'did not detect notice');
|
||
});
|
||
|
||
test('use `rootDirectory` from project when deploying', async t => {
|
||
const directory = fixture('project-root-directory');
|
||
|
||
const firstResult = await execute([directory, '--confirm', '--public']);
|
||
t.is(firstResult.exitCode, 0, formatOutput(firstResult));
|
||
|
||
const { host: firstHost } = new URL(firstResult.stdout);
|
||
const response = await apiFetch(`/v12/now/deployments/get?url=${firstHost}`);
|
||
t.is(response.status, 200);
|
||
const { projectId } = await response.json();
|
||
t.is(typeof projectId, 'string', projectId);
|
||
|
||
const projectResponse = await apiFetch(`/v2/projects/${projectId}`, {
|
||
method: 'PATCH',
|
||
body: JSON.stringify({
|
||
rootDirectory: 'src',
|
||
}),
|
||
});
|
||
console.log('response', await projectResponse.text());
|
||
t.is(projectResponse.status, 200);
|
||
|
||
const secondResult = await execute([directory, '--public']);
|
||
t.is(secondResult.exitCode, 0, formatOutput(secondResult));
|
||
|
||
const pageResponse1 = await fetch(secondResult.stdout);
|
||
t.is(pageResponse1.status, 200);
|
||
t.regex(await pageResponse1.text(), /I am a website/gm);
|
||
|
||
// Ensures that the `now.json` file has been applied
|
||
const pageResponse2 = await fetch(`${secondResult.stdout}/i-do-exist`);
|
||
t.is(pageResponse2.status, 200);
|
||
t.regex(await pageResponse2.text(), /I am a website/gm);
|
||
|
||
await apiFetch(`/v2/projects/${projectId}`, {
|
||
method: 'DELETE',
|
||
});
|
||
});
|
||
|
||
test('now deploy with unknown `VERCEL_ORG_ID` or `VERCEL_PROJECT_ID` should error', async t => {
|
||
const output = await execute(['deploy'], {
|
||
env: { VERCEL_ORG_ID: 'asdf', VERCEL_PROJECT_ID: 'asdf' },
|
||
});
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.is(output.stderr.includes('Project not found'), true, formatOutput(output));
|
||
});
|
||
|
||
test('now env with unknown `VERCEL_ORG_ID` or `VERCEL_PROJECT_ID` should error', async t => {
|
||
const output = await execute(['env'], {
|
||
env: { VERCEL_ORG_ID: 'asdf', VERCEL_PROJECT_ID: 'asdf' },
|
||
});
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.is(output.stderr.includes('Project not found'), true, formatOutput(output));
|
||
});
|
||
|
||
test('whoami with `VERCEL_ORG_ID` should favor `--scope` and should error', async t => {
|
||
const user = await fetchTokenInformation(token);
|
||
|
||
const output = await execute(['whoami', '--scope', 'asdf'], {
|
||
env: { VERCEL_ORG_ID: user.uid },
|
||
});
|
||
|
||
t.is(output.exitCode, 1, formatOutput(output));
|
||
t.is(
|
||
output.stderr.includes('The specified scope does not exist'),
|
||
true,
|
||
formatOutput(output)
|
||
);
|
||
});
|
||
|
||
test('whoami with local .vercel scope', async t => {
|
||
const directory = fixture('static-deployment');
|
||
const user = await fetchTokenInformation(token);
|
||
|
||
// create local .vercel
|
||
await ensureDir(path.join(directory, '.vercel'));
|
||
await fs.writeFile(
|
||
path.join(directory, '.vercel', 'project.json'),
|
||
JSON.stringify({ orgId: user.uid, projectId: 'xxx' })
|
||
);
|
||
|
||
const output = await execute(['whoami'], {
|
||
cwd: directory,
|
||
});
|
||
|
||
t.is(output.exitCode, 0, formatOutput(output));
|
||
t.is(output.stdout.includes(contextName), true, formatOutput(output));
|
||
|
||
// clean up
|
||
await remove(path.join(directory, '.vercel'));
|
||
});
|