mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-27 11:49:14 +00:00
Compare commits
76 Commits
@now/next@
...
@now/node@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c89cf6fd57 | ||
|
|
b7ad18425f | ||
|
|
5554b2d004 | ||
|
|
e4ce0d6802 | ||
|
|
f477ee6e3b | ||
|
|
59b4029a0c | ||
|
|
8411b53fa3 | ||
|
|
7651bcf4a4 | ||
|
|
6fd1b50924 | ||
|
|
a03b8689e9 | ||
|
|
22c8ed15c7 | ||
|
|
e1f7b859b6 | ||
|
|
c75cc99f1e | ||
|
|
30ddd29889 | ||
|
|
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.2-canary.0",
|
||||
"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;
|
||||
|
||||
@@ -50,7 +50,7 @@ export interface Config {
|
||||
export interface Meta {
|
||||
isDev?: boolean;
|
||||
skipDownload?: boolean;
|
||||
requestPath?: string;
|
||||
requestPath?: string | null;
|
||||
filesChanged?: string[];
|
||||
filesRemoved?: string[];
|
||||
env?: Env;
|
||||
@@ -187,6 +187,42 @@ export interface ShouldServeOptions {
|
||||
config: Config;
|
||||
}
|
||||
|
||||
export interface StartDevServerOptions {
|
||||
/**
|
||||
* Name of entrypoint file for this particular build job. Value
|
||||
* `files[entrypoint]` is guaranteed to exist and be a valid File reference.
|
||||
* `entrypoint` is always a discrete file and never a glob, since globs are
|
||||
* expanded into separate builds at deployment time.
|
||||
*/
|
||||
entrypoint: string;
|
||||
|
||||
/**
|
||||
* A writable temporary directory where you are encouraged to perform your
|
||||
* build process. This directory will be populated with the restored cache.
|
||||
*/
|
||||
workPath: string;
|
||||
}
|
||||
|
||||
export interface StartDevServerSuccess {
|
||||
/**
|
||||
* Port number where the dev server can be connected to, assumed to be running
|
||||
* on `localhost`.
|
||||
*/
|
||||
port: number;
|
||||
|
||||
/**
|
||||
* Process ID number of the dev server. Useful for the `now dev` server to
|
||||
* shut down the dev server once an HTTP request has been fulfilled.
|
||||
*/
|
||||
pid: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* `startDevServer()` may return `null` to opt-out of spawning a dev server for
|
||||
* a given `entrypoint`.
|
||||
*/
|
||||
export type StartDevServerResult = StartDevServerSuccess | null;
|
||||
|
||||
/**
|
||||
* Credit to Iain Reid, MIT license.
|
||||
* Source: https://gist.github.com/iainreid820/5c1cc527fe6b5b7dba41fec7fe54bf6e
|
||||
|
||||
@@ -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.5",
|
||||
"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,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/node": "5.5.0",
|
||||
"@sindresorhus/slugify": "0.10.0",
|
||||
"@sindresorhus/slugify": "0.11.0",
|
||||
"@tootallnate/once": "1.0.0",
|
||||
"@types/ansi-escapes": "3.0.0",
|
||||
"@types/ansi-regex": "4.0.0",
|
||||
"@types/async-retry": "1.2.1",
|
||||
@@ -141,8 +142,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}`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ const help = () => {
|
||||
--ca ${chalk.bold.underline(
|
||||
'FILE'
|
||||
)} CA certificate chain file
|
||||
-N, --next Show next page of results
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -61,6 +62,12 @@ const help = () => {
|
||||
${chalk.gray('–')} Remove a certificate
|
||||
|
||||
${chalk.cyan('$ now certs rm id')}
|
||||
|
||||
${chalk.gray('–')} Paginate results, where ${chalk.dim(
|
||||
'`1584722256178`'
|
||||
)} is the time in milliseconds since the UNIX epoch.
|
||||
|
||||
${chalk.cyan(`$ now certs ls --next 1584722256178`)}
|
||||
`);
|
||||
};
|
||||
|
||||
@@ -69,7 +76,7 @@ const COMMAND_CONFIG = {
|
||||
issue: ['issue'],
|
||||
ls: ['ls', 'list'],
|
||||
renew: ['renew'],
|
||||
rm: ['rm', 'remove']
|
||||
rm: ['rm', 'remove'],
|
||||
};
|
||||
|
||||
export default async function main(ctx: NowContext) {
|
||||
@@ -80,10 +87,11 @@ export default async function main(ctx: NowContext) {
|
||||
'--challenge-only': Boolean,
|
||||
'--overwrite': Boolean,
|
||||
'--output': String,
|
||||
'--after': String,
|
||||
'--crt': String,
|
||||
'--key': String,
|
||||
'--ca': String
|
||||
'--ca': String,
|
||||
'--next': Number,
|
||||
'-N': '--next',
|
||||
});
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import plural from 'pluralize';
|
||||
import psl from 'psl';
|
||||
import table from 'text-table';
|
||||
// @ts-ignore
|
||||
import Now from '../../util';
|
||||
@@ -10,14 +8,14 @@ import Client from '../../util/client';
|
||||
import getScope from '../../util/get-scope';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import getCerts from '../../util/certs/get-certs';
|
||||
import { CertNotFound } from '../../util/errors-ts';
|
||||
import strlen from '../../util/strlen';
|
||||
import { Output } from '../../util/output';
|
||||
import { NowContext, Cert } from '../../types';
|
||||
import getCommandFlags from '../../util/get-command-flags';
|
||||
|
||||
interface Options {
|
||||
'--debug'?: boolean;
|
||||
'--after'?: string;
|
||||
'--next'?: number;
|
||||
}
|
||||
|
||||
async function ls(
|
||||
@@ -32,8 +30,7 @@ async function ls(
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
const debug = opts['--debug'];
|
||||
const after = opts['--after'];
|
||||
const { '--debug': debug, '--next': nextTimestamp } = opts;
|
||||
const client = new Client({ apiUrl, token, currentTeam, debug });
|
||||
let contextName = null;
|
||||
|
||||
@@ -47,7 +44,10 @@ async function ls(
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (typeof nextTimestamp !== 'undefined' && Number.isNaN(nextTimestamp)) {
|
||||
output.error('Please provide a number for flag --next');
|
||||
return 1;
|
||||
}
|
||||
const now = new Now({ apiUrl, token, debug, currentTeam });
|
||||
const lsStamp = stamp();
|
||||
|
||||
@@ -59,38 +59,29 @@ async function ls(
|
||||
}
|
||||
|
||||
// Get the list of certificates
|
||||
const certificates = await getCerts(output, now, { after }).catch(err => err);
|
||||
|
||||
if (certificates instanceof CertNotFound) {
|
||||
output.error(certificates.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (certificates instanceof Error) {
|
||||
throw certificates;
|
||||
}
|
||||
|
||||
const certs = sortByCn(certificates);
|
||||
|
||||
output.log(
|
||||
`${plural('certificate', certs.length, true)} found under ${chalk.bold(
|
||||
contextName
|
||||
)} ${lsStamp()}`
|
||||
const { certs, pagination } = await getCerts(now, nextTimestamp).catch(
|
||||
err => err
|
||||
);
|
||||
|
||||
if (certs.length >= 100) {
|
||||
const { uid: lastCert } = certificates[certificates.length - 1];
|
||||
output.note(
|
||||
`There may be more certificates that can be retrieved with ${cmd(
|
||||
`now ${process.argv.slice(2).join(' ')} --after=${lastCert}`
|
||||
)}.\n`
|
||||
);
|
||||
}
|
||||
output.log(
|
||||
`${
|
||||
certs.length > 0 ? 'Certificates' : 'No certificates'
|
||||
} found under ${chalk.bold(contextName)} ${lsStamp()}`
|
||||
);
|
||||
|
||||
if (certs.length > 0) {
|
||||
console.log(formatCertsTable(certs));
|
||||
}
|
||||
|
||||
if (pagination && pagination.count === 20) {
|
||||
const flags = getCommandFlags(opts, ['_', '--next']);
|
||||
output.log(
|
||||
`To display the next page run ${cmd(
|
||||
`now certs ls${flags} --next ${pagination.next}`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -161,18 +152,4 @@ function formatExpirationDate(date: Date) {
|
||||
: chalk.gray(`in ${ms(diff)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function sorts the list of certs by root domain changing *
|
||||
* to 'wildcard' since that will allow psl get the root domain
|
||||
* properly to make the comparison.
|
||||
*/
|
||||
function sortByCn(certsList: Cert[]) {
|
||||
return certsList.concat().sort((a: Cert, b: Cert) => {
|
||||
const domainA = psl.get(a.cns[0].replace('*', 'wildcard'));
|
||||
const domainB = psl.get(b.cns[0].replace('*', 'wildcard'));
|
||||
if (!domainA || !domainB) return 0;
|
||||
return domainA.localeCompare(domainB);
|
||||
});
|
||||
}
|
||||
|
||||
export default ls;
|
||||
|
||||
@@ -98,7 +98,7 @@ async function getCertsToDelete(
|
||||
contextName: string,
|
||||
id: string
|
||||
) {
|
||||
const cert = await getCertById(output, client, id);
|
||||
const cert = await getCertById(client, id);
|
||||
if (cert instanceof ERRORS.CertNotFound) {
|
||||
const certs = await getCertsForDomain(output, client, contextName, id);
|
||||
if (certs instanceof ERRORS.CertsPermissionDenied) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -2,7 +2,6 @@ import chalk from 'chalk';
|
||||
import table from 'text-table';
|
||||
import mri from 'mri';
|
||||
import ms from 'ms';
|
||||
import plural from 'pluralize';
|
||||
import strlen from '../util/strlen.ts';
|
||||
import { handleError, error } from '../util/error';
|
||||
import NowSecrets from '../util/secrets';
|
||||
@@ -12,6 +11,8 @@ import Client from '../util/client.ts';
|
||||
import getScope from '../util/get-scope.ts';
|
||||
import createOutput from '../util/output';
|
||||
import confirm from '../util/input/confirm';
|
||||
import getCommandFlags from '../util/get-command-flags';
|
||||
import cmd from '../util/output/cmd.ts';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -38,6 +39,7 @@ const help = () => {
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
-N, --next Show next page of results
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -60,6 +62,12 @@ const help = () => {
|
||||
)} symbol)
|
||||
|
||||
${chalk.cyan(`$ now -e MY_SECRET=${chalk.bold('@my-secret')}`)}
|
||||
|
||||
${chalk.gray('–')} Paginate results, where ${chalk.dim(
|
||||
'`1584722256178`'
|
||||
)} is the time in milliseconds since the UNIX epoch.
|
||||
|
||||
${chalk.cyan(`$ now secrets ls --next 1584722256178`)}
|
||||
`);
|
||||
};
|
||||
|
||||
@@ -68,6 +76,7 @@ let argv;
|
||||
let debug;
|
||||
let apiUrl;
|
||||
let subcommand;
|
||||
let nextTimestamp;
|
||||
|
||||
const main = async ctx => {
|
||||
argv = mri(ctx.argv.slice(2), {
|
||||
@@ -76,6 +85,7 @@ const main = async ctx => {
|
||||
help: 'h',
|
||||
debug: 'd',
|
||||
yes: 'y',
|
||||
next: 'N',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -84,6 +94,7 @@ const main = async ctx => {
|
||||
debug = argv.debug;
|
||||
apiUrl = ctx.apiUrl;
|
||||
subcommand = argv._[0];
|
||||
nextTimestamp = argv.next;
|
||||
|
||||
if (argv.help || !subcommand) {
|
||||
help();
|
||||
@@ -110,7 +121,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,13 +137,13 @@ 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();
|
||||
|
||||
if (subcommand === 'ls' || subcommand === 'list') {
|
||||
if (args.length !== 0) {
|
||||
if (args.length > 1) {
|
||||
console.error(
|
||||
error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan('`now secret ls`')}`
|
||||
@@ -141,11 +152,11 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
const list = await secrets.ls();
|
||||
const elapsed = ms(new Date() - start);
|
||||
const { secrets: list, pagination } = await secrets.ls(nextTimestamp);
|
||||
const elapsed = ms(Date.now() - start);
|
||||
|
||||
console.log(
|
||||
`${plural('secret', list.length, true)} found under ${chalk.bold(
|
||||
`${list.length > 0 ? 'Secrets' : 'No secrets'} found under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
@@ -172,6 +183,19 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
console.log(`\n${out}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
if (pagination && pagination.count === 20) {
|
||||
const prefixedArgs = getPrefixedFlags(argv);
|
||||
const flags = getCommandFlags(prefixedArgs, [
|
||||
'_',
|
||||
'--next',
|
||||
'-N',
|
||||
'-d',
|
||||
'-y',
|
||||
]);
|
||||
const nextCmd = `now secrets ${subcommand}${flags} --next ${pagination.next}`;
|
||||
output.log(`To display the next page run ${cmd(nextCmd)}`);
|
||||
}
|
||||
return secrets.close();
|
||||
}
|
||||
|
||||
@@ -186,8 +210,8 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
);
|
||||
return exit(1);
|
||||
}
|
||||
const list = await secrets.ls();
|
||||
const theSecret = list.find(secret => secret.name === args[0]);
|
||||
|
||||
const theSecret = await secrets.getSecretByNameOrId(args[0]);
|
||||
|
||||
if (theSecret) {
|
||||
const yes =
|
||||
@@ -260,7 +284,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);
|
||||
|
||||
@@ -304,3 +349,28 @@ async function readConfirmation(output, secret, contextName) {
|
||||
|
||||
return confirm(`${chalk.bold.red('Are you sure?')}`, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function adds a prefix `-` or `--` to the flags
|
||||
* passed from the command line, because the package `mri`
|
||||
* used to extract the args removes them for some reason.
|
||||
*/
|
||||
function getPrefixedFlags(args) {
|
||||
const prefixedArgs = {};
|
||||
|
||||
for (const arg in args) {
|
||||
if (arg === '_') {
|
||||
prefixedArgs[arg] = argv[arg];
|
||||
} else {
|
||||
let prefix = '-';
|
||||
// Full form flags need two dashes, whereas one letter
|
||||
// flags need only one.
|
||||
if (arg.length > 1) {
|
||||
prefix = '--';
|
||||
}
|
||||
prefixedArgs[`${prefix}${arg}`] = argv[arg];
|
||||
}
|
||||
}
|
||||
|
||||
return prefixedArgs;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { Cert } from '../../types';
|
||||
import { Output } from '../output/create-output';
|
||||
import Client from '../client';
|
||||
import * as ERRORS from '../errors-ts';
|
||||
|
||||
export default async function getCertById(
|
||||
output: Output,
|
||||
client: Client,
|
||||
id: string
|
||||
) {
|
||||
export default async function getCertById(client: Client, id: string) {
|
||||
try {
|
||||
return await client.fetch<Cert>(`/v3/now/certs/${id}`);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,47 +1,17 @@
|
||||
import { URLSearchParams } from 'url';
|
||||
import Client from '../client';
|
||||
import { Output } from '../output';
|
||||
import { Cert } from '../../types';
|
||||
import getCertById from './get-cert-by-id';
|
||||
import { CertNotFound } from '../errors-ts';
|
||||
import { Cert, PaginationOptions } from '../../types';
|
||||
|
||||
type Response = {
|
||||
certs: Cert[];
|
||||
pagination: PaginationOptions;
|
||||
};
|
||||
|
||||
function sortByCreated(a: Cert, b: Cert) {
|
||||
const dateA = new Date(a.created);
|
||||
const dateB = new Date(b.created);
|
||||
export default async function getCerts(client: Client, next?: number) {
|
||||
let certsUrl = `/v4/now/certs?limit=20`;
|
||||
|
||||
if (dateA > dateB) {
|
||||
return -1;
|
||||
if (next) {
|
||||
certsUrl += `&until=${next}`;
|
||||
}
|
||||
|
||||
if (dateA < dateB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
export default async function getCerts(
|
||||
output: Output,
|
||||
client: Client,
|
||||
options?: { after?: string }
|
||||
) {
|
||||
const query = new URLSearchParams({ limit: '100' });
|
||||
|
||||
if (options && options.after) {
|
||||
const lastCert = await getCertById(output, client, options.after);
|
||||
|
||||
if (lastCert instanceof CertNotFound) {
|
||||
throw lastCert;
|
||||
}
|
||||
|
||||
query.set('until', new Date(lastCert.created).getTime().toString());
|
||||
}
|
||||
|
||||
const { certs } = await client.fetch<Response>(`/v3/now/certs?${query}`);
|
||||
|
||||
return certs.sort(sortByCreated);
|
||||
return await client.fetch<Response>(certsUrl);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -24,19 +24,19 @@ function onMessage(message) {
|
||||
}
|
||||
|
||||
async function processMessage(message) {
|
||||
const { builderName, buildParams } = message;
|
||||
const { builderName, buildOptions } = message;
|
||||
const builder = require(builderName);
|
||||
|
||||
// Convert the `files` to back into `FileFsRef` instances
|
||||
for (const name of Object.keys(buildParams.files)) {
|
||||
for (const name of Object.keys(buildOptions.files)) {
|
||||
const ref = Object.assign(
|
||||
Object.create(FileFsRef.prototype),
|
||||
buildParams.files[name]
|
||||
buildOptions.files[name]
|
||||
);
|
||||
buildParams.files[name] = ref;
|
||||
buildOptions.files[name] = ref;
|
||||
}
|
||||
|
||||
const result = await builder.build(buildParams);
|
||||
const result = await builder.build(buildOptions);
|
||||
|
||||
// `@now/next` sets this, but it causes "Converting circular
|
||||
// structure to JSON" errors, so delete the property...
|
||||
|
||||
@@ -2,24 +2,30 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import ms from 'ms';
|
||||
import bytes from 'bytes';
|
||||
import { promisify } from 'util';
|
||||
import { delimiter, dirname, join } from 'path';
|
||||
import { fork, ChildProcess } from 'child_process';
|
||||
import { createFunction } from '@zeit/fun';
|
||||
import { Builder, File, Lambda, FileBlob, FileFsRef } from '@now/build-utils';
|
||||
import {
|
||||
Builder,
|
||||
BuildOptions,
|
||||
Env,
|
||||
File,
|
||||
Lambda,
|
||||
FileBlob,
|
||||
FileFsRef,
|
||||
} from '@now/build-utils';
|
||||
import plural from 'pluralize';
|
||||
import minimatch from 'minimatch';
|
||||
import _treeKill from 'tree-kill';
|
||||
|
||||
import { Output } from '../output';
|
||||
import highlight from '../output/highlight';
|
||||
import { treeKill } from '../tree-kill';
|
||||
import { relative } from '../path-helpers';
|
||||
import { LambdaSizeExceededError } from '../errors-ts';
|
||||
|
||||
import DevServer from './server';
|
||||
import { builderModulePathPromise, getBuilder } from './builder-cache';
|
||||
import {
|
||||
EnvConfig,
|
||||
NowConfig,
|
||||
BuildMatch,
|
||||
BuildResult,
|
||||
@@ -27,6 +33,7 @@ import {
|
||||
BuilderOutput,
|
||||
BuildResultV3,
|
||||
BuilderOutputs,
|
||||
EnvConfigs,
|
||||
} from './types';
|
||||
import { normalizeRoutes } from '@now/routing-utils';
|
||||
import getUpdateCommand from '../get-update-command';
|
||||
@@ -41,11 +48,9 @@ interface BuildMessageResult extends BuildMessage {
|
||||
error?: object;
|
||||
}
|
||||
|
||||
const treeKill = promisify(_treeKill);
|
||||
|
||||
async function createBuildProcess(
|
||||
match: BuildMatch,
|
||||
buildEnv: EnvConfig,
|
||||
envConfigs: EnvConfigs,
|
||||
workPath: string,
|
||||
output: Output,
|
||||
yarnPath?: string
|
||||
@@ -61,10 +66,10 @@ async function createBuildProcess(
|
||||
PATH = `${yarnPath}${delimiter}${PATH}`;
|
||||
}
|
||||
|
||||
const env: EnvConfig = {
|
||||
const env: Env = {
|
||||
...process.env,
|
||||
PATH,
|
||||
...buildEnv,
|
||||
...envConfigs.allEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
};
|
||||
|
||||
@@ -109,7 +114,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 +136,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 buildOptions: BuildOptions = {
|
||||
files,
|
||||
entrypoint,
|
||||
workPath,
|
||||
@@ -148,8 +153,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,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -158,7 +165,7 @@ export async function executeBuild(
|
||||
buildProcess.send({
|
||||
type: 'build',
|
||||
builderName: pkg.name,
|
||||
buildParams,
|
||||
buildOptions,
|
||||
});
|
||||
|
||||
buildResultOrOutputs = await new Promise((resolve, reject) => {
|
||||
@@ -189,7 +196,7 @@ export async function executeBuild(
|
||||
buildProcess!.on('message', onMessage);
|
||||
});
|
||||
} else {
|
||||
buildResultOrOutputs = await builder.build(buildParams);
|
||||
buildResultOrOutputs = await builder.build(buildOptions);
|
||||
}
|
||||
|
||||
// Sort out build result to builder v2 shape
|
||||
@@ -361,7 +368,7 @@ export async function executeBuild(
|
||||
Variables: {
|
||||
...nowConfig.env,
|
||||
...asset.environment,
|
||||
...env,
|
||||
...envConfigs.runEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ import PCRE from 'pcre-to-regexp';
|
||||
import isURL from './is-url';
|
||||
import DevServer from './server';
|
||||
|
||||
import { HttpHeadersConfig, RouteConfig, RouteResult } from './types';
|
||||
import { HttpHeadersConfig, RouteResult } from './types';
|
||||
import { isHandler, Route, HandleValue } from '@now/routing-utils';
|
||||
|
||||
export function resolveRouteParameters(
|
||||
@@ -48,22 +48,24 @@ export function getRoutesTypes(routes: Route[] = []) {
|
||||
export async function devRouter(
|
||||
reqUrl: string = '/',
|
||||
reqMethod?: string,
|
||||
routes?: RouteConfig[],
|
||||
routes?: Route[],
|
||||
devServer?: DevServer,
|
||||
previousHeaders?: HttpHeadersConfig,
|
||||
missRoutes?: RouteConfig[],
|
||||
missRoutes?: Route[],
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ import {
|
||||
getTransformedRoutes,
|
||||
appendRoutesToPhase,
|
||||
HandleValue,
|
||||
Route,
|
||||
} from '@now/routing-utils';
|
||||
import once from '@tootallnate/once';
|
||||
import directoryTemplate from 'serve-handler/src/directory';
|
||||
import getPort from 'get-port';
|
||||
import { ChildProcess } from 'child_process';
|
||||
@@ -25,6 +27,8 @@ import which from 'which';
|
||||
|
||||
import {
|
||||
Builder,
|
||||
Env,
|
||||
StartDevServerResult,
|
||||
FileFsRef,
|
||||
PackageJson,
|
||||
detectBuilders,
|
||||
@@ -33,9 +37,9 @@ import {
|
||||
spawnCommand,
|
||||
} from '@now/build-utils';
|
||||
|
||||
import { once } from '../once';
|
||||
import link from '../output/link';
|
||||
import { Output } from '../output';
|
||||
import { treeKill } from '../tree-kill';
|
||||
import { relative } from '../path-helpers';
|
||||
import { getDistTag } from '../get-dist-tag';
|
||||
import getNowConfigPath from '../config/local-path';
|
||||
@@ -72,7 +76,6 @@ import errorTemplate502 from './templates/error_502';
|
||||
import redirectTemplate from './templates/redirect';
|
||||
|
||||
import {
|
||||
EnvConfig,
|
||||
NowConfig,
|
||||
DevServerOptions,
|
||||
BuildMatch,
|
||||
@@ -83,8 +86,9 @@ import {
|
||||
InvokePayload,
|
||||
InvokeResult,
|
||||
ListenSpec,
|
||||
RouteConfig,
|
||||
RouteResult,
|
||||
HttpHeadersConfig,
|
||||
EnvConfigs,
|
||||
} from './types';
|
||||
|
||||
interface FSEvent {
|
||||
@@ -108,8 +112,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;
|
||||
@@ -132,6 +135,7 @@ export default class DevServer {
|
||||
private devCommand?: string;
|
||||
private devProcess?: ChildProcess;
|
||||
private devProcessPort?: number;
|
||||
private devServerPids: Set<number>;
|
||||
|
||||
private getNowConfigPromise: Promise<NowConfig> | null;
|
||||
private blockingBuildsPromise: Promise<void> | null;
|
||||
@@ -141,8 +145,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;
|
||||
@@ -153,7 +156,7 @@ export default class DevServer {
|
||||
|
||||
this.cachedNowConfig = null;
|
||||
this.apiDir = null;
|
||||
this.apiExtensions = new Set<string>();
|
||||
this.apiExtensions = new Set();
|
||||
this.server = http.createServer(this.devServerHandler);
|
||||
this.server.timeout = 0; // Disable timeout
|
||||
this.serverUrlPrinted = false;
|
||||
@@ -173,6 +176,8 @@ export default class DevServer {
|
||||
this.podId = Math.random()
|
||||
.toString(32)
|
||||
.slice(-5);
|
||||
|
||||
this.devServerPids = new Set();
|
||||
}
|
||||
|
||||
async exit(code = 1) {
|
||||
@@ -451,11 +456,11 @@ export default class DevServer {
|
||||
await this.updateBuildMatches(nowConfig);
|
||||
}
|
||||
|
||||
async getLocalEnv(fileName: string, base?: EnvConfig): Promise<EnvConfig> {
|
||||
async getLocalEnv(fileName: string, base?: Env): Promise<Env> {
|
||||
// TODO: use the file watcher to only invalidate the env `dotfile`
|
||||
// once a change to the `fileName` occurs
|
||||
const filePath = join(this.cwd, fileName);
|
||||
let env: EnvConfig = {};
|
||||
let env: Env = {};
|
||||
try {
|
||||
const dotenv = await fs.readFile(filePath, 'utf8');
|
||||
this.output.debug(`Using local env: ${filePath}`);
|
||||
@@ -580,7 +585,7 @@ export default class DevServer {
|
||||
config.builds = config.builds || [];
|
||||
config.builds.push(...builders);
|
||||
|
||||
const routes: RouteConfig[] = [];
|
||||
const routes: Route[] = [];
|
||||
const { routes: nowConfigRoutes } = config;
|
||||
routes.push(...(redirectRoutes || []));
|
||||
routes.push(
|
||||
@@ -664,11 +669,7 @@ export default class DevServer {
|
||||
await this.tryValidateOrExit(config, validateNowConfigFunctions);
|
||||
}
|
||||
|
||||
validateEnvConfig(
|
||||
type: string,
|
||||
env: EnvConfig = {},
|
||||
localEnv: EnvConfig = {}
|
||||
): EnvConfig {
|
||||
validateEnvConfig(type: string, env: Env = {}, localEnv: Env = {}): Env {
|
||||
// Validate if there are any missing env vars defined in `now.json`,
|
||||
// but not in the `.env` / `.build.env` file
|
||||
const missing: string[] = Object.entries(env)
|
||||
@@ -684,7 +685,7 @@ export default class DevServer {
|
||||
throw new MissingDotenvVarsError(type, missing);
|
||||
}
|
||||
|
||||
const merged: EnvConfig = { ...env, ...localEnv };
|
||||
const merged: Env = { ...env, ...localEnv };
|
||||
|
||||
// Validate that the env var name matches what AWS Lambda allows:
|
||||
// - https://docs.aws.amazon.com/lambda/latest/dg/env_variables.html
|
||||
@@ -739,13 +740,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);
|
||||
@@ -872,6 +873,7 @@ export default class DevServer {
|
||||
*/
|
||||
async stop(exitCode?: number): Promise<void> {
|
||||
const { devProcess } = this;
|
||||
const { debug, log } = this.output;
|
||||
if (this.stopping) return;
|
||||
|
||||
this.stopping = true;
|
||||
@@ -879,7 +881,7 @@ export default class DevServer {
|
||||
if (this.serverUrlPrinted) {
|
||||
// This makes it look cleaner
|
||||
process.stdout.write('\n');
|
||||
this.output.log(`Stopping ${chalk.bold('`now dev`')} server`);
|
||||
log(`Stopping ${chalk.bold('`now dev`')} server`);
|
||||
}
|
||||
|
||||
const ops: Promise<void>[] = [];
|
||||
@@ -908,15 +910,29 @@ export default class DevServer {
|
||||
ops.push(close(this.server));
|
||||
|
||||
if (this.watcher) {
|
||||
this.output.debug(`Closing file watcher`);
|
||||
debug(`Closing file watcher`);
|
||||
this.watcher.close();
|
||||
}
|
||||
|
||||
if (this.updateBuildersPromise) {
|
||||
this.output.debug(`Waiting for builders update to complete`);
|
||||
debug(`Waiting for builders update to complete`);
|
||||
ops.push(this.updateBuildersPromise);
|
||||
}
|
||||
|
||||
for (const pid of this.devServerPids) {
|
||||
debug(`Killing builder dev server with PID ${pid}`);
|
||||
ops.push(
|
||||
treeKill(pid).then(
|
||||
() => {
|
||||
debug(`Killed builder dev server with PID ${pid}`);
|
||||
},
|
||||
(err: Error) => {
|
||||
debug(`Failed to kill builder dev server with PID ${pid}: ${err}`);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(ops);
|
||||
} catch (err) {
|
||||
@@ -1222,9 +1238,11 @@ export default class DevServer {
|
||||
res: http.ServerResponse,
|
||||
nowRequestId: string,
|
||||
nowConfig: NowConfig,
|
||||
routes: RouteConfig[] | undefined = nowConfig.routes,
|
||||
routes: Route[] | undefined = nowConfig.routes,
|
||||
callLevel: number = 0
|
||||
) => {
|
||||
const { debug } = this.output;
|
||||
|
||||
// If there is a double-slash present in the URL,
|
||||
// then perform a redirect to make it "clean".
|
||||
const parsed = url.parse(req.url || '/');
|
||||
@@ -1241,16 +1259,14 @@ export default class DevServer {
|
||||
return;
|
||||
}
|
||||
|
||||
this.output.debug(`Rewriting URL from "${req.url}" to "${location}"`);
|
||||
debug(`Rewriting URL from "${req.url}" to "${location}"`);
|
||||
req.url = location;
|
||||
}
|
||||
|
||||
await this.updateBuildMatches(nowConfig);
|
||||
|
||||
if (this.blockingBuildsPromise) {
|
||||
this.output.debug(
|
||||
'Waiting for builds to complete before handling request'
|
||||
);
|
||||
debug('Waiting for builds to complete before handling request');
|
||||
await this.blockingBuildsPromise;
|
||||
}
|
||||
|
||||
@@ -1264,19 +1280,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
|
||||
@@ -1285,7 +1307,7 @@ export default class DevServer {
|
||||
Object.assign(destParsed.query, routeResult.uri_args);
|
||||
const destUrl = url.format(destParsed);
|
||||
|
||||
this.output.debug(`ProxyPass: ${destUrl}`);
|
||||
debug(`ProxyPass: ${destUrl}`);
|
||||
this.setResponseHeaders(res, nowRequestId);
|
||||
return proxyPass(req, res, destUrl, this.output);
|
||||
}
|
||||
@@ -1384,7 +1406,7 @@ export default class DevServer {
|
||||
if (!match) {
|
||||
// if the dev command is started, proxy to it
|
||||
if (this.devProcessPort) {
|
||||
this.output.debug('Proxy to dev command server');
|
||||
debug('Proxying to frontend dev server');
|
||||
return proxyPass(
|
||||
req,
|
||||
res,
|
||||
@@ -1416,7 +1438,7 @@ export default class DevServer {
|
||||
origUrl.pathname = dest;
|
||||
Object.assign(origUrl.query, uri_args);
|
||||
const newUrl = url.format(origUrl);
|
||||
this.output.debug(
|
||||
debug(
|
||||
`Checking build result's ${buildResult.routes.length} \`routes\` to match ${newUrl}`
|
||||
);
|
||||
const matchedRoute = await devRouter(
|
||||
@@ -1426,9 +1448,7 @@ export default class DevServer {
|
||||
this
|
||||
);
|
||||
if (matchedRoute.found && callLevel === 0) {
|
||||
this.output.debug(
|
||||
`Found matching route ${matchedRoute.dest} for ${newUrl}`
|
||||
);
|
||||
debug(`Found matching route ${matchedRoute.dest} for ${newUrl}`);
|
||||
req.url = newUrl;
|
||||
await this.serveProjectAsNowV2(
|
||||
req,
|
||||
@@ -1442,7 +1462,82 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
|
||||
// Before doing any asset matching, check if this builder supports the
|
||||
// `startDevServer()` "optimization". In this case, the now dev server invokes
|
||||
// `startDevServer()` on the builder for every HTTP request so that it boots
|
||||
// up a single-serve dev HTTP server that now dev will proxy this HTTP request
|
||||
// to. Once the proxied request is finished, now dev shuts down the dev
|
||||
// server child process.
|
||||
const { builder, package: builderPkg } = match.builderWithPkg;
|
||||
if (typeof builder.startDevServer === 'function') {
|
||||
let devServerResult: StartDevServerResult = null;
|
||||
try {
|
||||
devServerResult = await builder.startDevServer({
|
||||
entrypoint: match.entrypoint,
|
||||
workPath: this.cwd,
|
||||
});
|
||||
} catch (err) {
|
||||
// `starDevServer()` threw an error. Most likely this means the dev
|
||||
// server process exited before sending the port information message
|
||||
// (missing dependency at runtime, for example).
|
||||
console.error(`Error starting "${builderPkg.name}" dev server:`, err);
|
||||
await this.sendError(
|
||||
req,
|
||||
res,
|
||||
nowRequestId,
|
||||
'NO_STATUS_CODE_FROM_DEV_SERVER',
|
||||
502
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (devServerResult) {
|
||||
const { port, pid } = devServerResult;
|
||||
this.devServerPids.add(pid);
|
||||
|
||||
res.once('close', () => {
|
||||
debug(`Killing builder dev server with PID ${pid}`);
|
||||
treeKill(pid).then(
|
||||
() => {
|
||||
this.devServerPids.delete(pid);
|
||||
debug(`Killed builder dev server with PID ${pid}`);
|
||||
},
|
||||
(err: Error) => {
|
||||
this.devServerPids.delete(pid);
|
||||
debug(
|
||||
`Failed to kill builder dev server with PID ${pid}: ${err}`
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
debug(
|
||||
`Proxying to "${builderPkg.name}" dev server (port=${port}, pid=${pid})`
|
||||
);
|
||||
|
||||
// Mix in the routing based query parameters
|
||||
const parsed = url.parse(req.url || '/', true);
|
||||
Object.assign(parsed.query, uri_args);
|
||||
req.url = url.format({
|
||||
pathname: parsed.pathname,
|
||||
query: parsed.query,
|
||||
});
|
||||
|
||||
this.setResponseHeaders(res, nowRequestId);
|
||||
return proxyPass(
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${port}`,
|
||||
this.output,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
debug(`Skipping \`startDevServer()\` for ${match.entrypoint}`);
|
||||
}
|
||||
}
|
||||
|
||||
let foundAsset = findAsset(match, requestPath, nowConfig);
|
||||
|
||||
if (!foundAsset && callLevel === 0) {
|
||||
await this.triggerBuild(match, buildRequestPath, req);
|
||||
|
||||
@@ -1450,25 +1545,30 @@ 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'))
|
||||
) {
|
||||
debug('Proxying to frontend dev server');
|
||||
return proxyPass(
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${this.devProcessPort}`,
|
||||
this.output,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (!foundAsset) {
|
||||
await this.send404(req, res, nowRequestId);
|
||||
return;
|
||||
}
|
||||
|
||||
const { asset, assetKey } = foundAsset;
|
||||
this.output.debug(
|
||||
debug(
|
||||
`Serving asset: [${asset.type}] ${assetKey} ${(asset as any)
|
||||
.contentType || ''}`
|
||||
);
|
||||
@@ -1533,7 +1633,7 @@ export default class DevServer {
|
||||
body: body.toString('base64'),
|
||||
};
|
||||
|
||||
this.output.debug(`Invoking lambda: "${assetKey}" with ${path}`);
|
||||
debug(`Invoking lambda: "${assetKey}" with ${path}`);
|
||||
|
||||
let result: InvokeResult;
|
||||
try {
|
||||
@@ -1676,14 +1776,13 @@ export default class DevServer {
|
||||
|
||||
const port = await getPort();
|
||||
|
||||
const env: EnvConfig = {
|
||||
const env: Env = {
|
||||
// Because of child process 'pipe' below, isTTY will be false.
|
||||
// Most frameworks use `chalk`/`supports-color` so we enable it anyway.
|
||||
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}`,
|
||||
};
|
||||
@@ -1886,7 +1985,7 @@ async function shouldServe(
|
||||
const shouldServe = await builder.shouldServe({
|
||||
entrypoint: src,
|
||||
files,
|
||||
config,
|
||||
config: config || {},
|
||||
requestPath,
|
||||
workPath: devServer.cwd,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { basename, extname, join } from 'path';
|
||||
import { BuilderParams, BuildResult, ShouldServeParams } from './types';
|
||||
import { FileFsRef, BuildOptions, ShouldServeOptions } from '@now/build-utils';
|
||||
import { BuildResult } from './types';
|
||||
|
||||
export const version = 2;
|
||||
|
||||
@@ -7,7 +8,7 @@ export function build({
|
||||
files,
|
||||
entrypoint,
|
||||
config,
|
||||
}: BuilderParams): BuildResult {
|
||||
}: BuildOptions): BuildResult {
|
||||
let path = entrypoint;
|
||||
const outputDir = config.zeroConfig ? config.outputDirectory : '';
|
||||
const outputMatch = outputDir + '/';
|
||||
@@ -16,7 +17,7 @@ export function build({
|
||||
path = path.slice(outputMatch.length);
|
||||
}
|
||||
const output = {
|
||||
[path]: files[entrypoint],
|
||||
[path]: files[entrypoint] as FileFsRef,
|
||||
};
|
||||
const watch = [path];
|
||||
|
||||
@@ -28,7 +29,7 @@ export function shouldServe({
|
||||
files,
|
||||
requestPath,
|
||||
config = {},
|
||||
}: ShouldServeParams) {
|
||||
}: ShouldServeOptions) {
|
||||
let outputPrefix = '';
|
||||
const outputDir = config.zeroConfig ? config.outputDirectory : '';
|
||||
const outputMatch = outputDir + '/';
|
||||
|
||||
@@ -3,11 +3,16 @@ import { ChildProcess } from 'child_process';
|
||||
import { Lambda as FunLambda } from '@zeit/fun';
|
||||
import {
|
||||
Builder as BuildConfig,
|
||||
BuildOptions,
|
||||
PrepareCacheOptions,
|
||||
ShouldServeOptions,
|
||||
StartDevServerOptions,
|
||||
StartDevServerResult,
|
||||
Env,
|
||||
FileBlob,
|
||||
FileFsRef,
|
||||
Lambda,
|
||||
PackageJson,
|
||||
Config,
|
||||
} from '@now/build-utils';
|
||||
import { NowConfig } from 'now-client';
|
||||
import { HandleValue, Route } from '@now/routing-utils';
|
||||
@@ -22,8 +27,21 @@ export interface DevServerOptions {
|
||||
frameworkSlug: string | null;
|
||||
}
|
||||
|
||||
export interface EnvConfig {
|
||||
[name: string]: string | undefined;
|
||||
export interface EnvConfigs {
|
||||
/**
|
||||
* environment variables from `.env.build` file (deprecated)
|
||||
*/
|
||||
buildEnv: Env;
|
||||
|
||||
/**
|
||||
* environment variables from `.env` file
|
||||
*/
|
||||
runEnv: Env;
|
||||
|
||||
/**
|
||||
* environment variables from `.env` and `.env.build`
|
||||
*/
|
||||
allEnv: Env;
|
||||
}
|
||||
|
||||
export interface BuildMatch extends BuildConfig {
|
||||
@@ -35,8 +53,6 @@ export interface BuildMatch extends BuildConfig {
|
||||
buildProcess?: ChildProcess;
|
||||
}
|
||||
|
||||
export type RouteConfig = Route;
|
||||
|
||||
export interface HttpHandler {
|
||||
(req: http.IncomingMessage, res: http.ServerResponse): void;
|
||||
}
|
||||
@@ -45,9 +61,9 @@ export interface BuilderInputs {
|
||||
[path: string]: FileFsRef;
|
||||
}
|
||||
|
||||
export type BuiltLambda = Lambda & {
|
||||
export interface BuiltLambda extends Lambda {
|
||||
fn?: FunLambda;
|
||||
};
|
||||
}
|
||||
|
||||
export type BuilderOutput = BuiltLambda | FileFsRef | FileBlob;
|
||||
|
||||
@@ -61,28 +77,6 @@ export interface CacheOutputs {
|
||||
[path: string]: CacheOutput;
|
||||
}
|
||||
|
||||
export interface BuilderParamsBase {
|
||||
files: BuilderInputs;
|
||||
entrypoint: string;
|
||||
config: Config;
|
||||
meta?: {
|
||||
isDev?: boolean;
|
||||
requestPath?: string | null;
|
||||
filesChanged?: string[];
|
||||
filesRemoved?: string[];
|
||||
env?: EnvConfig;
|
||||
buildEnv?: EnvConfig;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BuilderParams extends BuilderParamsBase {
|
||||
workPath: string;
|
||||
}
|
||||
|
||||
export interface PrepareCacheParams extends BuilderParams {
|
||||
cachePath: string;
|
||||
}
|
||||
|
||||
export interface BuilderConfigAttr {
|
||||
maxLambdaSize?: string | number;
|
||||
}
|
||||
@@ -91,47 +85,40 @@ export interface Builder {
|
||||
version?: 1 | 2 | 3 | 4;
|
||||
config?: BuilderConfigAttr;
|
||||
build(
|
||||
params: BuilderParams
|
||||
opts: BuildOptions
|
||||
):
|
||||
| BuilderOutputs
|
||||
| BuildResult
|
||||
| Promise<BuilderOutputs>
|
||||
| Promise<BuildResult>;
|
||||
shouldServe?(params: ShouldServeParams): boolean | Promise<boolean>;
|
||||
prepareCache?(
|
||||
params: PrepareCacheParams
|
||||
opts: PrepareCacheOptions
|
||||
): CacheOutputs | Promise<CacheOutputs>;
|
||||
shouldServe?(params: ShouldServeOptions): boolean | Promise<boolean>;
|
||||
startDevServer?(opts: StartDevServerOptions): Promise<StartDevServerResult>;
|
||||
}
|
||||
|
||||
export interface BuildResult {
|
||||
output: BuilderOutputs;
|
||||
routes: RouteConfig[];
|
||||
routes: Route[];
|
||||
watch: string[];
|
||||
distPath?: string;
|
||||
}
|
||||
|
||||
export interface BuildResultV3 {
|
||||
output: Lambda;
|
||||
routes: RouteConfig[];
|
||||
routes: Route[];
|
||||
watch: string[];
|
||||
distPath?: string;
|
||||
}
|
||||
|
||||
export interface BuildResultV4 {
|
||||
output: { [filePath: string]: Lambda };
|
||||
routes: RouteConfig[];
|
||||
routes: Route[];
|
||||
watch: string[];
|
||||
distPath?: string;
|
||||
}
|
||||
|
||||
export interface ShouldServeParams {
|
||||
files: BuilderInputs;
|
||||
entrypoint: string;
|
||||
config?: Config;
|
||||
requestPath: string;
|
||||
workPath: string;
|
||||
}
|
||||
|
||||
export interface BuilderWithPackage {
|
||||
runInProcess?: boolean;
|
||||
builder: Readonly<Builder>;
|
||||
@@ -147,6 +134,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>
|
||||
@@ -154,7 +143,7 @@ export interface RouteResult {
|
||||
// "uri_args": <object (key=value) list of new uri args to be passed along to dest >
|
||||
uri_args?: { [key: string]: any };
|
||||
// "matched_route": <object of the route spec that matched>
|
||||
matched_route?: RouteConfig;
|
||||
matched_route?: Route;
|
||||
// "matched_route_idx": <integer of the index of the route matched>
|
||||
matched_route_idx?: number;
|
||||
// "userDest": <boolean in case the destination was user defined>
|
||||
|
||||
@@ -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(
|
||||
' | '
|
||||
)}`;
|
||||
}
|
||||
@@ -1,7 +1,18 @@
|
||||
import Client from './client';
|
||||
import getTeams from './get-teams';
|
||||
import { Team } from '../types';
|
||||
|
||||
export default async function getTeamById(client: Client, teamId: string) {
|
||||
const teams = await getTeams(client);
|
||||
return teams.find(team => team.id === teamId) || null;
|
||||
const teamCache = new Map<string, Team>();
|
||||
|
||||
export default async function getTeamById(
|
||||
client: Client,
|
||||
teamId: string
|
||||
): Promise<Team> {
|
||||
let team = teamCache.get(teamId);
|
||||
|
||||
if (!team) {
|
||||
team = await client.fetch<Team>(`/teams/${teamId}`);
|
||||
teamCache.set(teamId, team);
|
||||
}
|
||||
|
||||
return team;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -340,9 +340,15 @@ export default class Now extends EventEmitter {
|
||||
return new Error(error.message);
|
||||
}
|
||||
|
||||
async listSecrets() {
|
||||
const { secrets } = await this.retry(async bail => {
|
||||
const res = await this._fetch('/now/secrets');
|
||||
async listSecrets(next) {
|
||||
const payload = await this.retry(async bail => {
|
||||
let secretsUrl = '/v3/now/secrets?limit=20';
|
||||
|
||||
if (next) {
|
||||
secretsUrl += `&until=${next}`;
|
||||
}
|
||||
|
||||
const res = await this._fetch(secretsUrl);
|
||||
|
||||
if (res.status === 200) {
|
||||
// What we want
|
||||
@@ -356,7 +362,7 @@ export default class Now extends EventEmitter {
|
||||
throw await responseError(res, 'Failed to list secrets');
|
||||
});
|
||||
|
||||
return secrets;
|
||||
return payload;
|
||||
}
|
||||
|
||||
async list(app, { version = 4, meta = {}, nextTimestamp } = {}) {
|
||||
@@ -388,7 +394,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 +411,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,20 +0,0 @@
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export function once<T>(emitter: EventEmitter, name: string): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
function cleanup() {
|
||||
emitter.removeListener(name, onEvent);
|
||||
emitter.removeListener('error', onError);
|
||||
}
|
||||
function onEvent(arg: T) {
|
||||
cleanup();
|
||||
resolve(arg);
|
||||
}
|
||||
function onError(err: Error) {
|
||||
cleanup();
|
||||
reject(err);
|
||||
}
|
||||
emitter.on(name, onEvent);
|
||||
emitter.on('error', onError);
|
||||
});
|
||||
}
|
||||
@@ -123,7 +123,11 @@ export async function getLinkedOrg(
|
||||
|
||||
return { status: 'linked', org };
|
||||
} catch (error) {
|
||||
if (error.code === 'NOT_AUTHORIZED' || error.code === 'TEAM_DELETED') {
|
||||
if (
|
||||
error.status === 403 ||
|
||||
error.code === 'NOT_AUTHORIZED' ||
|
||||
error.code === 'TEAM_DELETED'
|
||||
) {
|
||||
output.error(error.message);
|
||||
return { status: 'error', exitCode: 1 };
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -1,8 +1,43 @@
|
||||
import Now from '.';
|
||||
|
||||
export default class Secrets extends Now {
|
||||
ls() {
|
||||
return this.listSecrets();
|
||||
ls(next) {
|
||||
return this.listSecrets(next);
|
||||
}
|
||||
|
||||
getSecretByNameOrId(nameOrId) {
|
||||
return this.retry(async (bail, attempt) => {
|
||||
if (this._debug) {
|
||||
console.time(`> [debug] #${attempt} GET /secrets/${nameOrId}`);
|
||||
}
|
||||
const res = await this._fetch(`/now/secrets/${nameOrId}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (this._debug) {
|
||||
console.timeEnd(`> [debug] #${attempt} GET /secrets/${nameOrId}`);
|
||||
}
|
||||
|
||||
if (res.status === 403) {
|
||||
return bail(new Error('Unauthorized'));
|
||||
}
|
||||
|
||||
if (res.status === 404) {
|
||||
return bail(new Error('Not Found'));
|
||||
}
|
||||
|
||||
if (res.status === 400) {
|
||||
return bail(new Error('Bad Request'));
|
||||
}
|
||||
|
||||
const body = await res.json();
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error(body.error.message);
|
||||
}
|
||||
|
||||
return body;
|
||||
});
|
||||
}
|
||||
|
||||
rm(nameOrId) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
4
packages/now-cli/src/util/tree-kill.ts
Normal file
4
packages/now-cli/src/util/tree-kill.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import _treeKill from 'tree-kill';
|
||||
import { promisify } from 'util';
|
||||
|
||||
export const treeKill = promisify(_treeKill);
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
export default (_req, res) => {
|
||||
module.exports = (_req, res) => {
|
||||
res.end('current date: ' + new Date().toISOString());
|
||||
};
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default (_req, res) => {
|
||||
module.exports = (_req, res) => {
|
||||
res.end('random number: ' + Math.random());
|
||||
};
|
||||
|
||||
@@ -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: [
|
||||
|
||||
14
packages/now-cli/test/integration-v1.js
vendored
14
packages/now-cli/test/integration-v1.js
vendored
@@ -2299,19 +2299,15 @@ test('now certs ls', async t => {
|
||||
t.regex(output.stderr, /certificates? found under/gm, formatOutput(output));
|
||||
});
|
||||
|
||||
test('now certs ls --after=cert_test', async t => {
|
||||
const output = await execute(['certs', 'ls', '--after=cert_test']);
|
||||
test('now certs ls --next=123456', async t => {
|
||||
const output = await execute(['certs', 'ls', '--next=123456']);
|
||||
|
||||
console.log(output.stderr);
|
||||
console.log(output.stdout);
|
||||
console.log(output.exitCode);
|
||||
|
||||
t.is(output.exitCode, 1, formatOutput(output));
|
||||
t.regex(
|
||||
output.stderr,
|
||||
/The cert cert_test can't be found\./gm,
|
||||
formatOutput(output)
|
||||
);
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
t.regex(output.stderr, /No certificates found under/gm, formatOutput(output));
|
||||
});
|
||||
|
||||
test('now hasOwnProperty not a valid subcommand', async t => {
|
||||
@@ -2377,7 +2373,7 @@ test('now secret ls', async t => {
|
||||
console.log(output.exitCode);
|
||||
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
t.regex(output.stdout, /secrets? found under/gm, formatOutput(output));
|
||||
t.regex(output.stdout, /Secrets found under/gm, formatOutput(output));
|
||||
t.regex(output.stdout, new RegExp(), formatOutput(output));
|
||||
});
|
||||
|
||||
|
||||
286
packages/now-cli/test/integration.js
vendored
286
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([
|
||||
@@ -1745,19 +2015,15 @@ test('now certs ls', async t => {
|
||||
t.regex(output.stderr, /certificates? found under/gm, formatOutput(output));
|
||||
});
|
||||
|
||||
test('now certs ls --after=cert_test', async t => {
|
||||
const output = await execute(['certs', 'ls', '--after=cert_test']);
|
||||
test('now certs ls --next=123456', async t => {
|
||||
const output = await execute(['certs', 'ls', '--next=123456']);
|
||||
|
||||
console.log(output.stderr);
|
||||
console.log(output.stdout);
|
||||
console.log(output.exitCode);
|
||||
|
||||
t.is(output.exitCode, 1, formatOutput(output));
|
||||
t.regex(
|
||||
output.stderr,
|
||||
/The cert cert_test can't be found\./gm,
|
||||
formatOutput(output)
|
||||
);
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
t.regex(output.stderr, /No certificates found under/gm, formatOutput(output));
|
||||
});
|
||||
|
||||
test('now hasOwnProperty not a valid subcommand', async t => {
|
||||
@@ -1832,7 +2098,7 @@ test('now secret ls', async t => {
|
||||
console.log(output.exitCode);
|
||||
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
t.regex(output.stdout, /secrets? found under/gm, formatOutput(output));
|
||||
t.regex(output.stdout, /Secrets found under/gm, formatOutput(output));
|
||||
t.regex(output.stdout, new RegExp(), formatOutput(output));
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user