mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-24 19:00:03 +00:00
Compare commits
62 Commits
@now/next@
...
@now/next@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21337de7cd | ||
|
|
a6686f9ff5 | ||
|
|
65c621bd55 | ||
|
|
0827d3514d | ||
|
|
7bc5d9fb5b | ||
|
|
c53106ecee | ||
|
|
91e4d18ab8 | ||
|
|
33cd78b93a | ||
|
|
a765d27e5a | ||
|
|
e65ff4bfd5 | ||
|
|
30a4787390 | ||
|
|
36f6f1db77 | ||
|
|
8043e25d6d | ||
|
|
73b4e7aea4 | ||
|
|
de7e063c9b | ||
|
|
e7c30deee8 | ||
|
|
7464fac792 | ||
|
|
252363cce4 | ||
|
|
d7dceeb2a1 | ||
|
|
6b5b9e8b2f | ||
|
|
addd036149 | ||
|
|
7ca672a470 | ||
|
|
3af0e99689 | ||
|
|
b14068de8a | ||
|
|
c9437e714a | ||
|
|
c0aff5cf4a | ||
|
|
9110b14bb4 | ||
|
|
40e4b69267 | ||
|
|
6c4934dcc4 | ||
|
|
d64e54d61a | ||
|
|
0b75bf07f7 | ||
|
|
a861a8f3f7 | ||
|
|
6407b17b7f | ||
|
|
558463a988 | ||
|
|
1a133995b8 | ||
|
|
a83eecf674 | ||
|
|
a932ed65fa | ||
|
|
e2ae497762 | ||
|
|
89989719c2 | ||
|
|
8166b8e1e7 | ||
|
|
1ceeac498c | ||
|
|
1c47d1360d | ||
|
|
ddcd0918e9 | ||
|
|
573b6b8110 | ||
|
|
40039d7f9b | ||
|
|
dcb37e92f5 | ||
|
|
fe7f875549 | ||
|
|
a516ed6fb8 | ||
|
|
ca2c5f85ef | ||
|
|
adb5a01cc0 | ||
|
|
6b4d39ab4d | ||
|
|
07ce3d2e34 | ||
|
|
93ffcf487b | ||
|
|
3631f0f4cf | ||
|
|
b67b5be8a9 | ||
|
|
bf67b1a29e | ||
|
|
ed86473f74 | ||
|
|
399a3cd114 | ||
|
|
d0fd09810a | ||
|
|
f298f2e894 | ||
|
|
569200ae0e | ||
|
|
c91495338d |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,5 +1,8 @@
|
||||
# Ignore test fixtures in GitHub Languages
|
||||
# See https://github.com/github/linguist#vendored-code
|
||||
examples/* linguist-vendored
|
||||
utils/* linguist-vendored
|
||||
test/* linguist-vendored
|
||||
packages/*/test/* linguist-vendored
|
||||
|
||||
# Go build fails with Windows line endings.
|
||||
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -8,6 +8,7 @@
|
||||
/packages/now-cli/src/util/dev/ @tootallnate @leo @styfle @AndyBitz
|
||||
/packages/now-cli/src/commands/domains/ @javivelasco @mglagola @anatrajkovska
|
||||
/packages/now-cli/src/commands/certs/ @javivelasco @mglagola @anatrajkovska
|
||||
/packages/now-cli/src/commands/env @styfle @lucleray
|
||||
/packages/now-client @leo @rdev
|
||||
/packages/now-build-utils @styfle @AndyBitz
|
||||
/packages/now-node @styfle @tootallnate @lucleray
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -23,5 +23,5 @@ packages/now-cli/test/fixtures/integration
|
||||
test/lib/deployment/failed-page.txt
|
||||
.DS_Store
|
||||
.next
|
||||
.env
|
||||
public
|
||||
/.env
|
||||
/public
|
||||
|
||||
@@ -8,7 +8,7 @@ module.exports = {
|
||||
resolve: `gatsby-plugin-manifest`,
|
||||
options: {
|
||||
name: 'Gatsby + Node.js (TypeScript) API',
|
||||
short_name: 'Gatbsy + Node.js (TypeScript)',
|
||||
short_name: 'Gatsby + Node.js (TypeScript)',
|
||||
start_url: '/',
|
||||
icon: 'src/images/gatsby-icon.png',
|
||||
},
|
||||
|
||||
BIN
examples/ionic-react/public/assets/icon/favicon.png
Normal file
BIN
examples/ionic-react/public/assets/icon/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 930 B |
BIN
examples/ionic-react/public/assets/icon/icon.png
Normal file
BIN
examples/ionic-react/public/assets/icon/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
1
examples/ionic-react/public/assets/shapes.svg
Normal file
1
examples/ionic-react/public/assets/shapes.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
30
examples/ionic-react/public/index.html
Normal file
30
examples/ionic-react/public/index.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Ionic App</title>
|
||||
|
||||
<base href="/" />
|
||||
|
||||
<meta
|
||||
name="viewport"
|
||||
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="msapplication-tap-highlight" content="no" />
|
||||
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="%PUBLIC_URL%/assets/icon/favicon.png" />
|
||||
|
||||
<!-- add to homescreen for ios -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-title" content="Ionic App" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
21
examples/ionic-react/public/manifest.json
Normal file
21
examples/ionic-react/public/manifest.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"short_name": "Ionic App",
|
||||
"name": "My Ionic App",
|
||||
"icons": [
|
||||
{
|
||||
"src": "assets/icon/favicon.png",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "assets/icon/icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -8,8 +8,8 @@
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "9.2.2",
|
||||
"react": "16.13.0",
|
||||
"react-dom": "16.13.0"
|
||||
"next": "^9.3.3",
|
||||
"react": "^16.13.0",
|
||||
"react-dom": "^16.13.0"
|
||||
}
|
||||
}
|
||||
|
||||
4
now.json
4
now.json
@@ -9,10 +9,6 @@
|
||||
"destination": "/api/frameworks"
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
"GITHUB_ACCESS_TOKEN": "@now-api-examples-github-token",
|
||||
"SENTRY_DSN": "@sentry-product-dsn"
|
||||
},
|
||||
"github": {
|
||||
"silent": true,
|
||||
"autoJobCancelation": true
|
||||
|
||||
@@ -59,9 +59,6 @@
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"signal-exit": "TooTallNate/signal-exit#update/sighub-to-sigint-on-windows"
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true
|
||||
|
||||
@@ -309,6 +309,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Ionic Angular",
|
||||
"slug": "ionic-angular",
|
||||
"demo": "https://ionic-angular.now-examples.now.sh",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic.svg",
|
||||
"tagline": "Ionic Angular allows you to build mobile PWAs with Angular and the Ionic Framework.",
|
||||
"description": "An Ionic Angular site, created with the Ionic CLI.",
|
||||
"website": "https://ionicframework.com",
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@ionic\\/angular\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"placeholder": "`npm run build` or `ng build`"
|
||||
},
|
||||
"devCommand": {
|
||||
"value": "ng start"
|
||||
},
|
||||
"outputDirectory": {
|
||||
"value": "www"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Angular",
|
||||
"slug": "angular",
|
||||
@@ -397,7 +425,7 @@
|
||||
"name": "Ionic React",
|
||||
"slug": "ionic-react",
|
||||
"demo": "https://ionic-react.now-examples.now.sh",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic-react.svg",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic.svg",
|
||||
"tagline": "Ionic React allows you to build mobile PWAs with React and the Ionic Framework.",
|
||||
"description": "An Ionic React site, created with the Ionic CLI.",
|
||||
"website": "https://ionicframework.com",
|
||||
@@ -411,13 +439,13 @@
|
||||
},
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"placeholder": "npm run build"
|
||||
"placeholder": "`npm run build` or `react-scripts build`"
|
||||
},
|
||||
"devCommand": {
|
||||
"value": "stencil build --dev --watch --serve --port $PORT"
|
||||
"value": "react-scripts start"
|
||||
},
|
||||
"outputDirectory": {
|
||||
"value": "public"
|
||||
"value": "build"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
Before Width: | Height: | Size: 1003 B After Width: | Height: | Size: 1003 B |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/frameworks",
|
||||
"version": "0.0.13-canary.0",
|
||||
"version": "0.0.13",
|
||||
"main": "frameworks.json",
|
||||
"license": "UNLICENSED"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/build-utils",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
@@ -190,7 +190,12 @@ export async function detectBuilders(
|
||||
hasNextApiFiles = true;
|
||||
}
|
||||
|
||||
if (!fallbackEntrypoint && buildCommand && !fileName.includes('/')) {
|
||||
if (
|
||||
!fallbackEntrypoint &&
|
||||
buildCommand &&
|
||||
!fileName.includes('/') &&
|
||||
fileName !== 'now.json'
|
||||
) {
|
||||
fallbackEntrypoint = fileName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,18 @@ import spawn from 'cross-spawn';
|
||||
import { SpawnOptions } from 'child_process';
|
||||
import { deprecate } from 'util';
|
||||
import { cpus } from 'os';
|
||||
import { NowBuildError } from '../errors';
|
||||
import { Meta, PackageJson, NodeVersion, Config } from '../types';
|
||||
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
|
||||
|
||||
interface SpawnOptionsExtended extends SpawnOptions {
|
||||
prettyCommand?: string;
|
||||
}
|
||||
|
||||
export function spawnAsync(
|
||||
command: string,
|
||||
args: string[],
|
||||
opts: SpawnOptions = {}
|
||||
opts: SpawnOptionsExtended = {}
|
||||
) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const stderrLogs: Buffer[] = [];
|
||||
@@ -29,12 +34,18 @@ export function spawnAsync(
|
||||
return resolve();
|
||||
}
|
||||
|
||||
const errorLogs = stderrLogs.map(line => line.toString()).join('');
|
||||
if (opts.stdio !== 'inherit') {
|
||||
reject(new Error(`Exited with ${code || signal}\n${errorLogs}`));
|
||||
} else {
|
||||
reject(new Error(`Exited with ${code || signal}`));
|
||||
}
|
||||
const cmd = opts.prettyCommand
|
||||
? `Command "${opts.prettyCommand}"`
|
||||
: 'Command';
|
||||
reject(
|
||||
new NowBuildError({
|
||||
code: `NOW_BUILD_UTILS_SPAWN_${code || signal}`,
|
||||
message:
|
||||
opts.stdio === 'inherit'
|
||||
? `${cmd} exited with ${code || signal}`
|
||||
: stderrLogs.map(line => line.toString()).join(''),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -42,7 +53,7 @@ export function spawnAsync(
|
||||
export function execAsync(
|
||||
command: string,
|
||||
args: string[],
|
||||
opts: SpawnOptions = {}
|
||||
opts: SpawnOptionsExtended = {}
|
||||
) {
|
||||
return new Promise<{ stdout: string; stderr: string; code: number }>(
|
||||
(resolve, reject) => {
|
||||
@@ -64,10 +75,15 @@ export function execAsync(
|
||||
child.on('error', reject);
|
||||
child.on('close', (code, signal) => {
|
||||
if (code !== 0) {
|
||||
const cmd = opts.prettyCommand
|
||||
? `Command "${opts.prettyCommand}"`
|
||||
: 'Command';
|
||||
|
||||
return reject(
|
||||
new Error(
|
||||
`Program "${command}" exited with non-zero exit code ${code} ${signal}.`
|
||||
)
|
||||
new NowBuildError({
|
||||
code: `NOW_BUILD_UTILS_EXEC_${code || signal}`,
|
||||
message: `${cmd} exited with ${code || signal}`,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -82,18 +98,20 @@ export function execAsync(
|
||||
}
|
||||
|
||||
export function spawnCommand(command: string, options: SpawnOptions = {}) {
|
||||
const opts = { ...options, prettyCommand: command };
|
||||
if (process.platform === 'win32') {
|
||||
return spawn('cmd.exe', ['/C', command], options);
|
||||
return spawn('cmd.exe', ['/C', command], opts);
|
||||
}
|
||||
|
||||
return spawn('sh', ['-c', command], options);
|
||||
return spawn('sh', ['-c', command], opts);
|
||||
}
|
||||
|
||||
export async function execCommand(command: string, options: SpawnOptions = {}) {
|
||||
const opts = { ...options, prettyCommand: command };
|
||||
if (process.platform === 'win32') {
|
||||
await spawnAsync('cmd.exe', ['/C', command], options);
|
||||
await spawnAsync('cmd.exe', ['/C', command], opts);
|
||||
} else {
|
||||
await spawnAsync('sh', ['-c', command], options);
|
||||
await spawnAsync('sh', ['-c', command], opts);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -120,9 +138,11 @@ export async function runShellScript(
|
||||
assert(path.isAbsolute(fsPath));
|
||||
const destPath = path.dirname(fsPath);
|
||||
await chmodPlusX(fsPath);
|
||||
await spawnAsync(`./${path.basename(fsPath)}`, args, {
|
||||
cwd: destPath,
|
||||
const command = `./${path.basename(fsPath)}`;
|
||||
await spawnAsync(command, args, {
|
||||
...spawnOpts,
|
||||
cwd: destPath,
|
||||
prettyCommand: command,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -249,7 +269,7 @@ export async function runNpmInstall(
|
||||
debug(`Installing to ${destPath}`);
|
||||
|
||||
const { hasPackageLockJson } = await scanParentDirs(destPath);
|
||||
const opts: SpawnOptions = { cwd: destPath, ...spawnOpts };
|
||||
const opts: SpawnOptionsExtended = { cwd: destPath, ...spawnOpts };
|
||||
const env = opts.env ? { ...opts.env } : { ...process.env };
|
||||
delete env.NODE_ENV;
|
||||
opts.env = env;
|
||||
@@ -258,11 +278,13 @@ export async function runNpmInstall(
|
||||
let commandArgs: string[];
|
||||
|
||||
if (hasPackageLockJson) {
|
||||
opts.prettyCommand = 'npm install';
|
||||
command = 'npm';
|
||||
commandArgs = args
|
||||
.filter(a => a !== '--prefer-offline')
|
||||
.concat(['install', '--no-audit', '--unsafe-perm']);
|
||||
} else {
|
||||
opts.prettyCommand = 'yarn install';
|
||||
command = 'yarn';
|
||||
commandArgs = args.concat(['install', '--ignore-engines']);
|
||||
}
|
||||
@@ -285,7 +307,7 @@ export async function runBundleInstall(
|
||||
}
|
||||
|
||||
assert(path.isAbsolute(destPath));
|
||||
const opts = { cwd: destPath, ...spawnOpts };
|
||||
const opts = { ...spawnOpts, cwd: destPath, prettyCommand: 'bundle install' };
|
||||
|
||||
await spawnAsync(
|
||||
'bundle',
|
||||
@@ -313,7 +335,7 @@ export async function runPipInstall(
|
||||
}
|
||||
|
||||
assert(path.isAbsolute(destPath));
|
||||
const opts = { cwd: destPath, ...spawnOpts };
|
||||
const opts = { ...spawnOpts, cwd: destPath, prettyCommand: 'pip3 install' };
|
||||
|
||||
await spawnAsync(
|
||||
'pip3',
|
||||
@@ -340,18 +362,22 @@ export async function runPackageJsonScript(
|
||||
);
|
||||
if (!hasScript) return false;
|
||||
|
||||
const opts = { cwd: destPath, ...spawnOpts };
|
||||
|
||||
if (hasPackageLockJson) {
|
||||
console.log(`Running "npm run ${scriptName}"`);
|
||||
await spawnAsync('npm', ['run', scriptName], opts);
|
||||
const prettyCommand = `npm run ${scriptName}`;
|
||||
console.log(`Running "${prettyCommand}"`);
|
||||
await spawnAsync('npm', ['run', scriptName], {
|
||||
...spawnOpts,
|
||||
cwd: destPath,
|
||||
prettyCommand,
|
||||
});
|
||||
} else {
|
||||
console.log(`Running "yarn run ${scriptName}"`);
|
||||
await spawnAsync(
|
||||
'yarn',
|
||||
['--ignore-engines', '--cwd', destPath, 'run', scriptName],
|
||||
opts
|
||||
);
|
||||
const prettyCommand = `yarn run ${scriptName}`;
|
||||
console.log(`Running "${prettyCommand}"`);
|
||||
await spawnAsync('yarn', ['--ignore-engines', 'run', scriptName], {
|
||||
...spawnOpts,
|
||||
cwd: destPath,
|
||||
prettyCommand,
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -7,6 +7,19 @@ import {
|
||||
} from '../';
|
||||
|
||||
describe('Test `detectBuilders`', () => {
|
||||
it('should never select now.json src', async () => {
|
||||
const files = ['docs/index.md', 'mkdocs.yml', 'now.json'];
|
||||
const { builders, errors } = await detectBuilders(files, null, {
|
||||
projectSettings: {
|
||||
buildCommand: 'mkdocs build',
|
||||
outputDirectory: 'site',
|
||||
},
|
||||
});
|
||||
expect(errors).toBe(null);
|
||||
expect(builders).toBeDefined();
|
||||
expect(builders![0].src).not.toBe('now.json');
|
||||
});
|
||||
|
||||
it('package.json + no build', async () => {
|
||||
const pkg = { dependencies: { next: '9.0.0' } };
|
||||
const files = ['package.json', 'pages/index.js', 'public/index.html'];
|
||||
@@ -817,6 +830,21 @@ describe('Test `detectBuilders`', () => {
|
||||
|
||||
describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
const featHandleMiss = true;
|
||||
|
||||
it('should never select now.json src', async () => {
|
||||
const files = ['docs/index.md', 'mkdocs.yml', 'now.json'];
|
||||
const { builders, errors } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
projectSettings: {
|
||||
buildCommand: 'mkdocs build',
|
||||
outputDirectory: 'site',
|
||||
},
|
||||
});
|
||||
expect(errors).toBe(null);
|
||||
expect(builders).toBeDefined();
|
||||
expect(builders![0].src).not.toBe('now.json');
|
||||
});
|
||||
|
||||
it('package.json + no build', async () => {
|
||||
const pkg = { dependencies: { next: '9.0.0' } };
|
||||
const files = ['package.json', 'pages/index.js', 'public/index.html'];
|
||||
|
||||
@@ -21,7 +21,6 @@ describe('Test `walkParentDirs`', () => {
|
||||
await walkParentDirs({ base, start, filename });
|
||||
fail('Expected error');
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
deepEqual(
|
||||
(error as Error).message,
|
||||
'Expected "base" to be absolute path'
|
||||
@@ -36,7 +35,6 @@ describe('Test `walkParentDirs`', () => {
|
||||
await walkParentDirs({ base, start, filename });
|
||||
fail('Expected error');
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
deepEqual(
|
||||
(error as Error).message,
|
||||
'Expected "start" to be absolute path'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now",
|
||||
"version": "17.1.2-canary.4",
|
||||
"version": "18.0.1-canary.1",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Now",
|
||||
@@ -13,7 +13,7 @@
|
||||
"scripts": {
|
||||
"preinstall": "node ./scripts/preinstall.js",
|
||||
"test-unit": "nyc ava test/unit.js test/dev-builder.unit.js test/dev-router.unit.js test/dev-server.unit.js --serial --fail-fast --verbose",
|
||||
"test-integration-cli": "ava test/integration.js --serial --fail-fast",
|
||||
"test-integration-cli": "ava test/integration.js --serial --fail-fast --verbose",
|
||||
"test-integration-v1": "ava test/integration-v1.js --serial --fail-fast",
|
||||
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
|
||||
"prepublishOnly": "yarn build",
|
||||
@@ -63,7 +63,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/node": "5.5.0",
|
||||
"@sindresorhus/slugify": "0.10.0",
|
||||
"@sindresorhus/slugify": "0.11.0",
|
||||
"@types/ansi-escapes": "3.0.0",
|
||||
"@types/ansi-regex": "4.0.0",
|
||||
"@types/async-retry": "1.2.1",
|
||||
@@ -141,8 +141,9 @@
|
||||
"micro": "9.1.2",
|
||||
"mime-types": "2.1.24",
|
||||
"minimatch": "3.0.4",
|
||||
"mri": "1.1.0",
|
||||
"mri": "1.1.5",
|
||||
"ms": "2.1.2",
|
||||
"nanoid": "3.0.2",
|
||||
"node-fetch": "2.6.0",
|
||||
"npm-package-arg": "6.1.0",
|
||||
"nyc": "13.2.0",
|
||||
|
||||
@@ -8,6 +8,8 @@ import getAliases from '../../util/alias/get-aliases';
|
||||
import getScope from '../../util/get-scope.ts';
|
||||
import stamp from '../../util/output/stamp.ts';
|
||||
import strlen from '../../util/strlen.ts';
|
||||
import getCommandFlags from '../../util/get-command-flags';
|
||||
import cmd from '../../util/output/cmd.ts';
|
||||
|
||||
export default async function ls(ctx, opts, args, output) {
|
||||
const {
|
||||
@@ -101,9 +103,12 @@ export default async function ls(ctx, opts, args, output) {
|
||||
console.log(printAliasTable(aliases));
|
||||
}
|
||||
|
||||
if (pagination && aliases.length === 20) {
|
||||
if (pagination && pagination.count === 20) {
|
||||
const flags = getCommandFlags(opts, ['_', '--next']);
|
||||
output.log(
|
||||
`To display the next page use the flag --next ${pagination.next}`
|
||||
`To display the next page run ${cmd(
|
||||
`now alias ls${flags} --next ${pagination.next}`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export const latestHelp = () => `
|
||||
'(default)'
|
||||
)}
|
||||
dev Start a local development server
|
||||
env Manages the Environment Variables for your current Project
|
||||
init [example] Initialize an example project
|
||||
ls | list [app] Lists deployments
|
||||
inspect [id] Displays information related to a deployment
|
||||
@@ -28,7 +29,7 @@ export const latestHelp = () => `
|
||||
domains [name] Manages your domain names
|
||||
dns [name] Manages your DNS records
|
||||
certs [cmd] Manages your SSL certificates
|
||||
secrets [name] Manages your secret environment variables
|
||||
secrets [name] Manages your global Secrets, for use in Environment Variables
|
||||
logs [url] Displays the logs for a deployment
|
||||
teams Manages your teams
|
||||
whoami Shows the username of the currently logged in user
|
||||
@@ -46,7 +47,7 @@ export const latestHelp = () => `
|
||||
)} Path to the global ${'`.now`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
-f, --force Force a new deployment even if nothing has changed
|
||||
--force-with-cache Force a new deployment even if nothing has changed but retain build cache
|
||||
--with-cache Retain build cache when using "--force"
|
||||
-t ${chalk.underline('TOKEN')}, --token=${chalk.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
@@ -84,7 +85,7 @@ export const latestHelp = () => `
|
||||
|
||||
${chalk.cyan('$ now /usr/src/project')}
|
||||
|
||||
${chalk.gray('–')} Deploy with environment variables
|
||||
${chalk.gray('–')} Deploy with Environment Variables
|
||||
|
||||
${chalk.cyan('$ now -e NODE_ENV=production -e SECRET=@mysql-secret')}
|
||||
|
||||
@@ -98,7 +99,7 @@ export const latestHelp = () => `
|
||||
|
||||
export const latestArgs = {
|
||||
'--force': Boolean,
|
||||
'--force-with-cache': Boolean,
|
||||
'--with-cache': Boolean,
|
||||
'--public': Boolean,
|
||||
'--no-clipboard': Boolean,
|
||||
'--env': [String],
|
||||
|
||||
@@ -506,7 +506,7 @@ export default async function main(
|
||||
env: deploymentEnv,
|
||||
build: { env: deploymentBuildEnv },
|
||||
forceNew: argv['--force'],
|
||||
forceNewWithCache: argv['--force-with-cache'],
|
||||
withCache: argv['--with-cache'],
|
||||
quiet,
|
||||
wantsPublic: argv['--public'] || localConfig.public,
|
||||
isFile,
|
||||
@@ -666,7 +666,7 @@ export default async function main(
|
||||
if (err instanceof BuildError) {
|
||||
output.error('Build failed');
|
||||
output.error(
|
||||
`Check your logs at ${now.url}/_logs or run ${code(
|
||||
`Check your logs at https://${now.url}/_logs or run ${code(
|
||||
`now logs ${now.url}`,
|
||||
{
|
||||
// Backticks are interpreted as part of the URL, causing CMD+Click
|
||||
|
||||
@@ -93,7 +93,7 @@ let paths: string[];
|
||||
|
||||
// Options
|
||||
let forceNew: boolean;
|
||||
let forceNewWithCache: boolean;
|
||||
let withCache: boolean;
|
||||
let deploymentName: string;
|
||||
let sessionAffinity: string;
|
||||
let log: any;
|
||||
@@ -239,7 +239,7 @@ export default async function main(
|
||||
|
||||
// Options
|
||||
forceNew = argv.force;
|
||||
forceNewWithCache = argv['force-with-cache'];
|
||||
withCache = argv['with-cache'];
|
||||
deploymentName = argv.name;
|
||||
sessionAffinity = argv['session-affinity'];
|
||||
debugEnabled = argv.debug;
|
||||
@@ -743,7 +743,7 @@ async function sync({
|
||||
meta: metadata,
|
||||
followSymlinks,
|
||||
forceNew,
|
||||
forceNewWithCache,
|
||||
withCache,
|
||||
forwardNpm,
|
||||
quiet,
|
||||
scale,
|
||||
|
||||
@@ -37,7 +37,7 @@ export default async function dev(
|
||||
// retrieve dev command
|
||||
const [link, frameworks] = await Promise.all([
|
||||
getLinkedProject(output, client, cwd),
|
||||
getFrameworks(),
|
||||
getFrameworks(client),
|
||||
]);
|
||||
|
||||
if (link.status === 'error') {
|
||||
|
||||
@@ -9,6 +9,8 @@ import stamp from '../../util/output/stamp';
|
||||
import strlen from '../../util/strlen';
|
||||
import { Output } from '../../util/output';
|
||||
import { Domain, NowContext } from '../../types';
|
||||
import getCommandFlags from '../../util/get-command-flags';
|
||||
import cmd from '../../util/output/cmd';
|
||||
|
||||
type Options = {
|
||||
'--debug': boolean;
|
||||
@@ -68,9 +70,12 @@ export default async function ls(
|
||||
console.log(`${formatDomainsTable(domains)}\n`);
|
||||
}
|
||||
|
||||
if (pagination && domains.length === 20) {
|
||||
if (pagination && pagination.count === 20) {
|
||||
const flags = getCommandFlags(opts, ['_', '--next']);
|
||||
output.log(
|
||||
`To display the next page use the flag --next ${pagination.next}`
|
||||
`To display the next page run ${cmd(
|
||||
`now domains ls${flags} --next ${pagination.next}`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
172
packages/now-cli/src/commands/env/add.ts
vendored
Normal file
172
packages/now-cli/src/commands/env/add.ts
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import { NowContext, ProjectEnvTarget } from '../../types';
|
||||
import { Output } from '../../util/output';
|
||||
import Client from '../../util/client';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import { getLinkedProject } from '../../util/projects/link';
|
||||
import addEnvRecord from '../../util/env/add-env-record';
|
||||
import getEnvVariables from '../../util/env/get-env-records';
|
||||
import {
|
||||
isValidEnvTarget,
|
||||
getEnvTargetPlaceholder,
|
||||
getEnvTargetChoices,
|
||||
} from '../../util/env/env-target';
|
||||
import readStandardInput from '../../util/input/read-standard-input';
|
||||
import cmd from '../../util/output/cmd';
|
||||
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';
|
||||
|
||||
type Options = {
|
||||
'--debug': boolean;
|
||||
};
|
||||
|
||||
export default async function add(
|
||||
ctx: NowContext,
|
||||
opts: Options,
|
||||
args: string[],
|
||||
output: Output
|
||||
) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
const debug = opts['--debug'];
|
||||
const client = new Client({ apiUrl, token, currentTeam, debug });
|
||||
const link = await getLinkedProject(output, client);
|
||||
|
||||
if (link.status === 'error') {
|
||||
return link.exitCode;
|
||||
} else if (link.status === 'not_linked') {
|
||||
output.print(
|
||||
`${chalk.red(
|
||||
'Error!'
|
||||
)} Your codebase isn’t linked to a project on ZEIT Now. Run ${cmd(
|
||||
'now'
|
||||
)} to link it.\n`
|
||||
);
|
||||
return 1;
|
||||
} else {
|
||||
const { project } = link;
|
||||
const stdInput = await readStandardInput();
|
||||
let [envName, envTarget] = args;
|
||||
|
||||
if (args.length > 2) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${cmd(
|
||||
`now env add <name> ${getEnvTargetPlaceholder()}`
|
||||
)}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (stdInput && (!envName || !envTarget)) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${cmd(
|
||||
`now env add <name> <target> < <file>`
|
||||
)}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
let envTargets: ProjectEnvTarget[] = [];
|
||||
if (envTarget) {
|
||||
if (!isValidEnvTarget(envTarget)) {
|
||||
output.error(
|
||||
`The Environment ${param(
|
||||
envTarget
|
||||
)} is invalid. It must be one of: ${getEnvTargetPlaceholder()}.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
envTargets.push(envTarget);
|
||||
}
|
||||
|
||||
while (!envName) {
|
||||
const { inputName } = await inquirer.prompt({
|
||||
type: 'input',
|
||||
name: 'inputName',
|
||||
message: `What’s the name of the variable?`,
|
||||
});
|
||||
|
||||
envName = inputName;
|
||||
|
||||
if (!inputName) {
|
||||
output.error('Name cannot be empty');
|
||||
}
|
||||
}
|
||||
|
||||
const envs = await getEnvVariables(output, client, project.id);
|
||||
const existing = new Set(
|
||||
envs.filter(r => r.key === envName).map(r => r.target)
|
||||
);
|
||||
const choices = getEnvTargetChoices().filter(c => !existing.has(c.value));
|
||||
|
||||
if (choices.length === 0) {
|
||||
output.error(
|
||||
`The variable ${param(
|
||||
envName
|
||||
)} has already been added to all Environments. To remove, run ${cmd(
|
||||
`now env rm ${envName}`
|
||||
)}.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
let envValue: string;
|
||||
|
||||
if (stdInput) {
|
||||
envValue = stdInput;
|
||||
} else {
|
||||
const { inputValue } = await inquirer.prompt({
|
||||
type: 'password',
|
||||
name: 'inputValue',
|
||||
message: `What’s the value of ${envName}?`,
|
||||
});
|
||||
envValue = inputValue || '';
|
||||
}
|
||||
|
||||
while (envTargets.length === 0) {
|
||||
const { inputTargets } = await inquirer.prompt({
|
||||
name: 'inputTargets',
|
||||
type: 'checkbox',
|
||||
message: `Add ${envName} to which Environments (select multiple)?`,
|
||||
choices,
|
||||
});
|
||||
|
||||
envTargets = inputTargets;
|
||||
|
||||
if (inputTargets.length === 0) {
|
||||
output.error('Please select at least one Environment');
|
||||
}
|
||||
}
|
||||
|
||||
const addStamp = stamp();
|
||||
try {
|
||||
await withSpinner('Saving', () =>
|
||||
addEnvRecord(output, client, project.id, envName, envValue, envTargets)
|
||||
);
|
||||
} catch (error) {
|
||||
if (isKnownError(error) && error.serverMessage) {
|
||||
output.error(error.serverMessage);
|
||||
return 1;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`Added Environment Variable ${chalk.bold(
|
||||
envName
|
||||
)} to Project ${chalk.bold(project.name)} ${chalk.gray(addStamp())}`,
|
||||
emoji('success')
|
||||
)}\n`
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
114
packages/now-cli/src/commands/env/index.ts
vendored
Normal file
114
packages/now-cli/src/commands/env/index.ts
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { NowContext } from '../../types';
|
||||
import createOutput from '../../util/output';
|
||||
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 handleError from '../../util/handle-error';
|
||||
import logo from '../../util/output/logo';
|
||||
|
||||
import add from './add';
|
||||
import pull from './pull';
|
||||
import ls from './ls';
|
||||
import rm from './rm';
|
||||
|
||||
const help = () => {
|
||||
const placeholder = getEnvTargetPlaceholder();
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} now env`)} [options] <command>
|
||||
|
||||
${chalk.dim('Commands:')}
|
||||
|
||||
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:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||
'FILE'
|
||||
)} Path to the local ${'`now.json`'} file
|
||||
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
|
||||
'DIR'
|
||||
)} Path to the global ${'`.now`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Add a new variable to multiple Environments
|
||||
|
||||
${chalk.cyan('$ now env add <name>')}
|
||||
${chalk.cyan('$ now env add API_TOKEN')}
|
||||
|
||||
${chalk.gray('–')} Add a new variable for a specific Environment
|
||||
|
||||
${chalk.cyan(`$ now env add <name> ${placeholder}`)}
|
||||
${chalk.cyan('$ now env add DB_CONNECTION production')}
|
||||
|
||||
${chalk.gray('–')} Add a new Environment Variable from stdin
|
||||
|
||||
${chalk.cyan(`$ cat <file> | now env add <name> ${placeholder}`)}
|
||||
${chalk.cyan('$ cat ~/.npmrc | now env add NPM_RC preview')}
|
||||
${chalk.cyan('$ now env add DB_PASS production < secret.txt')}
|
||||
|
||||
${chalk.gray('–')} Remove an variable from multiple Environments
|
||||
|
||||
${chalk.cyan('$ now env rm <name>')}
|
||||
${chalk.cyan('$ now env rm API_TOKEN')}
|
||||
|
||||
${chalk.gray('–')} Remove a variable from a specific Environment
|
||||
|
||||
${chalk.cyan(`$ now env rm <name> ${placeholder}`)}
|
||||
${chalk.cyan('$ now env rm NPM_RC preview')}
|
||||
`);
|
||||
};
|
||||
|
||||
const COMMAND_CONFIG = {
|
||||
ls: ['ls', 'list'],
|
||||
add: ['add'],
|
||||
rm: ['rm', 'remove'],
|
||||
pull: ['pull'],
|
||||
};
|
||||
|
||||
export default async function main(ctx: NowContext) {
|
||||
let argv;
|
||||
|
||||
try {
|
||||
argv = getArgs(ctx.argv.slice(2), {
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
return 2;
|
||||
}
|
||||
|
||||
const output = createOutput({ debug: argv['--debug'] });
|
||||
const { subcommand, args } = getSubcommand(argv._.slice(1), COMMAND_CONFIG);
|
||||
switch (subcommand) {
|
||||
case 'ls':
|
||||
return ls(ctx, argv, args, output);
|
||||
case 'add':
|
||||
return add(ctx, argv, args, output);
|
||||
case 'rm':
|
||||
return rm(ctx, argv, args, output);
|
||||
case 'pull':
|
||||
return pull(ctx, argv, args, output);
|
||||
default:
|
||||
output.error(getInvalidSubcommand(COMMAND_CONFIG));
|
||||
help();
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
117
packages/now-cli/src/commands/env/ls.ts
vendored
Normal file
117
packages/now-cli/src/commands/env/ls.ts
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import plural from 'pluralize';
|
||||
import { Output } from '../../util/output';
|
||||
import { ProjectEnvVariable, ProjectEnvTarget, NowContext } from '../../types';
|
||||
import Client from '../../util/client';
|
||||
import formatTable from '../../util/format-table';
|
||||
import getEnvVariables from '../../util/env/get-env-records';
|
||||
import {
|
||||
isValidEnvTarget,
|
||||
getEnvTargetPlaceholder,
|
||||
} from '../../util/env/env-target';
|
||||
import { getLinkedProject } from '../../util/projects/link';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import cmd from '../../util/output/cmd';
|
||||
import param from '../../util/output/param';
|
||||
|
||||
type Options = {
|
||||
'--debug': boolean;
|
||||
};
|
||||
|
||||
export default async function ls(
|
||||
ctx: NowContext,
|
||||
opts: Options,
|
||||
args: string[],
|
||||
output: Output
|
||||
) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
const debug = opts['--debug'];
|
||||
const client = new Client({ apiUrl, token, currentTeam, debug });
|
||||
const link = await getLinkedProject(output, client);
|
||||
|
||||
if (link.status === 'error') {
|
||||
return link.exitCode;
|
||||
} else if (link.status === 'not_linked') {
|
||||
output.print(
|
||||
`${chalk.red(
|
||||
'Error!'
|
||||
)} Your codebase isn’t linked to a project on ZEIT Now. Run ${cmd(
|
||||
'now'
|
||||
)} to link it.\n`
|
||||
);
|
||||
return 1;
|
||||
} else {
|
||||
if (args.length > 1) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${cmd(
|
||||
`now env ls ${getEnvTargetPlaceholder()}`
|
||||
)}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { project } = link;
|
||||
const envTarget = args[0] as ProjectEnvTarget | undefined;
|
||||
|
||||
if (!isValidEnvTarget(envTarget)) {
|
||||
output.error(
|
||||
`The Environment ${param(
|
||||
envTarget
|
||||
)} is invalid. It must be one of: ${getEnvTargetPlaceholder()}.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const lsStamp = stamp();
|
||||
|
||||
const records = await getEnvVariables(
|
||||
output,
|
||||
client,
|
||||
project.id,
|
||||
envTarget
|
||||
);
|
||||
output.log(
|
||||
`${plural(
|
||||
'Environment Variable',
|
||||
records.length,
|
||||
true
|
||||
)} found in Project ${chalk.bold(project.name)} ${chalk.gray(lsStamp())}`
|
||||
);
|
||||
console.log(getTable(records));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function getTable(records: ProjectEnvVariable[]) {
|
||||
return formatTable(
|
||||
['name', 'value', 'environment', 'created'],
|
||||
['l', 'l', 'l', 'l', 'l'],
|
||||
[
|
||||
{
|
||||
name: '',
|
||||
rows: records.map(getRow),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
function getRow({
|
||||
key,
|
||||
system = false,
|
||||
target,
|
||||
createdAt = 0,
|
||||
}: ProjectEnvVariable) {
|
||||
const now = Date.now();
|
||||
return [
|
||||
chalk.bold(key),
|
||||
chalk.gray(chalk.italic(system ? 'Populated by System' : 'Encrypted')),
|
||||
target || '',
|
||||
`${ms(now - createdAt)} ago`,
|
||||
];
|
||||
}
|
||||
120
packages/now-cli/src/commands/env/pull.ts
vendored
Normal file
120
packages/now-cli/src/commands/env/pull.ts
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
import chalk from 'chalk';
|
||||
import { NowContext, ProjectEnvTarget } from '../../types';
|
||||
import { Output } from '../../util/output';
|
||||
import promptBool from '../../util/prompt-bool';
|
||||
import Client from '../../util/client';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import getEnvVariables from '../../util/env/get-env-records';
|
||||
import getDecryptedSecret from '../../util/env/get-decrypted-secret';
|
||||
import { getLinkedProject } from '../../util/projects/link';
|
||||
import cmd from '../../util/output/cmd';
|
||||
import param from '../../util/output/param';
|
||||
import withSpinner from '../../util/with-spinner';
|
||||
import { join } from 'path';
|
||||
import { promises, existsSync } from 'fs';
|
||||
import { emoji, prependEmoji } from '../../util/emoji';
|
||||
const { writeFile } = promises;
|
||||
|
||||
type Options = {
|
||||
'--debug': boolean;
|
||||
'--yes': boolean;
|
||||
};
|
||||
|
||||
export default async function pull(
|
||||
ctx: NowContext,
|
||||
opts: Options,
|
||||
args: string[],
|
||||
output: Output
|
||||
) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
const debug = opts['--debug'];
|
||||
const client = new Client({ apiUrl, token, currentTeam, debug });
|
||||
const link = await getLinkedProject(output, client);
|
||||
|
||||
if (link.status === 'error') {
|
||||
return link.exitCode;
|
||||
} else if (link.status === 'not_linked') {
|
||||
output.print(
|
||||
`${chalk.red(
|
||||
'Error!'
|
||||
)} Your codebase isn’t linked to a project on ZEIT Now. Run ${cmd(
|
||||
'now'
|
||||
)} to link it.\n`
|
||||
);
|
||||
return 1;
|
||||
} else {
|
||||
if (args.length > 1) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${cmd('now env pull <file>')}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { project } = link;
|
||||
const [filename = '.env'] = args;
|
||||
const fullPath = join(process.cwd(), filename);
|
||||
const exists = existsSync(fullPath);
|
||||
const skipConfirmation = opts['--yes'];
|
||||
|
||||
if (
|
||||
exists &&
|
||||
!skipConfirmation &&
|
||||
!(await promptBool(
|
||||
output,
|
||||
`Found existing file ${param(filename)}. Do you want to overwrite?`
|
||||
))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
return 0;
|
||||
}
|
||||
|
||||
output.print(
|
||||
`Downloading Development Environment Variables for Project ${chalk.bold(
|
||||
project.name
|
||||
)}\n`
|
||||
);
|
||||
const pullStamp = stamp();
|
||||
|
||||
const records = await withSpinner('Downloading', async () => {
|
||||
const dev = ProjectEnvTarget.Development;
|
||||
const envs = await getEnvVariables(output, client, project.id, dev);
|
||||
const values = await Promise.all(
|
||||
envs.map(env => getDecryptedSecret(output, client, env.value))
|
||||
);
|
||||
const results: { key: string; value: string }[] = [];
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
results.push({ key: envs[i].key, value: values[i] });
|
||||
}
|
||||
return results;
|
||||
});
|
||||
|
||||
const contents =
|
||||
records
|
||||
.map(({ key, value }) => `${key}="${escapeValue(value)}"`)
|
||||
.join('\n') + '\n';
|
||||
|
||||
await writeFile(fullPath, contents, 'utf8');
|
||||
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`${exists ? 'Updated' : 'Created'} ${chalk.bold(
|
||||
filename
|
||||
)} file ${chalk.gray(pullStamp())}`,
|
||||
emoji('success')
|
||||
)}\n`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function escapeValue(value: string) {
|
||||
return value
|
||||
.replace(new RegExp('\\"', 'g'), '\\"') // escape quotes
|
||||
.replace(new RegExp('\n', 'g'), '\\n') // combine newlines (unix) into one line
|
||||
.replace(new RegExp('\r', 'g'), '\\r'); // combine newlines (windows) into one line
|
||||
}
|
||||
168
packages/now-cli/src/commands/env/rm.ts
vendored
Normal file
168
packages/now-cli/src/commands/env/rm.ts
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import { NowContext, ProjectEnvTarget } from '../../types';
|
||||
import { Output } from '../../util/output';
|
||||
import promptBool from '../../util/prompt-bool';
|
||||
import { getLinkedProject } from '../../util/projects/link';
|
||||
import removeEnvRecord from '../../util/env/remove-env-record';
|
||||
import getEnvVariables from '../../util/env/get-env-records';
|
||||
import {
|
||||
isValidEnvTarget,
|
||||
getEnvTargetPlaceholder,
|
||||
getEnvTargetChoices,
|
||||
} from '../../util/env/env-target';
|
||||
import Client from '../../util/client';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import cmd from '../../util/output/cmd';
|
||||
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';
|
||||
|
||||
type Options = {
|
||||
'--debug': boolean;
|
||||
'--yes': boolean;
|
||||
};
|
||||
|
||||
export default async function rm(
|
||||
ctx: NowContext,
|
||||
opts: Options,
|
||||
args: string[],
|
||||
output: Output
|
||||
) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
const debug = opts['--debug'];
|
||||
const client = new Client({ apiUrl, token, currentTeam, debug });
|
||||
const link = await getLinkedProject(output, client);
|
||||
|
||||
if (link.status === 'error') {
|
||||
return link.exitCode;
|
||||
} else if (link.status === 'not_linked') {
|
||||
output.print(
|
||||
`${chalk.red(
|
||||
'Error!'
|
||||
)} Your codebase isn’t linked to a project on ZEIT Now. Run ${cmd(
|
||||
'now'
|
||||
)} to link it.\n`
|
||||
);
|
||||
return 1;
|
||||
} else {
|
||||
if (args.length > 2) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${cmd(
|
||||
`now env rm <name> ${getEnvTargetPlaceholder()}`
|
||||
)}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { project } = link;
|
||||
let [envName, envTarget] = args;
|
||||
let envTargets: ProjectEnvTarget[] = [];
|
||||
|
||||
if (envTarget) {
|
||||
if (!isValidEnvTarget(envTarget)) {
|
||||
output.error(
|
||||
`The Environment ${param(
|
||||
envTarget
|
||||
)} is invalid. It must be one of: ${getEnvTargetPlaceholder()}.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
envTargets.push(envTarget);
|
||||
}
|
||||
|
||||
while (!envName) {
|
||||
const { inputName } = await inquirer.prompt({
|
||||
type: 'input',
|
||||
name: 'inputName',
|
||||
message: `What’s the name of the variable?`,
|
||||
});
|
||||
|
||||
if (!inputName) {
|
||||
output.error(`Name cannot be empty`);
|
||||
continue;
|
||||
}
|
||||
|
||||
envName = inputName;
|
||||
}
|
||||
|
||||
const envs = await getEnvVariables(output, client, project.id);
|
||||
const existing = new Set(
|
||||
envs.filter(r => r.key === envName).map(r => r.target)
|
||||
);
|
||||
|
||||
if (existing.size === 0) {
|
||||
output.error(
|
||||
`The Environment Variable ${param(envName)} was not found.\n`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (envTargets.length === 0) {
|
||||
const choices = getEnvTargetChoices().filter(c => existing.has(c.value));
|
||||
if (choices.length === 0) {
|
||||
output.error(
|
||||
`The Environment Variable ${param(
|
||||
envName
|
||||
)} was found but it is not assigned to any Environments.\n`
|
||||
);
|
||||
return 1;
|
||||
} else if (choices.length === 1) {
|
||||
envTargets = [choices[0].value];
|
||||
} else {
|
||||
const { inputTargets } = await inquirer.prompt({
|
||||
name: 'inputTargets',
|
||||
type: 'checkbox',
|
||||
message: `Remove ${envName} from which Environments (select multiple)?`,
|
||||
choices,
|
||||
});
|
||||
envTargets = inputTargets;
|
||||
}
|
||||
}
|
||||
|
||||
const skipConfirmation = opts['--yes'];
|
||||
if (
|
||||
!skipConfirmation &&
|
||||
!(await promptBool(
|
||||
output,
|
||||
`Removing Environment Variable ${param(
|
||||
envName
|
||||
)} from Project ${chalk.bold(project.name)}. Are you sure?`
|
||||
))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
return 0;
|
||||
}
|
||||
|
||||
const rmStamp = stamp();
|
||||
|
||||
try {
|
||||
await withSpinner('Removing', async () => {
|
||||
for (const target of envTargets) {
|
||||
await removeEnvRecord(output, client, project.id, envName, target);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (isKnownError(error) && error.serverMessage) {
|
||||
output.error(error.serverMessage);
|
||||
return 1;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`Removed Environment Variable ${chalk.gray(rmStamp())}`,
|
||||
emoji('success')
|
||||
)}\n`
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export default new Map([
|
||||
['domain', 'domains'],
|
||||
['domains', 'domains'],
|
||||
['downgrade', 'upgrade'],
|
||||
['env', 'env'],
|
||||
['help', 'help'],
|
||||
['init', 'init'],
|
||||
['inspect', 'inspect'],
|
||||
|
||||
@@ -155,11 +155,7 @@ export default async function main(ctx) {
|
||||
type === STATIC
|
||||
? null
|
||||
: caught(
|
||||
now.fetch(
|
||||
`/v1/now/deployments/${encodeURIComponent(
|
||||
finalId
|
||||
)}/events?types=event`
|
||||
)
|
||||
now.fetch(`/v1/now/deployments/${encodeURIComponent(finalId)}/events`)
|
||||
),
|
||||
isBuilds ? now.fetch(buildsUrl) : { builds: [] },
|
||||
]);
|
||||
|
||||
@@ -17,6 +17,7 @@ import getScope from '../util/get-scope.ts';
|
||||
import toHost from '../util/to-host';
|
||||
import parseMeta from '../util/parse-meta';
|
||||
import { isValidName } from '../util/is-valid-name';
|
||||
import getCommandFlags from '../util/get-command-flags';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -332,11 +333,16 @@ export default async function main(ctx) {
|
||||
hsep: ' '.repeat(4),
|
||||
stringLength: strlen,
|
||||
}
|
||||
).replace(/^/gm, ' ')}\n\n`
|
||||
).replace(/^/gm, ' ')}\n`
|
||||
);
|
||||
|
||||
if (pagination && deployments.length === 20) {
|
||||
log(`To display the next page use the flag --next ${pagination.next}`);
|
||||
if (pagination && pagination.count === 20) {
|
||||
const flags = getCommandFlags(argv, ['_', '--next']);
|
||||
log(
|
||||
`To display the next page run ${cmd(
|
||||
`now ls${app ? ' ' + app : ''}${flags} --next ${pagination.next}`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import Now from '../util';
|
||||
import createOutput from '../util/output';
|
||||
import logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed.ts';
|
||||
import { maybeURL, normalizeURL, parseInstanceURL } from '../util/url';
|
||||
import { maybeURL, normalizeURL } from '../util/url';
|
||||
import printEvents from '../util/events';
|
||||
import Client from '../util/client.ts';
|
||||
import getScope from '../util/get-scope.ts';
|
||||
@@ -16,7 +16,6 @@ const help = () => {
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
-a, --all Include access logs
|
||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||
'FILE'
|
||||
)} Path to the local ${'`now.json`'} file
|
||||
@@ -28,9 +27,6 @@ const help = () => {
|
||||
-n ${chalk.bold.underline(
|
||||
'NUMBER'
|
||||
)} Number of logs [100]
|
||||
-q ${chalk.bold.underline('QUERY')}, --query=${chalk.bold.underline(
|
||||
'QUERY'
|
||||
)} Search query
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
@@ -65,23 +61,18 @@ export default async function main(ctx) {
|
||||
let apiUrl;
|
||||
let head;
|
||||
let limit;
|
||||
let query;
|
||||
let follow;
|
||||
let types;
|
||||
let outputMode;
|
||||
|
||||
let since;
|
||||
let until;
|
||||
let instanceId;
|
||||
|
||||
argv = mri(ctx.argv.slice(2), {
|
||||
string: ['query', 'since', 'until', 'output'],
|
||||
boolean: ['help', 'all', 'debug', 'head', 'follow'],
|
||||
string: ['since', 'until', 'output'],
|
||||
boolean: ['help', 'debug', 'head', 'follow'],
|
||||
alias: {
|
||||
help: 'h',
|
||||
all: 'a',
|
||||
debug: 'd',
|
||||
query: 'q',
|
||||
follow: 'f',
|
||||
output: 'o',
|
||||
},
|
||||
@@ -121,7 +112,7 @@ export default async function main(ctx) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
[deploymentIdOrURL, instanceId] = parseInstanceURL(normalizedURL);
|
||||
deploymentIdOrURL = normalizedURL;
|
||||
}
|
||||
|
||||
debug = argv.debug;
|
||||
@@ -129,10 +120,8 @@ export default async function main(ctx) {
|
||||
|
||||
head = argv.head;
|
||||
limit = typeof argv.n === 'number' ? argv.n : 100;
|
||||
query = argv.query || '';
|
||||
follow = argv.f;
|
||||
if (follow) until = 0;
|
||||
types = argv.all ? [] : ['command', 'stdout', 'stderr', 'exit'];
|
||||
outputMode = argv.output in logPrinters ? argv.output : 'short';
|
||||
|
||||
const {
|
||||
@@ -204,9 +193,6 @@ export default async function main(ctx) {
|
||||
const findOpts1 = {
|
||||
direction,
|
||||
limit,
|
||||
query,
|
||||
types,
|
||||
instanceId,
|
||||
since,
|
||||
until,
|
||||
}; // no follow
|
||||
@@ -236,9 +222,6 @@ export default async function main(ctx) {
|
||||
const since2 = lastEvent ? lastEvent.date : Date.now();
|
||||
const findOpts2 = {
|
||||
direction: 'forward',
|
||||
query,
|
||||
types,
|
||||
instanceId,
|
||||
since: since2,
|
||||
follow: true,
|
||||
};
|
||||
|
||||
@@ -190,7 +190,10 @@ export default async function main(ctx) {
|
||||
}
|
||||
|
||||
aliases = await Promise.all(
|
||||
deployments.map(depl => getAliases(client, depl.uid))
|
||||
deployments.map(async depl => {
|
||||
const { aliases } = await getAliases(client, depl.uid);
|
||||
return aliases;
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
cancelWait();
|
||||
|
||||
@@ -110,7 +110,7 @@ const main = async ctx => {
|
||||
}
|
||||
|
||||
try {
|
||||
await run({ output, token, contextName, currentTeam });
|
||||
await run({ output, token, contextName, currentTeam, ctx });
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
exit(1);
|
||||
@@ -126,7 +126,7 @@ export default async ctx => {
|
||||
}
|
||||
};
|
||||
|
||||
async function run({ output, token, contextName, currentTeam }) {
|
||||
async function run({ output, token, contextName, currentTeam, ctx }) {
|
||||
const secrets = new NowSecrets({ apiUrl, token, debug, currentTeam });
|
||||
const args = argv._.slice(1);
|
||||
const start = Date.now();
|
||||
@@ -260,7 +260,28 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
const [name, value] = args;
|
||||
const [name, parsedValue] = args;
|
||||
const [originalName, originalValue] = ctx.argv.slice(-2);
|
||||
|
||||
let value = parsedValue;
|
||||
if (
|
||||
name === originalName &&
|
||||
typeof parsedValue === 'boolean' &&
|
||||
parsedValue !== originalValue
|
||||
) {
|
||||
// Corner case where `mri` transforms the secret value into a boolean because
|
||||
// it starts with a `-` so it thinks its a flag, so we use the original value instead.
|
||||
value = originalValue;
|
||||
}
|
||||
|
||||
if (typeof value === 'boolean') {
|
||||
const example = chalk.cyan(`$ now secret add -- "${name}"`);
|
||||
console.log(
|
||||
`If your secret starts with '-', make sure to terminate command options with double dash and wrap it in quotes. Example: \n ${example} `
|
||||
);
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
await secrets.add(name, value);
|
||||
const elapsed = ms(new Date() - start);
|
||||
|
||||
|
||||
@@ -216,6 +216,33 @@ export type DNSRecordData =
|
||||
| SRVRecordData
|
||||
| MXRecordData;
|
||||
|
||||
export interface Secret {
|
||||
uid: string;
|
||||
name: string;
|
||||
value: string;
|
||||
teamId?: string;
|
||||
userId?: string;
|
||||
projectId?: string;
|
||||
created: string;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
export enum ProjectEnvTarget {
|
||||
Production = 'production',
|
||||
Preview = 'preview',
|
||||
Development = 'development',
|
||||
}
|
||||
|
||||
export interface ProjectEnvVariable {
|
||||
key: string;
|
||||
value: string;
|
||||
configurationId?: string | null;
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
target?: ProjectEnvTarget;
|
||||
system?: boolean;
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
@@ -21,7 +21,7 @@ export default class Client extends EventEmitter {
|
||||
_apiUrl: string;
|
||||
_debug: boolean;
|
||||
_forceNew: boolean;
|
||||
_forceNewWithCache: boolean;
|
||||
_withCache: boolean;
|
||||
_output: Output;
|
||||
_token: string;
|
||||
currentTeam?: string;
|
||||
@@ -31,21 +31,21 @@ export default class Client extends EventEmitter {
|
||||
token,
|
||||
currentTeam,
|
||||
forceNew = false,
|
||||
forceNewWithCache = false,
|
||||
withCache = false,
|
||||
debug = false,
|
||||
}: {
|
||||
apiUrl: string;
|
||||
token: string;
|
||||
currentTeam?: string;
|
||||
forceNew?: boolean;
|
||||
forceNewWithCache?: boolean;
|
||||
withCache?: boolean;
|
||||
debug?: boolean;
|
||||
}) {
|
||||
super();
|
||||
this._token = token;
|
||||
this._debug = debug;
|
||||
this._forceNew = forceNew;
|
||||
this._forceNewWithCache = forceNewWithCache;
|
||||
this._withCache = withCache;
|
||||
this._output = createOutput({ debug });
|
||||
this._apiUrl = apiUrl;
|
||||
this._onRetry = this._onRetry.bind(this);
|
||||
|
||||
@@ -11,11 +11,8 @@ async function getEventsStream(now, idOrHost, options) {
|
||||
direction: options.direction,
|
||||
follow: options.follow ? '1' : '',
|
||||
format: options.format || 'lines',
|
||||
instanceId: options.instanceId,
|
||||
limit: options.limit,
|
||||
q: options.query,
|
||||
since: options.since,
|
||||
types: (options.types || []).join(','),
|
||||
until: options.until,
|
||||
})}`
|
||||
);
|
||||
|
||||
@@ -66,7 +66,7 @@ export default async function processDeployment({
|
||||
quiet: boolean;
|
||||
nowConfig?: NowConfig;
|
||||
force?: boolean;
|
||||
forceNewWithCache?: boolean;
|
||||
withCache?: boolean;
|
||||
org: Org;
|
||||
projectName: string;
|
||||
isSettingUpProject: boolean;
|
||||
@@ -83,7 +83,7 @@ export default async function processDeployment({
|
||||
requestBody,
|
||||
deployStamp,
|
||||
force,
|
||||
forceNewWithCache,
|
||||
withCache,
|
||||
nowConfig,
|
||||
quiet,
|
||||
} = args;
|
||||
@@ -101,7 +101,7 @@ export default async function processDeployment({
|
||||
userAgent: ua,
|
||||
path: paths[0],
|
||||
force,
|
||||
forceNewWithCache,
|
||||
withCache,
|
||||
skipAutoDetectionConfirmation,
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ import {
|
||||
BuilderOutput,
|
||||
BuildResultV3,
|
||||
BuilderOutputs,
|
||||
BuilderParams,
|
||||
EnvConfigs,
|
||||
} from './types';
|
||||
import { normalizeRoutes } from '@now/routing-utils';
|
||||
import getUpdateCommand from '../get-update-command';
|
||||
@@ -45,7 +47,7 @@ const treeKill = promisify(_treeKill);
|
||||
|
||||
async function createBuildProcess(
|
||||
match: BuildMatch,
|
||||
buildEnv: EnvConfig,
|
||||
envConfigs: EnvConfigs,
|
||||
workPath: string,
|
||||
output: Output,
|
||||
yarnPath?: string
|
||||
@@ -64,7 +66,7 @@ async function createBuildProcess(
|
||||
const env: EnvConfig = {
|
||||
...process.env,
|
||||
PATH,
|
||||
...buildEnv,
|
||||
...envConfigs.allEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
};
|
||||
|
||||
@@ -109,7 +111,7 @@ export async function executeBuild(
|
||||
builderWithPkg: { runInProcess, builder, package: pkg },
|
||||
} = match;
|
||||
const { entrypoint } = match;
|
||||
const { env, debug, buildEnv, yarnPath, cwd: workPath } = devServer;
|
||||
const { debug, envConfigs, yarnPath, cwd: workPath } = devServer;
|
||||
|
||||
const startTime = Date.now();
|
||||
const showBuildTimestamp =
|
||||
@@ -131,14 +133,14 @@ export async function executeBuild(
|
||||
devServer.output.debug(`Creating build process for ${entrypoint}`);
|
||||
buildProcess = await createBuildProcess(
|
||||
match,
|
||||
buildEnv,
|
||||
envConfigs,
|
||||
workPath,
|
||||
devServer.output,
|
||||
yarnPath
|
||||
);
|
||||
}
|
||||
|
||||
const buildParams = {
|
||||
const buildParams: BuilderParams = {
|
||||
files,
|
||||
entrypoint,
|
||||
workPath,
|
||||
@@ -148,8 +150,10 @@ export async function executeBuild(
|
||||
requestPath,
|
||||
filesChanged,
|
||||
filesRemoved,
|
||||
env,
|
||||
buildEnv,
|
||||
// This env distiniction is only necessary to maintain
|
||||
// backwards compatibility with the `@now/next` builder.
|
||||
env: envConfigs.runEnv,
|
||||
buildEnv: envConfigs.buildEnv,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -361,7 +365,7 @@ export async function executeBuild(
|
||||
Variables: {
|
||||
...nowConfig.env,
|
||||
...asset.environment,
|
||||
...env,
|
||||
...envConfigs.runEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -54,16 +54,18 @@ export async function devRouter(
|
||||
missRoutes?: RouteConfig[],
|
||||
phase?: HandleValue | null
|
||||
): Promise<RouteResult> {
|
||||
let found: RouteResult | undefined;
|
||||
let result: RouteResult | undefined;
|
||||
let { query, pathname: reqPathname = '/' } = url.parse(reqUrl, true);
|
||||
const combinedHeaders: HttpHeadersConfig = { ...previousHeaders };
|
||||
let status: number | undefined;
|
||||
let isContinue = false;
|
||||
|
||||
// Try route match
|
||||
if (routes) {
|
||||
let idx = -1;
|
||||
for (const routeConfig of routes) {
|
||||
idx++;
|
||||
isContinue = false;
|
||||
|
||||
if (isHandler(routeConfig)) {
|
||||
// We don't expect any Handle, only Source routes
|
||||
@@ -110,6 +112,7 @@ export async function devRouter(
|
||||
status = routeConfig.status;
|
||||
}
|
||||
reqPathname = destPath;
|
||||
isContinue = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -149,9 +152,10 @@ export async function devRouter(
|
||||
|
||||
const isDestUrl = isURL(destPath);
|
||||
if (isDestUrl) {
|
||||
found = {
|
||||
result = {
|
||||
found: true,
|
||||
dest: destPath,
|
||||
continue: isContinue,
|
||||
userDest: false,
|
||||
isDestUrl,
|
||||
status: routeConfig.status || status,
|
||||
@@ -167,9 +171,10 @@ export async function devRouter(
|
||||
destPath = `/${destPath}`;
|
||||
}
|
||||
const { pathname, query } = url.parse(destPath, true);
|
||||
found = {
|
||||
result = {
|
||||
found: true,
|
||||
dest: pathname || '/',
|
||||
continue: isContinue,
|
||||
userDest: Boolean(routeConfig.dest),
|
||||
isDestUrl,
|
||||
status: routeConfig.status || status,
|
||||
@@ -185,10 +190,11 @@ export async function devRouter(
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
found = {
|
||||
if (!result) {
|
||||
result = {
|
||||
found: false,
|
||||
dest: reqPathname,
|
||||
continue: isContinue,
|
||||
status,
|
||||
isDestUrl: false,
|
||||
uri_args: query,
|
||||
@@ -197,5 +203,5 @@ export async function devRouter(
|
||||
};
|
||||
}
|
||||
|
||||
return found;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -85,6 +85,8 @@ import {
|
||||
ListenSpec,
|
||||
RouteConfig,
|
||||
RouteResult,
|
||||
HttpHeadersConfig,
|
||||
EnvConfigs,
|
||||
} from './types';
|
||||
|
||||
interface FSEvent {
|
||||
@@ -108,8 +110,7 @@ export default class DevServer {
|
||||
public cwd: string;
|
||||
public debug: boolean;
|
||||
public output: Output;
|
||||
public env: EnvConfig;
|
||||
public buildEnv: EnvConfig;
|
||||
public envConfigs: EnvConfigs;
|
||||
public frameworkSlug: string | null;
|
||||
public files: BuilderInputs;
|
||||
public yarnPath: string;
|
||||
@@ -141,8 +142,7 @@ export default class DevServer {
|
||||
this.cwd = cwd;
|
||||
this.debug = options.debug;
|
||||
this.output = options.output;
|
||||
this.env = {};
|
||||
this.buildEnv = {};
|
||||
this.envConfigs = { buildEnv: {}, runEnv: {}, allEnv: {} };
|
||||
this.files = {};
|
||||
this.address = '';
|
||||
this.devCommand = options.devCommand;
|
||||
@@ -739,13 +739,13 @@ export default class DevServer {
|
||||
// Retrieve the path of the native module
|
||||
const nowConfig = await this.getNowConfig(false);
|
||||
const nowConfigBuild = nowConfig.build || {};
|
||||
const [env, buildEnv] = await Promise.all([
|
||||
const [runEnv, buildEnv] = await Promise.all([
|
||||
this.getLocalEnv('.env', nowConfig.env),
|
||||
this.getLocalEnv('.env.build', nowConfigBuild.env),
|
||||
]);
|
||||
Object.assign(process.env, buildEnv);
|
||||
this.env = env;
|
||||
this.buildEnv = buildEnv;
|
||||
const allEnv = { ...buildEnv, ...runEnv };
|
||||
Object.assign(process.env, allEnv);
|
||||
this.envConfigs = { buildEnv, runEnv, allEnv };
|
||||
|
||||
const opts = { output: this.output, isBuilds: true };
|
||||
const files = await getFiles(this.cwd, nowConfig, opts);
|
||||
@@ -1264,19 +1264,25 @@ export default class DevServer {
|
||||
let routeResult: RouteResult | null = null;
|
||||
let match: BuildMatch | null = null;
|
||||
let statusCode: number | undefined;
|
||||
let prevUrl = req.url;
|
||||
let prevHeaders: HttpHeadersConfig = {};
|
||||
|
||||
for (const phase of phases) {
|
||||
statusCode = undefined;
|
||||
const phaseRoutes = handleMap.get(phase) || [];
|
||||
routeResult = await devRouter(
|
||||
req.url,
|
||||
prevUrl,
|
||||
req.method,
|
||||
phaseRoutes,
|
||||
this,
|
||||
undefined,
|
||||
prevHeaders,
|
||||
missRoutes,
|
||||
phase
|
||||
);
|
||||
prevUrl =
|
||||
routeResult.continue && routeResult.dest ? routeResult.dest : req.url;
|
||||
prevHeaders =
|
||||
routeResult.continue && routeResult.headers ? routeResult.headers : {};
|
||||
|
||||
if (routeResult.isDestUrl) {
|
||||
// Mix the `routes` result dest query params into the req path
|
||||
@@ -1450,19 +1456,24 @@ export default class DevServer {
|
||||
foundAsset = findAsset(match, requestPath, nowConfig);
|
||||
}
|
||||
|
||||
if (!foundAsset) {
|
||||
// if the dev command is started, proxy to it
|
||||
if (this.devProcessPort) {
|
||||
this.output.debug('Proxy to dev command server');
|
||||
return proxyPass(
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${this.devProcessPort}`,
|
||||
this.output,
|
||||
false
|
||||
);
|
||||
}
|
||||
// Proxy to the dev server:
|
||||
// - when there is no asset
|
||||
// - when the asset is not a Lambda (the dev server must take care of all static files)
|
||||
if (
|
||||
this.devProcessPort &&
|
||||
(!foundAsset || (foundAsset && foundAsset.asset.type !== 'Lambda'))
|
||||
) {
|
||||
this.output.debug('Proxy to dev command server');
|
||||
return proxyPass(
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${this.devProcessPort}`,
|
||||
this.output,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (!foundAsset) {
|
||||
await this.send404(req, res, nowRequestId);
|
||||
return;
|
||||
}
|
||||
@@ -1682,8 +1693,7 @@ export default class DevServer {
|
||||
FORCE_COLOR: process.stdout.isTTY ? '1' : '0',
|
||||
...(this.frameworkSlug === 'create-react-app' ? { BROWSER: 'none' } : {}),
|
||||
...process.env,
|
||||
...this.buildEnv,
|
||||
...(this.frameworkSlug === 'nextjs' ? this.env : {}),
|
||||
...this.envConfigs.allEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
PORT: `${port}`,
|
||||
};
|
||||
|
||||
@@ -26,6 +26,23 @@ export interface EnvConfig {
|
||||
[name: string]: string | undefined;
|
||||
}
|
||||
|
||||
export interface EnvConfigs {
|
||||
/**
|
||||
* environment variables from `.env.build` file (deprecated)
|
||||
*/
|
||||
buildEnv: EnvConfig;
|
||||
|
||||
/**
|
||||
* environment variables from `.env` file
|
||||
*/
|
||||
runEnv: EnvConfig;
|
||||
|
||||
/**
|
||||
* environment variables from `.env` and `.env.build`
|
||||
*/
|
||||
allEnv: EnvConfig;
|
||||
}
|
||||
|
||||
export interface BuildMatch extends BuildConfig {
|
||||
entrypoint: string;
|
||||
builderWithPkg: BuilderWithPackage;
|
||||
@@ -147,6 +164,8 @@ export interface RouteResult {
|
||||
found: boolean;
|
||||
// "dest": <string of the dest, either file for lambda or full url for remote>
|
||||
dest: string;
|
||||
// `true` if last route in current phase matched but set `continue: true`
|
||||
continue: boolean;
|
||||
// "status": <integer in case exit code is intended to be changed>
|
||||
status?: number;
|
||||
// "headers": <object of the added response header values>
|
||||
|
||||
@@ -19,7 +19,7 @@ export default async function addDNSRecord(
|
||||
) {
|
||||
try {
|
||||
const record = await client.fetch<Response>(
|
||||
`/v3/domains/${domain}/records`,
|
||||
`/v3/domains/${encodeURIComponent(domain)}/records`,
|
||||
{
|
||||
body: recordData,
|
||||
method: 'POST',
|
||||
|
||||
@@ -22,12 +22,15 @@ export default async function importZonefile(
|
||||
const zonefile = readFileSync(resolve(zonefilePath), 'utf8');
|
||||
|
||||
try {
|
||||
const res = await client.fetch<Response>(`/v3/domains/${domain}/records`, {
|
||||
headers: { 'Content-Type': 'text/dns' },
|
||||
body: zonefile,
|
||||
method: 'PUT',
|
||||
json: false,
|
||||
});
|
||||
const res = await client.fetch<Response>(
|
||||
`/v3/domains/${encodeURIComponent(domain)}/records`,
|
||||
{
|
||||
headers: { 'Content-Type': 'text/dns' },
|
||||
body: zonefile,
|
||||
method: 'PUT',
|
||||
json: false,
|
||||
}
|
||||
);
|
||||
|
||||
const { recordIds } = (await res.json()) as JSONResponse;
|
||||
cancelWait();
|
||||
|
||||
@@ -22,5 +22,7 @@ type Response = {
|
||||
};
|
||||
|
||||
export default async function checkTransfer(client: Client, name: string) {
|
||||
return client.fetch<Response>(`/v4/domains/${name}/registry`);
|
||||
return client.fetch<Response>(
|
||||
`/v4/domains/${encodeURIComponent(name)}/registry`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ async function getDomainByName(
|
||||
);
|
||||
try {
|
||||
const { domain } = await client.fetch<Response>(
|
||||
`/v4/domains/${domainName}`
|
||||
`/v4/domains/${encodeURIComponent(domainName)}`
|
||||
);
|
||||
cancelWait();
|
||||
return domain;
|
||||
|
||||
@@ -13,10 +13,13 @@ export default async function moveOutDomain(
|
||||
destination: string
|
||||
) {
|
||||
try {
|
||||
return await client.fetch<Response>(`/v4/domains/${name}`, {
|
||||
body: { op: 'move-out', destination },
|
||||
method: 'PATCH',
|
||||
});
|
||||
return await client.fetch<Response>(
|
||||
`/v4/domains/${encodeURIComponent(name)}`,
|
||||
{
|
||||
body: { op: 'move-out', destination },
|
||||
method: 'PATCH',
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
if (error.code === 'forbidden') {
|
||||
return new ERRORS.DomainPermissionDenied(name, contextName);
|
||||
|
||||
@@ -7,7 +7,9 @@ export default async function removeDomainByName(
|
||||
domain: string
|
||||
) {
|
||||
try {
|
||||
return await now.fetch(`/v3/domains/${domain}`, { method: 'DELETE' });
|
||||
return await now.fetch(`/v3/domains/${encodeURIComponent(domain)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'not_found') {
|
||||
return new ERRORS.DomainNotFound(domain);
|
||||
|
||||
61
packages/now-cli/src/util/env/add-env-record.ts
vendored
Normal file
61
packages/now-cli/src/util/env/add-env-record.ts
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Output } from '../output';
|
||||
import Client from '../client';
|
||||
import { Secret, ProjectEnvTarget, ProjectEnvVariable } from '../../types';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import slugify from '@sindresorhus/slugify';
|
||||
|
||||
export default async function addEnvRecord(
|
||||
output: Output,
|
||||
client: Client,
|
||||
projectId: string,
|
||||
envName: string,
|
||||
envValue: string | undefined,
|
||||
targets: ProjectEnvTarget[]
|
||||
): Promise<void> {
|
||||
output.debug(
|
||||
`Adding Environment Variable ${envName} to ${targets.length} targets`
|
||||
);
|
||||
|
||||
let values: string[] | undefined;
|
||||
|
||||
if (envValue) {
|
||||
const urlSecret = `/v2/now/secrets/${encodeURIComponent(envName)}`;
|
||||
const secrets = await Promise.all(
|
||||
targets.map(target =>
|
||||
client.fetch<Secret>(urlSecret, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
name: generateSecretName(envName, target),
|
||||
value: envValue,
|
||||
projectId: projectId,
|
||||
decryptable: target === ProjectEnvTarget.Development,
|
||||
}),
|
||||
})
|
||||
)
|
||||
);
|
||||
values = secrets.map(secret => secret.uid);
|
||||
}
|
||||
|
||||
const body = targets.map((target, i) => ({
|
||||
key: envName,
|
||||
value: values ? values[i] : '',
|
||||
target,
|
||||
}));
|
||||
|
||||
const urlProject = `/v4/projects/${projectId}/env`;
|
||||
await client.fetch<ProjectEnvVariable>(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()}`;
|
||||
}
|
||||
22
packages/now-cli/src/util/env/env-target.ts
vendored
Normal file
22
packages/now-cli/src/util/env/env-target.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ProjectEnvTarget } from '../../types';
|
||||
|
||||
function envTargets(): string[] {
|
||||
return Object.values(ProjectEnvTarget);
|
||||
}
|
||||
|
||||
export function getEnvTargetChoices() {
|
||||
return Object.entries(ProjectEnvTarget).map(([key, value]) => ({
|
||||
name: key,
|
||||
value: value,
|
||||
}));
|
||||
}
|
||||
|
||||
export function isValidEnvTarget(
|
||||
target?: string
|
||||
): target is ProjectEnvTarget | undefined {
|
||||
return typeof target === 'undefined' || envTargets().includes(target);
|
||||
}
|
||||
|
||||
export function getEnvTargetPlaceholder() {
|
||||
return `<${envTargets().join(' | ')}>`;
|
||||
}
|
||||
17
packages/now-cli/src/util/env/get-decrypted-secret.ts
vendored
Normal file
17
packages/now-cli/src/util/env/get-decrypted-secret.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Output } from '../output';
|
||||
import Client from '../client';
|
||||
import { Secret } from '../../types';
|
||||
|
||||
export default async function getDecryptedSecret(
|
||||
output: Output,
|
||||
client: Client,
|
||||
secretId: string
|
||||
): Promise<string> {
|
||||
if (!secretId) {
|
||||
return '';
|
||||
}
|
||||
output.debug(`Fetching decrypted secret ${secretId}`);
|
||||
const url = `/v2/now/secrets/${secretId}?decrypt=true`;
|
||||
const secret = await client.fetch<Secret>(url);
|
||||
return secret.value;
|
||||
}
|
||||
18
packages/now-cli/src/util/env/get-env-records.ts
vendored
Normal file
18
packages/now-cli/src/util/env/get-env-records.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Output } from '../output';
|
||||
import Client from '../client';
|
||||
import { ProjectEnvVariable, ProjectEnvTarget } from '../../types';
|
||||
|
||||
export default async function getEnvVariables(
|
||||
output: Output,
|
||||
client: Client,
|
||||
projectId: string,
|
||||
target?: ProjectEnvTarget
|
||||
): Promise<ProjectEnvVariable[]> {
|
||||
output.debug(
|
||||
`Fetching Environment Variables of project ${projectId} and target ${target}`
|
||||
);
|
||||
const qs = target ? `?target=${encodeURIComponent(target)}` : '';
|
||||
const url = `/v4/projects/${projectId}/env${qs}`;
|
||||
const records = await client.fetch<ProjectEnvVariable[]>(url);
|
||||
return records;
|
||||
}
|
||||
13
packages/now-cli/src/util/env/known-error.ts
vendored
Normal file
13
packages/now-cli/src/util/env/known-error.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
const knownErrorsCodes = new Set([
|
||||
'PAYMENT_REQUIRED',
|
||||
'BAD_REQUEST',
|
||||
'SYSTEM_ENV_WITH_VALUE',
|
||||
'RESERVED_ENV_VARIABLE',
|
||||
'ENV_ALREADY_EXISTS',
|
||||
'ENV_SHOULD_BE_A_SECRET',
|
||||
]);
|
||||
|
||||
export function isKnownError(error: { code?: string }) {
|
||||
const code = error && typeof error.code === 'string' ? error.code : '';
|
||||
return knownErrorsCodes.has(code.toUpperCase());
|
||||
}
|
||||
38
packages/now-cli/src/util/env/remove-env-record.ts
vendored
Normal file
38
packages/now-cli/src/util/env/remove-env-record.ts
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Output } from '../output';
|
||||
import Client from '../client';
|
||||
import { ProjectEnvTarget, Secret, ProjectEnvVariable } from '../../types';
|
||||
|
||||
export default async function removeEnvRecord(
|
||||
output: Output,
|
||||
client: Client,
|
||||
projectId: string,
|
||||
envName: string,
|
||||
target?: ProjectEnvTarget
|
||||
): Promise<void> {
|
||||
output.debug(
|
||||
`Removing Environment Variable ${envName} from target ${target}`
|
||||
);
|
||||
|
||||
const qs = target ? `?target=${encodeURIComponent(target)}` : '';
|
||||
const urlProject = `/v4/projects/${projectId}/env/${encodeURIComponent(
|
||||
envName
|
||||
)}${qs}`;
|
||||
|
||||
const env = await client.fetch<ProjectEnvVariable>(urlProject, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (env && env.value) {
|
||||
const idOrName = env.value.startsWith('@') ? env.value.slice(1) : env.value;
|
||||
const urlSecret = `/v2/now/secrets/${idOrName}`;
|
||||
const secret = await client.fetch<Secret>(urlSecret);
|
||||
|
||||
// Since integrations add global secrets, we must only delete if the secret was
|
||||
// specifically added to this project
|
||||
if (secret && secret.projectId === projectId) {
|
||||
await client.fetch<Secret>(urlSecret, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,11 +28,8 @@ async function printEvents(
|
||||
const q = qs.stringify({
|
||||
direction: findOpts.direction,
|
||||
limit: findOpts.limit,
|
||||
q: findOpts.query,
|
||||
types: (findOpts.types || []).join(','),
|
||||
since: findOpts.since,
|
||||
until: findOpts.until,
|
||||
instanceId: findOpts.instanceId,
|
||||
follow: findOpts.follow ? '1' : '',
|
||||
format: 'lines',
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ import strlen from './strlen';
|
||||
export default function formatTable(
|
||||
header: string[],
|
||||
align: Array<'l' | 'r' | 'c' | '.'>,
|
||||
blocks: { name: string, rows: string[][] }[],
|
||||
blocks: { name: string; rows: string[][] }[],
|
||||
hsep = ' '
|
||||
) {
|
||||
const nrCols = header.length;
|
||||
@@ -50,8 +50,8 @@ export default function formatTable(
|
||||
for (let j = 0; j < nrCols; j++) {
|
||||
const col = `${row[j]}`;
|
||||
const al = align[j] || 'l';
|
||||
const pad =
|
||||
padding[j] > 1 ? ' '.repeat(padding[j] * 8 - strlen(col)) : '';
|
||||
const spaces = Math.max(padding[j] * 8 - strlen(col), 0);
|
||||
const pad = ' '.repeat(spaces);
|
||||
rows[i][j] = al === 'l' ? col + pad : pad + col;
|
||||
}
|
||||
}
|
||||
|
||||
20
packages/now-cli/src/util/get-command-flags.ts
Normal file
20
packages/now-cli/src/util/get-command-flags.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
This function returns the provided arguments from a command in a string format.
|
||||
|
||||
Example: if `argv` is { '--debug': true, '--all': true, '--scope': 'zeit' },
|
||||
the output will be '--debug --all --scope zeit'.
|
||||
|
||||
Flags can be excluded using the `excludeFlags` param.
|
||||
*/
|
||||
export default function getCommandFlags(
|
||||
argv: { [key: string]: any },
|
||||
excludeFlags: string[] = []
|
||||
) {
|
||||
const flags = Object.keys(argv)
|
||||
.filter(key => !excludeFlags.includes(key))
|
||||
.map(
|
||||
key => `${key}${typeof argv[key] !== 'boolean' ? ' ' + argv[key] : ''}`
|
||||
);
|
||||
|
||||
return flags.length > 0 ? ` ${flags.join(' ')}` : '';
|
||||
}
|
||||
@@ -1,14 +1,6 @@
|
||||
import fetch from 'node-fetch';
|
||||
import { Framework } from '@now/frameworks';
|
||||
import Client from './client';
|
||||
|
||||
export async function getFrameworks(): Promise<Framework[]> {
|
||||
const res = await fetch('https://api.zeit.co/v1/frameworks');
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error('Could not retrieve frameworks');
|
||||
}
|
||||
|
||||
const json: Framework[] = await res.json();
|
||||
|
||||
return json;
|
||||
export async function getFrameworks(client: Client) {
|
||||
return await client.fetch<Framework[]>('/v1/frameworks');
|
||||
}
|
||||
|
||||
9
packages/now-cli/src/util/get-invalid-subcommand.ts
Normal file
9
packages/now-cli/src/util/get-invalid-subcommand.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
type CommandConfig = {
|
||||
[command: string]: string[];
|
||||
};
|
||||
|
||||
export default function getInvalidSubcommand(config: CommandConfig) {
|
||||
return `Please specify a valid subcommand: ${Object.keys(config).join(
|
||||
' | '
|
||||
)}`;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import NowTeams from './teams.js';
|
||||
|
||||
let teams: Team[] | undefined;
|
||||
|
||||
export default async function getTeams(client: Client) {
|
||||
export default async function getTeams(client: Client): Promise<Team[]> {
|
||||
if (teams) return teams;
|
||||
|
||||
try {
|
||||
@@ -17,8 +17,8 @@ export default async function getTeams(client: Client) {
|
||||
debug: client._debug,
|
||||
});
|
||||
|
||||
const teams = (await teamClient.ls()).teams;
|
||||
return teams as Team[];
|
||||
teams = (await teamClient.ls()).teams;
|
||||
return teams || [];
|
||||
} catch (error) {
|
||||
if (error instanceof APIError && error.status === 403) {
|
||||
throw new InvalidToken();
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class Now extends EventEmitter {
|
||||
token,
|
||||
currentTeam,
|
||||
forceNew = false,
|
||||
forceNewWithCache = false,
|
||||
withCache = false,
|
||||
debug = false,
|
||||
}) {
|
||||
super();
|
||||
@@ -42,7 +42,7 @@ export default class Now extends EventEmitter {
|
||||
this._token = token;
|
||||
this._debug = debug;
|
||||
this._forceNew = forceNew;
|
||||
this._forceNewWithCache = forceNewWithCache;
|
||||
this._withCache = withCache;
|
||||
this._output = createOutput({ debug });
|
||||
this._apiUrl = apiUrl;
|
||||
this._onRetry = this._onRetry.bind(this);
|
||||
@@ -72,7 +72,7 @@ export default class Now extends EventEmitter {
|
||||
env,
|
||||
build,
|
||||
forceNew = false,
|
||||
forceNewWithCache = false,
|
||||
withCache = false,
|
||||
target = null,
|
||||
deployStamp,
|
||||
projectSettings,
|
||||
@@ -164,7 +164,7 @@ export default class Now extends EventEmitter {
|
||||
meta,
|
||||
public: wantsPublic || nowConfig.public,
|
||||
forceNew,
|
||||
forceNewWithCache,
|
||||
withCache,
|
||||
name,
|
||||
project,
|
||||
description,
|
||||
@@ -190,7 +190,7 @@ export default class Now extends EventEmitter {
|
||||
quiet,
|
||||
nowConfig,
|
||||
force: forceNew,
|
||||
forceNewWithCache: forceNewWithCache,
|
||||
withCache,
|
||||
org,
|
||||
projectName: name,
|
||||
isSettingUpProject,
|
||||
@@ -388,7 +388,12 @@ export default class Now extends EventEmitter {
|
||||
if (!app && !Object.keys(meta).length) {
|
||||
// Get the 20 latest projects and their latest deployment
|
||||
const query = new URLSearchParams({ limit: (20).toString() });
|
||||
const projects = await fetchRetry(`/v2/projects/?${query}`);
|
||||
if (nextTimestamp) {
|
||||
query.set('until', String(nextTimestamp));
|
||||
}
|
||||
const { projects, pagination } = await fetchRetry(
|
||||
`/v4/projects/?${query}`
|
||||
);
|
||||
|
||||
const deployments = await Promise.all(
|
||||
projects.map(async ({ id: projectId }) => {
|
||||
@@ -400,7 +405,7 @@ export default class Now extends EventEmitter {
|
||||
})
|
||||
);
|
||||
|
||||
return { deployments: deployments.filter(x => x) };
|
||||
return { deployments: deployments.filter(x => x), pagination };
|
||||
}
|
||||
|
||||
const query = new URLSearchParams();
|
||||
|
||||
13
packages/now-cli/src/util/input/read-standard-input.ts
Normal file
13
packages/now-cli/src/util/input/read-standard-input.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export default async function readStandardInput(): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
setTimeout(() => resolve(''), 500);
|
||||
|
||||
if (process.stdin.isTTY) {
|
||||
// found tty so we know there is nothing piped to stdin
|
||||
resolve('');
|
||||
} else {
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.once('data', resolve);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import chalk from 'chalk';
|
||||
import { Output } from './output';
|
||||
|
||||
async function promptBool(output: Output, message: string) {
|
||||
return new Promise(resolve => {
|
||||
async function promptBool(output: Output, message: string): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
output.print(`${chalk.gray('>')} ${message} ${chalk.gray('[y/N] ')}`);
|
||||
process.stdin
|
||||
.on('data', d => {
|
||||
|
||||
@@ -2,11 +2,7 @@ import Now from './index';
|
||||
|
||||
export default class Teams extends Now {
|
||||
async create({ slug }) {
|
||||
return this.retry(async (bail, attempt) => {
|
||||
if (this._debug) {
|
||||
console.time(`> [debug] #${attempt} POST /teams}`);
|
||||
}
|
||||
|
||||
return this.retry(async bail => {
|
||||
const res = await this._fetch(`/teams`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
@@ -14,10 +10,6 @@ export default class Teams extends Now {
|
||||
},
|
||||
});
|
||||
|
||||
if (this._debug) {
|
||||
console.timeEnd(`> [debug] #${attempt} POST /teams`);
|
||||
}
|
||||
|
||||
if (res.status === 403) {
|
||||
return bail(new Error('Unauthorized'));
|
||||
}
|
||||
@@ -40,11 +32,7 @@ export default class Teams extends Now {
|
||||
}
|
||||
|
||||
async edit({ id, slug, name }) {
|
||||
return this.retry(async (bail, attempt) => {
|
||||
if (this._debug) {
|
||||
console.time(`> [debug] #${attempt} PATCH /teams/${id}}`);
|
||||
}
|
||||
|
||||
return this.retry(async bail => {
|
||||
const payload = {};
|
||||
if (name) {
|
||||
payload.name = name;
|
||||
@@ -58,10 +46,6 @@ export default class Teams extends Now {
|
||||
body: payload,
|
||||
});
|
||||
|
||||
if (this._debug) {
|
||||
console.timeEnd(`> [debug] #${attempt} PATCH /teams/${id}`);
|
||||
}
|
||||
|
||||
if (res.status === 403) {
|
||||
return bail(new Error('Unauthorized'));
|
||||
}
|
||||
@@ -84,11 +68,7 @@ export default class Teams extends Now {
|
||||
}
|
||||
|
||||
async inviteUser({ teamId, email }) {
|
||||
return this.retry(async (bail, attempt) => {
|
||||
if (this._debug) {
|
||||
console.time(`> [debug] #${attempt} POST /teams/${teamId}/members}`);
|
||||
}
|
||||
|
||||
return this.retry(async bail => {
|
||||
const publicRes = await this._fetch(`/www/user/public?email=${email}`);
|
||||
const { name, username } = await publicRes.json();
|
||||
|
||||
@@ -99,10 +79,6 @@ export default class Teams extends Now {
|
||||
},
|
||||
});
|
||||
|
||||
if (this._debug) {
|
||||
console.timeEnd(`> [debug] #${attempt} POST /teams/${teamId}/members}`);
|
||||
}
|
||||
|
||||
if (res.status === 403) {
|
||||
return bail(new Error('Unauthorized'));
|
||||
}
|
||||
@@ -126,17 +102,9 @@ export default class Teams extends Now {
|
||||
}
|
||||
|
||||
async ls() {
|
||||
return this.retry(async (bail, attempt) => {
|
||||
if (this._debug) {
|
||||
console.time(`> [debug] #${attempt} GET /teams}`);
|
||||
}
|
||||
|
||||
return this.retry(async bail => {
|
||||
const res = await this._fetch(`/teams`);
|
||||
|
||||
if (this._debug) {
|
||||
console.timeEnd(`> [debug] #${attempt} GET /teams`);
|
||||
}
|
||||
|
||||
if (res.status === 403) {
|
||||
const error = new Error('Unauthorized');
|
||||
error.code = 'not_authorized';
|
||||
|
||||
@@ -13,10 +13,3 @@ export const normalizeURL = u => {
|
||||
|
||||
return u;
|
||||
};
|
||||
|
||||
export const parseInstanceURL = u => {
|
||||
const m = /^(.+)-([a-z0-9]{24})(\.now\.sh)$/.exec(u);
|
||||
const url = m ? m[1] + m[3] : u;
|
||||
const instanceId = m ? m[2] : null;
|
||||
return [url, instanceId];
|
||||
};
|
||||
|
||||
14
packages/now-cli/test/dev-router.unit.js
vendored
14
packages/now-cli/test/dev-router.unit.js
vendored
@@ -10,6 +10,7 @@ test('[dev-router] 301 redirection', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/redirect',
|
||||
continue: false,
|
||||
status: 301,
|
||||
headers: { location: 'https://zeit.co' },
|
||||
uri_args: {},
|
||||
@@ -28,6 +29,7 @@ test('[dev-router] captured groups', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/endpoints/user.js',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
@@ -46,6 +48,7 @@ test('[dev-router] named groups', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/user.js',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: { id: '123' },
|
||||
@@ -69,6 +72,7 @@ test('[dev-router] optional named groups', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/api/functions/hello/index.js',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: { name: '' },
|
||||
@@ -88,6 +92,7 @@ test('[dev-router] proxy_pass', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: 'https://zeit.co',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
@@ -109,6 +114,7 @@ test('[dev-router] methods', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/get',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
@@ -123,6 +129,7 @@ test('[dev-router] methods', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/post',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
@@ -141,6 +148,7 @@ test('[dev-router] match without prefix slash', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/endpoints/user.js',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
@@ -164,6 +172,7 @@ test('[dev-router] match with needed prefixed slash', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/some/dest',
|
||||
continue: false,
|
||||
userDest: true,
|
||||
isDestUrl: false,
|
||||
phase: undefined,
|
||||
@@ -197,6 +206,7 @@ test('[dev-router] `continue: true` with fallthrough', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: false,
|
||||
dest: '/_next/static/chunks/0.js',
|
||||
continue: true,
|
||||
isDestUrl: false,
|
||||
phase: undefined,
|
||||
status: undefined,
|
||||
@@ -230,6 +240,7 @@ test('[dev-router] `continue: true` with match', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/hi',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
userDest: true,
|
||||
isDestUrl: false,
|
||||
@@ -253,6 +264,7 @@ test('[dev-router] match with catch-all with prefix slash', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/www/',
|
||||
continue: false,
|
||||
userDest: true,
|
||||
isDestUrl: false,
|
||||
phase: undefined,
|
||||
@@ -271,6 +283,7 @@ test('[dev-router] match with catch-all with no prefix slash', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/www/',
|
||||
continue: false,
|
||||
userDest: true,
|
||||
isDestUrl: false,
|
||||
phase: undefined,
|
||||
@@ -295,6 +308,7 @@ test('[dev-router] `continue: true` with `dest`', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: 'http://localhost:5000/a/foo',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
SKIP_PREFLIGHT_CHECK=true
|
||||
BROWSER=none
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"url": "https://github.com/marko-js-samples/marko-starter-demo"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production marko-starter build && mv dist public",
|
||||
"build": "NODE_ENV=production marko-starter build && rm -rf public && mv dist public",
|
||||
"format": "prettier src/**/*.{js,css,less} --write && marko-prettyprint src",
|
||||
"lint": "eslint src",
|
||||
"serve": "NODE_ENV=production marko-starter serve-static",
|
||||
|
||||
1
packages/now-cli/test/dev/fixtures/23-docusaurus/.env
Normal file
1
packages/now-cli/test/dev/fixtures/23-docusaurus/.env
Normal file
@@ -0,0 +1 @@
|
||||
BROWSER=none
|
||||
@@ -0,0 +1 @@
|
||||
FOO="build-and-runtime"
|
||||
@@ -0,0 +1,2 @@
|
||||
public
|
||||
.now
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = (_req, res) => res.end(process.env.FOO);
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "mkdir public && echo $FOO > public/index.html"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = (req, res) => res.json(req.query)
|
||||
@@ -0,0 +1 @@
|
||||
Index Page should not be served
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"routes": [
|
||||
{
|
||||
"src": "/(.*)",
|
||||
"dest": "/api/$1",
|
||||
"headers": {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "*"
|
||||
},
|
||||
"continue": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -137,7 +137,7 @@ async function getPackedBuilderPath(builderDirName) {
|
||||
}
|
||||
|
||||
async function testPath(t, port, status, path, expectedText, headers = {}) {
|
||||
const opts = { redirect: 'manual' };
|
||||
const opts = { redirect: 'manual-dont-change' };
|
||||
const res = await fetch(`http://localhost:${port}${path}`, opts);
|
||||
const msg = `Testing path ${path}`;
|
||||
t.is(res.status, status, msg);
|
||||
@@ -146,9 +146,15 @@ async function testPath(t, port, status, path, expectedText, headers = {}) {
|
||||
t.is(actualText.trim(), expectedText.trim(), msg);
|
||||
}
|
||||
if (headers) {
|
||||
Object.keys(headers).forEach(key => {
|
||||
const k = key.toLowerCase();
|
||||
t.is(headers[k], res.headers[k], msg);
|
||||
Object.entries(headers).forEach(([key, expectedValue]) => {
|
||||
let actualValue = res.headers.get(key);
|
||||
if (key.toLowerCase() === 'location' && actualValue === '//') {
|
||||
// HACK: `node-fetch` has strang behavior for location header so fix it
|
||||
// with `manual-dont-change` opt and convert double slash to single.
|
||||
// See https://github.com/node-fetch/node-fetch/issues/417#issuecomment-587233352
|
||||
actualValue = '/';
|
||||
}
|
||||
t.is(actualValue, expectedValue, msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -468,6 +474,19 @@ test(
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] should allow user rewrites for path segment files',
|
||||
testFixtureStdio('test-zero-config-rewrite', async (t, port, testPath) => {
|
||||
await testPath(404, '/');
|
||||
await testPath(200, '/echo/1', '{"id":"1"}', {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
});
|
||||
await testPath(200, '/echo/2', '{"id":"2"}', {
|
||||
'Access-Control-Allow-Headers': '*',
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] validate builds', async t => {
|
||||
const directory = fixture('invalid-builds');
|
||||
const output = await exec(directory);
|
||||
@@ -717,18 +736,15 @@ test(
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] 01-node', async t => {
|
||||
const tester = testFixtureStdio('01-node', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
test(
|
||||
'[now dev] 01-node',
|
||||
testFixtureStdio('01-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /A simple deployment with the Now API!/gm);
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// Angular has `engines: { node: "10.x" }` in its `package.json`
|
||||
test('[now dev] 02-angular-node', async t => {
|
||||
@@ -768,26 +784,21 @@ test('[now dev] 02-angular-node', async t => {
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] 03-aurelia', async t => {
|
||||
const tester = testFixtureStdio('03-aurelia', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
test(
|
||||
'[now dev] 03-aurelia',
|
||||
testFixtureStdio('03-aurelia', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Aurelia Navigation Skeleton/gm);
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 04-create-react-app',
|
||||
testFixtureStdio('04-create-react-app', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /React App/gm);
|
||||
})
|
||||
@@ -797,10 +808,8 @@ test('[now dev] 05-gatsby', async t => {
|
||||
if (shouldSkip(t, '05-gatsby', '>^6.14.0 || ^8.10.0 || >=9.10.0')) return;
|
||||
|
||||
const tester = testFixtureStdio('05-gatsby', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Gatsby Default Starter/gm);
|
||||
});
|
||||
@@ -808,24 +817,20 @@ test('[now dev] 05-gatsby', async t => {
|
||||
await tester(t);
|
||||
});
|
||||
|
||||
test('[now dev] 06-gridsome', async t => {
|
||||
const tester = testFixtureStdio('06-gridsome', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
test(
|
||||
'[now dev] 06-gridsome',
|
||||
testFixtureStdio('06-gridsome', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
t.is(response.status, 200, await response.text());
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 07-hexo-node',
|
||||
testFixtureStdio('07-hexo-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Hexo \+ Node.js API/gm);
|
||||
})
|
||||
@@ -834,175 +839,77 @@ test(
|
||||
test(
|
||||
'[now dev] 08-hugo',
|
||||
testFixtureStdio('08-hugo', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
// const body = await response.text();
|
||||
// t.regex(body, /Hugo on ZEIT Now/gm);
|
||||
t.is(response.status, 200, await response.text());
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] 10-nextjs-node', async t => {
|
||||
const tester = testFixtureStdio('10-nextjs-node', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
test(
|
||||
'[now dev] 10-nextjs-node',
|
||||
testFixtureStdio('10-nextjs-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Next.js \+ Node.js API/gm);
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
|
||||
// test('[now dev] 11-nuxtjs-node', async t => {
|
||||
// const directory = fixture('11-nuxtjs-node');
|
||||
// const { dev, port } = await testFixture(directory);
|
||||
|
||||
// try {
|
||||
// // start `now dev` detached in child_process
|
||||
// dev.unref();
|
||||
|
||||
// const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
|
||||
// validateResponseHeaders(t, response);
|
||||
|
||||
// const body = await response.text();
|
||||
// t.regex(body, /Nuxt.js \+ Node.js API/gm);
|
||||
|
||||
// } finally {
|
||||
// dev.kill('SIGTERM')
|
||||
// }
|
||||
// });
|
||||
|
||||
test('[now dev] 12-polymer-node', async t => {
|
||||
const directory = fixture('12-polymer-node');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 12-polymer-node',
|
||||
testFixtureStdio('12-polymer-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Polymer \+ Node.js API/gm);
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] 13-preact-node', async t => {
|
||||
const directory = fixture('13-preact-node');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 13-preact-node',
|
||||
testFixtureStdio('13-preact-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Preact \+ Node.js API/gm);
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] 14-svelte-node', async t => {
|
||||
const directory = fixture('14-svelte-node');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 80);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 14-svelte-node',
|
||||
testFixtureStdio('14-svelte-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Svelte \+ Node.js API/gm);
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
// test('[now dev] 15-umijs-node', async t => {
|
||||
// const directory = fixture('15-umijs-node');
|
||||
// const { dev, port } = await testFixture(directory);
|
||||
|
||||
// try {
|
||||
// // start `now dev` detached in child_process
|
||||
// dev.unref();
|
||||
|
||||
// const response = await fetchWithRetry(`http://localhost:${port}`, 80);
|
||||
|
||||
// validateResponseHeaders(t, response);
|
||||
|
||||
// const body = await response.text();
|
||||
// t.regex(body, /UmiJS \+ Node.js API/gm);
|
||||
|
||||
// } finally {
|
||||
// dev.kill('SIGTERM')
|
||||
// }
|
||||
// });
|
||||
|
||||
test('[now dev] 16-vue-node', async t => {
|
||||
const directory = fixture('16-vue-node');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 16-vue-node',
|
||||
testFixtureStdio('16-vue-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Vue.js \+ Node.js API/gm);
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] 17-vuepress-node', async t => {
|
||||
const directory = fixture('17-vuepress-node');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 17-vuepress-node',
|
||||
testFixtureStdio('17-vuepress-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /VuePress \+ Node.js API/gm);
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] double slashes redirect', async t => {
|
||||
const directory = fixture('01-node');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
// Wait for `now dev` to boot up
|
||||
await sleep(ms('10s'));
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] double slashes redirect',
|
||||
testFixtureStdio('01-node', async (t, port) => {
|
||||
{
|
||||
const res = await fetch(`http://localhost:${port}////?foo=bar`, {
|
||||
redirect: 'manual',
|
||||
@@ -1041,31 +948,24 @@ test('[now dev] double slashes redirect', async t => {
|
||||
body.startsWith('December')
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] 18-marko', async t => {
|
||||
const tester = testFixtureStdio('18-marko', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 18-marko',
|
||||
testFixtureStdio('18-marko', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Marko Starter/gm);
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 19-mithril',
|
||||
testFixtureStdio('19-mithril', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Mithril on ZEIT Now/gm);
|
||||
})
|
||||
@@ -1074,52 +974,42 @@ test(
|
||||
test(
|
||||
'[now dev] 20-riot',
|
||||
testFixtureStdio('20-riot', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Riot on ZEIT Now/gm);
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] 21-charge', async t => {
|
||||
const tester = testFixtureStdio('21-charge', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
test(
|
||||
'[now dev] 21-charge',
|
||||
testFixtureStdio('21-charge', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Welcome to my new Charge site/gm);
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 22-brunch',
|
||||
testFixtureStdio('22-brunch', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 50);
|
||||
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Bon Appétit./gm);
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] 23-docusaurus', async t => {
|
||||
const tester = testFixtureStdio('23-docusaurus', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
test(
|
||||
'[now dev] 23-docusaurus',
|
||||
testFixtureStdio('23-docusaurus', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /My Site/gm);
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] 24-ember', async t => {
|
||||
if (shouldSkip(t, '24-ember', '>^6.14.0 || ^8.10.0 || >=9.10.0')) return;
|
||||
@@ -1136,16 +1026,12 @@ test('[now dev] 24-ember', async t => {
|
||||
tester(t);
|
||||
});
|
||||
|
||||
test('[now dev] temporary directory listing', async t => {
|
||||
const directory = fixture('temporary-directory-listing');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
test(
|
||||
'[now dev] temporary directory listing',
|
||||
testFixtureStdio('temporary-directory-listing', async (t, port) => {
|
||||
const directory = fixture('temporary-directory-listing');
|
||||
await fs.unlink(path.join(directory, 'index.txt')).catch(() => null);
|
||||
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
await sleep(ms('20s'));
|
||||
|
||||
const firstResponse = await fetch(`http://localhost:${port}`);
|
||||
@@ -1166,10 +1052,8 @@ test('[now dev] temporary directory listing', async t => {
|
||||
|
||||
await sleep(ms('1s'));
|
||||
}
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] add a `package.json` to trigger `@now/static-build`', async t => {
|
||||
const directory = fixture('trigger-static-build');
|
||||
@@ -1240,13 +1124,9 @@ test('[now dev] no build matches warning', async t => {
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] do not recursivly check the path', async t => {
|
||||
const directory = fixture('handle-filesystem-missing');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
dev.unref();
|
||||
|
||||
test(
|
||||
'[now dev] do not recursivly check the path',
|
||||
testFixtureStdio('handle-filesystem-missing', async (t, port) => {
|
||||
{
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
validateResponseHeaders(t, response);
|
||||
@@ -1259,10 +1139,8 @@ test('[now dev] do not recursivly check the path', async t => {
|
||||
validateResponseHeaders(t, response);
|
||||
t.is(response.status, 404);
|
||||
}
|
||||
} finally {
|
||||
dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] render warning for empty cwd dir', async t => {
|
||||
const directory = fixture('empty');
|
||||
@@ -1356,24 +1234,15 @@ test('[now dev] do not rebuild for changes in the output directory', async t =>
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] 25-nextjs-src-dir', async t => {
|
||||
const directory = fixture('25-nextjs-src-dir');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 80);
|
||||
|
||||
test(
|
||||
'[now dev] 25-nextjs-src-dir',
|
||||
testFixtureStdio('25-nextjs-src-dir', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 10);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Next.js \+ Node.js API/gm);
|
||||
} finally {
|
||||
dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 26-nextjs-secrets',
|
||||
@@ -1389,6 +1258,16 @@ test(
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 27-zero-config-env',
|
||||
testFixtureStdio('27-zero-config-env', async (t, port) => {
|
||||
const api = await fetchWithRetry(`http://localhost:${port}/api/print`);
|
||||
const index = await fetchWithRetry(`http://localhost:${port}`);
|
||||
t.regex(await api.text(), new RegExp('build-and-runtime'));
|
||||
t.regex(await index.text(), new RegExp('build-and-runtime'));
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] Use `@now/python` with Flask requirements.txt',
|
||||
testFixtureStdio('python-flask', async (t, port) => {
|
||||
|
||||
@@ -323,6 +323,31 @@ CMD ["node", "index.js"]`,
|
||||
'index.html': 'Home page',
|
||||
'secret/file.txt': 'my secret',
|
||||
},
|
||||
'build-secret': {
|
||||
'package.json': JSON.stringify({
|
||||
private: true,
|
||||
scripts: {
|
||||
build: 'mkdir public && echo $MY_SECRET > public/index.txt',
|
||||
},
|
||||
}),
|
||||
'now.json': JSON.stringify({
|
||||
build: {
|
||||
env: {
|
||||
MY_SECRET: '@mysecret',
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
'api-env': {
|
||||
'api/get-env.js': 'module.exports = (_, res) => res.json(process.env)',
|
||||
'print.js': 'console.log(JSON.stringify(process.env))',
|
||||
'package.json': JSON.stringify({
|
||||
private: true,
|
||||
scripts: {
|
||||
build: 'mkdir public && node print.js > public/index.json',
|
||||
},
|
||||
}),
|
||||
},
|
||||
'alias-rules': {
|
||||
'rules.json': JSON.stringify({
|
||||
rules: [
|
||||
|
||||
272
packages/now-cli/test/integration.js
vendored
272
packages/now-cli/test/integration.js
vendored
@@ -135,8 +135,11 @@ const apiFetch = (url, { headers, ...options } = {}) => {
|
||||
};
|
||||
|
||||
const waitForPrompt = (cp, assertion) =>
|
||||
new Promise(resolve => {
|
||||
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);
|
||||
@@ -325,6 +328,197 @@ test('deploy using --local-config flag above target', async t => {
|
||||
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, /0 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, /4 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('print the deploy help message', async t => {
|
||||
const { stderr, stdout, exitCode } = await execa(
|
||||
binaryPath,
|
||||
@@ -390,6 +584,50 @@ test('should error with suggestion for secrets subcommand', async t => {
|
||||
);
|
||||
});
|
||||
|
||||
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,
|
||||
@@ -1676,6 +1914,38 @@ test('print correct link in legacy warning', async t => {
|
||||
t.regex(stderr, /migrate-to-zeit-now/);
|
||||
});
|
||||
|
||||
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([
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now-client",
|
||||
"version": "7.0.2-canary.0",
|
||||
"version": "7.1.0",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://zeit.co",
|
||||
|
||||
@@ -169,9 +169,9 @@ export async function* deploy(
|
||||
deploymentOptions.forceNew = clientOptions.force;
|
||||
}
|
||||
|
||||
if (clientOptions.forceNewWithCache) {
|
||||
if (clientOptions.withCache) {
|
||||
debug(
|
||||
`'forceNewWithCache' is provided. Force deploy will be performed with cache retention`
|
||||
`'withCache' is provided. Force deploy will be performed with cache retention`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import buildCreateDeployment from './create-deployment';
|
||||
|
||||
export { getNowIgnore } from './utils/index';
|
||||
export const createDeployment = buildCreateDeployment(2);
|
||||
export const createLegacyDeployment = buildCreateDeployment(1);
|
||||
export * from './errors';
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface NowClientOptions {
|
||||
teamId?: string;
|
||||
apiUrl?: string;
|
||||
force?: boolean;
|
||||
forceNewWithCache?: boolean;
|
||||
withCache?: boolean;
|
||||
userAgent?: string;
|
||||
defaultName?: string;
|
||||
isDirectory?: boolean;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { nodeFetch, zeitFetch } from './fetch';
|
||||
import { join, sep, relative } from 'path';
|
||||
import qs from 'querystring';
|
||||
import ignore from 'ignore';
|
||||
type Ignore = ReturnType<typeof ignore>;
|
||||
import { pkgVersion } from '../pkg';
|
||||
import { NowClientOptions, DeploymentOptions, NowConfig } from '../types';
|
||||
import { Sema } from 'async-sema';
|
||||
@@ -74,15 +75,17 @@ const maybeRead = async function<T>(path: string, default_: T) {
|
||||
}
|
||||
};
|
||||
|
||||
export async function getNowIgnore(path: string | string[]): Promise<any> {
|
||||
let ignores: string[] = [
|
||||
'.hg',
|
||||
'.git',
|
||||
export async function getNowIgnore(
|
||||
path: string | string[]
|
||||
): Promise<{ ig: Ignore; ignores: string[] }> {
|
||||
const ignores: string[] = [
|
||||
'.hg/',
|
||||
'.git/',
|
||||
'.gitmodules',
|
||||
'.svn',
|
||||
'.svn/',
|
||||
'.cache',
|
||||
'.next',
|
||||
'.now',
|
||||
'.next/',
|
||||
'.now/',
|
||||
'.npmignore',
|
||||
'.dockerignore',
|
||||
'.gitignore',
|
||||
@@ -95,7 +98,7 @@ export async function getNowIgnore(path: string | string[]): Promise<any> {
|
||||
'.venv',
|
||||
'npm-debug.log',
|
||||
'config.gypi',
|
||||
'node_modules',
|
||||
'node_modules/',
|
||||
'__pycache__/',
|
||||
'venv/',
|
||||
'CVS',
|
||||
|
||||
@@ -12,8 +12,8 @@ export function generateQueryString(clientOptions: NowClientOptions): string {
|
||||
options.set('forceNew', '1');
|
||||
}
|
||||
|
||||
if (clientOptions.forceNewWithCache) {
|
||||
options.set('forceNewWithCache', '1');
|
||||
if (clientOptions.withCache) {
|
||||
options.set('withCache', '1');
|
||||
}
|
||||
|
||||
if (clientOptions.skipAutoDetectionConfirmation) {
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
now "github.com/zeit/now/utils/go/bridge"
|
||||
now "github.com/zeit/now-go-bridge/go/bridge"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"__NOW_HANDLER_PACKAGE_NAME"
|
||||
|
||||
now "github.com/zeit/now/utils/go/bridge"
|
||||
now "github.com/zeit/now-go-bridge/go/bridge"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/go",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.7",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/go",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/next",
|
||||
"version": "2.5.1",
|
||||
"version": "2.5.4",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/next-js",
|
||||
|
||||
@@ -569,7 +569,7 @@ export const build = async ({
|
||||
},
|
||||
|
||||
// error handling
|
||||
...(output['404']
|
||||
...(output[path.join('./', entryDirectory, '404')]
|
||||
? [
|
||||
{ handle: 'error' } as Handler,
|
||||
|
||||
@@ -745,10 +745,10 @@ export const build = async ({
|
||||
// this can be either 404.html in latest versions
|
||||
// or _errors/404.html versions while this was experimental
|
||||
static404Page =
|
||||
staticPages['404'] && hasPages404
|
||||
? '404'
|
||||
: staticPages['_errors/404']
|
||||
? '_errors/404'
|
||||
staticPages[path.join(entryDirectory, '404')] && hasPages404
|
||||
? path.join(entryDirectory, '404')
|
||||
: staticPages[path.join(entryDirectory, '_errors/404')]
|
||||
? path.join(entryDirectory, '_errors/404')
|
||||
: undefined;
|
||||
|
||||
// > 1 because _error is a lambda but isn't used if a static 404 is available
|
||||
@@ -829,7 +829,7 @@ export const build = async ({
|
||||
} = await nodeFileTrace(apiPages, { base: workPath });
|
||||
|
||||
const { fileList, reasons: nonApiReasons } = await nodeFileTrace(
|
||||
Object.keys(pages).map(page => pages[page].fsPath),
|
||||
nonApiPages,
|
||||
{ base: workPath }
|
||||
);
|
||||
|
||||
@@ -1259,17 +1259,18 @@ export const build = async ({
|
||||
|
||||
{
|
||||
src: path.join('/', entryDirectory, '.*'),
|
||||
dest: path.join(
|
||||
'/',
|
||||
entryDirectory,
|
||||
static404Page
|
||||
? static404Page
|
||||
: // if static 404 is not present but we have pages/404.js
|
||||
// it is a lambda due to _app getInitialProps
|
||||
hasPages404 && lambdas['404']
|
||||
? '404'
|
||||
: '_error'
|
||||
),
|
||||
// if static 404 is not present but we have pages/404.js
|
||||
// it is a lambda due to _app getInitialProps
|
||||
dest: static404Page
|
||||
? path.join('/', static404Page)
|
||||
: path.join(
|
||||
'/',
|
||||
entryDirectory,
|
||||
hasPages404 &&
|
||||
lambdas[path.join('./', entryDirectory, '404')]
|
||||
? '404'
|
||||
: '_error'
|
||||
),
|
||||
status: 404,
|
||||
},
|
||||
]),
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"propes": [
|
||||
"probes": [
|
||||
{
|
||||
"path": "/api/memory",
|
||||
"status": 200,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"next": "9.2.3-canary.4",
|
||||
"next": "latest",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"propes": [
|
||||
"probes": [
|
||||
{
|
||||
"path": "/",
|
||||
"status": 200,
|
||||
|
||||
1
packages/now-next/test/fixtures/25-mono-repo-404/.gitignore
vendored
Normal file
1
packages/now-next/test/fixtures/25-mono-repo-404/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.now
|
||||
36
packages/now-next/test/fixtures/25-mono-repo-404/now.json
vendored
Normal file
36
packages/now-next/test/fixtures/25-mono-repo-404/now.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"version": 2,
|
||||
"routes": [
|
||||
{ "src": "/(.*)", "dest": "/packages/webapp/$1", "continue": true }
|
||||
],
|
||||
"builds": [
|
||||
{
|
||||
"src": "packages/webapp/next.config.js",
|
||||
"use": "@now/next"
|
||||
}
|
||||
],
|
||||
"probes": [
|
||||
{
|
||||
"path": "/",
|
||||
"mustContain": "Hi"
|
||||
},
|
||||
{
|
||||
"path": "/",
|
||||
"responseHeaders": {
|
||||
"x-now-cache": "HIT"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/non-existent",
|
||||
"mustContain": "custom 404!!"
|
||||
},
|
||||
{
|
||||
"path": "/non-existent",
|
||||
"mustContain": "__next"
|
||||
},
|
||||
{
|
||||
"path": "/non-existent",
|
||||
"status": 404
|
||||
}
|
||||
]
|
||||
}
|
||||
7
packages/now-next/test/fixtures/25-mono-repo-404/package.json
vendored
Normal file
7
packages/now-next/test/fixtures/25-mono-repo-404/package.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"private": true,
|
||||
"name": "mono-repo"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user