mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-24 11:49:13 +00:00
Compare commits
50 Commits
@now/next@
...
@now/node@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
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
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "9.2.2",
|
||||
"react": "16.13.0",
|
||||
"react-dom": "16.13.0"
|
||||
"next": "^9.3.3",
|
||||
"react": "^16.13.0",
|
||||
"react-dom": "^16.13.0"
|
||||
}
|
||||
}
|
||||
|
||||
4
now.json
4
now.json
@@ -9,10 +9,6 @@
|
||||
"destination": "/api/frameworks"
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
"GITHUB_ACCESS_TOKEN": "@now-api-examples-github-token",
|
||||
"SENTRY_DSN": "@sentry-product-dsn"
|
||||
},
|
||||
"github": {
|
||||
"silent": true,
|
||||
"autoJobCancelation": true
|
||||
|
||||
@@ -59,9 +59,6 @@
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"signal-exit": "TooTallNate/signal-exit#update/sighub-to-sigint-on-windows"
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true
|
||||
|
||||
@@ -309,6 +309,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Ionic Angular",
|
||||
"slug": "ionic-angular",
|
||||
"demo": "https://ionic-angular.now-examples.now.sh",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic.svg",
|
||||
"tagline": "Ionic Angular allows you to build mobile PWAs with Angular and the Ionic Framework.",
|
||||
"description": "An Ionic Angular site, created with the Ionic CLI.",
|
||||
"website": "https://ionicframework.com",
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@ionic\\/angular\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"placeholder": "`npm run build` or `ng build`"
|
||||
},
|
||||
"devCommand": {
|
||||
"value": "ng start"
|
||||
},
|
||||
"outputDirectory": {
|
||||
"value": "www"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Angular",
|
||||
"slug": "angular",
|
||||
@@ -397,7 +425,7 @@
|
||||
"name": "Ionic React",
|
||||
"slug": "ionic-react",
|
||||
"demo": "https://ionic-react.now-examples.now.sh",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic-react.svg",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic.svg",
|
||||
"tagline": "Ionic React allows you to build mobile PWAs with React and the Ionic Framework.",
|
||||
"description": "An Ionic React site, created with the Ionic CLI.",
|
||||
"website": "https://ionicframework.com",
|
||||
@@ -411,13 +439,13 @@
|
||||
},
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"placeholder": "npm run build"
|
||||
"placeholder": "`npm run build` or `react-scripts build`"
|
||||
},
|
||||
"devCommand": {
|
||||
"value": "stencil build --dev --watch --serve --port $PORT"
|
||||
"value": "react-scripts start"
|
||||
},
|
||||
"outputDirectory": {
|
||||
"value": "public"
|
||||
"value": "build"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
Before Width: | Height: | Size: 1003 B After Width: | Height: | Size: 1003 B |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/frameworks",
|
||||
"version": "0.0.13-canary.0",
|
||||
"version": "0.0.13",
|
||||
"main": "frameworks.json",
|
||||
"license": "UNLICENSED"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/build-utils",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
@@ -190,7 +190,12 @@ export async function detectBuilders(
|
||||
hasNextApiFiles = true;
|
||||
}
|
||||
|
||||
if (!fallbackEntrypoint && buildCommand && !fileName.includes('/')) {
|
||||
if (
|
||||
!fallbackEntrypoint &&
|
||||
buildCommand &&
|
||||
!fileName.includes('/') &&
|
||||
fileName !== 'now.json'
|
||||
) {
|
||||
fallbackEntrypoint = fileName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,18 @@ import spawn from 'cross-spawn';
|
||||
import { SpawnOptions } from 'child_process';
|
||||
import { deprecate } from 'util';
|
||||
import { cpus } from 'os';
|
||||
import { NowBuildError } from '../errors';
|
||||
import { Meta, PackageJson, NodeVersion, Config } from '../types';
|
||||
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
|
||||
|
||||
interface SpawnOptionsExtended extends SpawnOptions {
|
||||
prettyCommand?: string;
|
||||
}
|
||||
|
||||
export function spawnAsync(
|
||||
command: string,
|
||||
args: string[],
|
||||
opts: SpawnOptions = {}
|
||||
opts: SpawnOptionsExtended = {}
|
||||
) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const stderrLogs: Buffer[] = [];
|
||||
@@ -29,12 +34,18 @@ export function spawnAsync(
|
||||
return resolve();
|
||||
}
|
||||
|
||||
const errorLogs = stderrLogs.map(line => line.toString()).join('');
|
||||
if (opts.stdio !== 'inherit') {
|
||||
reject(new Error(`Exited with ${code || signal}\n${errorLogs}`));
|
||||
} else {
|
||||
reject(new Error(`Exited with ${code || signal}`));
|
||||
}
|
||||
const cmd = opts.prettyCommand
|
||||
? `Command "${opts.prettyCommand}"`
|
||||
: 'Command';
|
||||
reject(
|
||||
new NowBuildError({
|
||||
code: `NOW_BUILD_UTILS_SPAWN_${code || signal}`,
|
||||
message:
|
||||
opts.stdio === 'inherit'
|
||||
? `${cmd} exited with ${code || signal}`
|
||||
: stderrLogs.map(line => line.toString()).join(''),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -42,7 +53,7 @@ export function spawnAsync(
|
||||
export function execAsync(
|
||||
command: string,
|
||||
args: string[],
|
||||
opts: SpawnOptions = {}
|
||||
opts: SpawnOptionsExtended = {}
|
||||
) {
|
||||
return new Promise<{ stdout: string; stderr: string; code: number }>(
|
||||
(resolve, reject) => {
|
||||
@@ -64,10 +75,15 @@ export function execAsync(
|
||||
child.on('error', reject);
|
||||
child.on('close', (code, signal) => {
|
||||
if (code !== 0) {
|
||||
const cmd = opts.prettyCommand
|
||||
? `Command "${opts.prettyCommand}"`
|
||||
: 'Command';
|
||||
|
||||
return reject(
|
||||
new Error(
|
||||
`Program "${command}" exited with non-zero exit code ${code} ${signal}.`
|
||||
)
|
||||
new NowBuildError({
|
||||
code: `NOW_BUILD_UTILS_EXEC_${code || signal}`,
|
||||
message: `${cmd} exited with ${code || signal}`,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -82,18 +98,20 @@ export function execAsync(
|
||||
}
|
||||
|
||||
export function spawnCommand(command: string, options: SpawnOptions = {}) {
|
||||
const opts = { ...options, prettyCommand: command };
|
||||
if (process.platform === 'win32') {
|
||||
return spawn('cmd.exe', ['/C', command], options);
|
||||
return spawn('cmd.exe', ['/C', command], opts);
|
||||
}
|
||||
|
||||
return spawn('sh', ['-c', command], options);
|
||||
return spawn('sh', ['-c', command], opts);
|
||||
}
|
||||
|
||||
export async function execCommand(command: string, options: SpawnOptions = {}) {
|
||||
const opts = { ...options, prettyCommand: command };
|
||||
if (process.platform === 'win32') {
|
||||
await spawnAsync('cmd.exe', ['/C', command], options);
|
||||
await spawnAsync('cmd.exe', ['/C', command], opts);
|
||||
} else {
|
||||
await spawnAsync('sh', ['-c', command], options);
|
||||
await spawnAsync('sh', ['-c', command], opts);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -120,9 +138,11 @@ export async function runShellScript(
|
||||
assert(path.isAbsolute(fsPath));
|
||||
const destPath = path.dirname(fsPath);
|
||||
await chmodPlusX(fsPath);
|
||||
await spawnAsync(`./${path.basename(fsPath)}`, args, {
|
||||
cwd: destPath,
|
||||
const command = `./${path.basename(fsPath)}`;
|
||||
await spawnAsync(command, args, {
|
||||
...spawnOpts,
|
||||
cwd: destPath,
|
||||
prettyCommand: command,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -249,7 +269,7 @@ export async function runNpmInstall(
|
||||
debug(`Installing to ${destPath}`);
|
||||
|
||||
const { hasPackageLockJson } = await scanParentDirs(destPath);
|
||||
const opts: SpawnOptions = { cwd: destPath, ...spawnOpts };
|
||||
const opts: SpawnOptionsExtended = { cwd: destPath, ...spawnOpts };
|
||||
const env = opts.env ? { ...opts.env } : { ...process.env };
|
||||
delete env.NODE_ENV;
|
||||
opts.env = env;
|
||||
@@ -258,11 +278,13 @@ export async function runNpmInstall(
|
||||
let commandArgs: string[];
|
||||
|
||||
if (hasPackageLockJson) {
|
||||
opts.prettyCommand = 'npm install';
|
||||
command = 'npm';
|
||||
commandArgs = args
|
||||
.filter(a => a !== '--prefer-offline')
|
||||
.concat(['install', '--no-audit', '--unsafe-perm']);
|
||||
} else {
|
||||
opts.prettyCommand = 'yarn install';
|
||||
command = 'yarn';
|
||||
commandArgs = args.concat(['install', '--ignore-engines']);
|
||||
}
|
||||
@@ -285,7 +307,7 @@ export async function runBundleInstall(
|
||||
}
|
||||
|
||||
assert(path.isAbsolute(destPath));
|
||||
const opts = { cwd: destPath, ...spawnOpts };
|
||||
const opts = { ...spawnOpts, cwd: destPath, prettyCommand: 'bundle install' };
|
||||
|
||||
await spawnAsync(
|
||||
'bundle',
|
||||
@@ -313,7 +335,7 @@ export async function runPipInstall(
|
||||
}
|
||||
|
||||
assert(path.isAbsolute(destPath));
|
||||
const opts = { cwd: destPath, ...spawnOpts };
|
||||
const opts = { ...spawnOpts, cwd: destPath, prettyCommand: 'pip3 install' };
|
||||
|
||||
await spawnAsync(
|
||||
'pip3',
|
||||
@@ -340,18 +362,22 @@ export async function runPackageJsonScript(
|
||||
);
|
||||
if (!hasScript) return false;
|
||||
|
||||
const opts = { cwd: destPath, ...spawnOpts };
|
||||
|
||||
if (hasPackageLockJson) {
|
||||
console.log(`Running "npm run ${scriptName}"`);
|
||||
await spawnAsync('npm', ['run', scriptName], opts);
|
||||
const prettyCommand = `npm run ${scriptName}`;
|
||||
console.log(`Running "${prettyCommand}"`);
|
||||
await spawnAsync('npm', ['run', scriptName], {
|
||||
...spawnOpts,
|
||||
cwd: destPath,
|
||||
prettyCommand,
|
||||
});
|
||||
} else {
|
||||
console.log(`Running "yarn run ${scriptName}"`);
|
||||
await spawnAsync(
|
||||
'yarn',
|
||||
['--ignore-engines', '--cwd', destPath, 'run', scriptName],
|
||||
opts
|
||||
);
|
||||
const prettyCommand = `yarn run ${scriptName}`;
|
||||
console.log(`Running "${prettyCommand}"`);
|
||||
await spawnAsync('yarn', ['--ignore-engines', 'run', scriptName], {
|
||||
...spawnOpts,
|
||||
cwd: destPath,
|
||||
prettyCommand,
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -7,6 +7,19 @@ import {
|
||||
} from '../';
|
||||
|
||||
describe('Test `detectBuilders`', () => {
|
||||
it('should never select now.json src', async () => {
|
||||
const files = ['docs/index.md', 'mkdocs.yml', 'now.json'];
|
||||
const { builders, errors } = await detectBuilders(files, null, {
|
||||
projectSettings: {
|
||||
buildCommand: 'mkdocs build',
|
||||
outputDirectory: 'site',
|
||||
},
|
||||
});
|
||||
expect(errors).toBe(null);
|
||||
expect(builders).toBeDefined();
|
||||
expect(builders![0].src).not.toBe('now.json');
|
||||
});
|
||||
|
||||
it('package.json + no build', async () => {
|
||||
const pkg = { dependencies: { next: '9.0.0' } };
|
||||
const files = ['package.json', 'pages/index.js', 'public/index.html'];
|
||||
@@ -817,6 +830,21 @@ describe('Test `detectBuilders`', () => {
|
||||
|
||||
describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
const featHandleMiss = true;
|
||||
|
||||
it('should never select now.json src', async () => {
|
||||
const files = ['docs/index.md', 'mkdocs.yml', 'now.json'];
|
||||
const { builders, errors } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
projectSettings: {
|
||||
buildCommand: 'mkdocs build',
|
||||
outputDirectory: 'site',
|
||||
},
|
||||
});
|
||||
expect(errors).toBe(null);
|
||||
expect(builders).toBeDefined();
|
||||
expect(builders![0].src).not.toBe('now.json');
|
||||
});
|
||||
|
||||
it('package.json + no build', async () => {
|
||||
const pkg = { dependencies: { next: '9.0.0' } };
|
||||
const files = ['package.json', 'pages/index.js', 'public/index.html'];
|
||||
|
||||
@@ -21,7 +21,6 @@ describe('Test `walkParentDirs`', () => {
|
||||
await walkParentDirs({ base, start, filename });
|
||||
fail('Expected error');
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
deepEqual(
|
||||
(error as Error).message,
|
||||
'Expected "base" to be absolute path'
|
||||
@@ -36,7 +35,6 @@ describe('Test `walkParentDirs`', () => {
|
||||
await walkParentDirs({ base, start, filename });
|
||||
fail('Expected error');
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
deepEqual(
|
||||
(error as Error).message,
|
||||
'Expected "start" to be absolute path'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now",
|
||||
"version": "17.1.2-canary.4",
|
||||
"version": "18.0.0",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Now",
|
||||
@@ -13,7 +13,7 @@
|
||||
"scripts": {
|
||||
"preinstall": "node ./scripts/preinstall.js",
|
||||
"test-unit": "nyc ava test/unit.js test/dev-builder.unit.js test/dev-router.unit.js test/dev-server.unit.js --serial --fail-fast --verbose",
|
||||
"test-integration-cli": "ava test/integration.js --serial --fail-fast",
|
||||
"test-integration-cli": "ava test/integration.js --serial --fail-fast --verbose",
|
||||
"test-integration-v1": "ava test/integration-v1.js --serial --fail-fast",
|
||||
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
|
||||
"prepublishOnly": "yarn build",
|
||||
@@ -63,7 +63,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/node": "5.5.0",
|
||||
"@sindresorhus/slugify": "0.10.0",
|
||||
"@sindresorhus/slugify": "0.11.0",
|
||||
"@types/ansi-escapes": "3.0.0",
|
||||
"@types/ansi-regex": "4.0.0",
|
||||
"@types/async-retry": "1.2.1",
|
||||
@@ -141,8 +141,9 @@
|
||||
"micro": "9.1.2",
|
||||
"mime-types": "2.1.24",
|
||||
"minimatch": "3.0.4",
|
||||
"mri": "1.1.0",
|
||||
"mri": "1.1.5",
|
||||
"ms": "2.1.2",
|
||||
"nanoid": "3.0.2",
|
||||
"node-fetch": "2.6.0",
|
||||
"npm-package-arg": "6.1.0",
|
||||
"nyc": "13.2.0",
|
||||
|
||||
@@ -8,6 +8,8 @@ import getAliases from '../../util/alias/get-aliases';
|
||||
import getScope from '../../util/get-scope.ts';
|
||||
import stamp from '../../util/output/stamp.ts';
|
||||
import strlen from '../../util/strlen.ts';
|
||||
import getCommandFlags from '../../util/get-command-flags';
|
||||
import cmd from '../../util/output/cmd.ts';
|
||||
|
||||
export default async function ls(ctx, opts, args, output) {
|
||||
const {
|
||||
@@ -101,9 +103,12 @@ export default async function ls(ctx, opts, args, output) {
|
||||
console.log(printAliasTable(aliases));
|
||||
}
|
||||
|
||||
if (pagination && aliases.length === 20) {
|
||||
if (pagination && pagination.count === 20) {
|
||||
const flags = getCommandFlags(opts, ['_', '--next']);
|
||||
output.log(
|
||||
`To display the next page use the flag --next ${pagination.next}`
|
||||
`To display the next page run ${cmd(
|
||||
`now alias ls${flags} --next ${pagination.next}`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export const latestHelp = () => `
|
||||
'(default)'
|
||||
)}
|
||||
dev Start a local development server
|
||||
env Manages the Environment Variables for your current Project
|
||||
init [example] Initialize an example project
|
||||
ls | list [app] Lists deployments
|
||||
inspect [id] Displays information related to a deployment
|
||||
@@ -28,7 +29,7 @@ export const latestHelp = () => `
|
||||
domains [name] Manages your domain names
|
||||
dns [name] Manages your DNS records
|
||||
certs [cmd] Manages your SSL certificates
|
||||
secrets [name] Manages your secret environment variables
|
||||
secrets [name] Manages your global Secrets, for use in Environment Variables
|
||||
logs [url] Displays the logs for a deployment
|
||||
teams Manages your teams
|
||||
whoami Shows the username of the currently logged in user
|
||||
@@ -46,7 +47,7 @@ export const latestHelp = () => `
|
||||
)} Path to the global ${'`.now`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
-f, --force Force a new deployment even if nothing has changed
|
||||
--force-with-cache Force a new deployment even if nothing has changed but retain build cache
|
||||
--with-cache Retain build cache when using "--force"
|
||||
-t ${chalk.underline('TOKEN')}, --token=${chalk.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
@@ -84,7 +85,7 @@ export const latestHelp = () => `
|
||||
|
||||
${chalk.cyan('$ now /usr/src/project')}
|
||||
|
||||
${chalk.gray('–')} Deploy with environment variables
|
||||
${chalk.gray('–')} Deploy with Environment Variables
|
||||
|
||||
${chalk.cyan('$ now -e NODE_ENV=production -e SECRET=@mysql-secret')}
|
||||
|
||||
@@ -98,7 +99,7 @@ export const latestHelp = () => `
|
||||
|
||||
export const latestArgs = {
|
||||
'--force': Boolean,
|
||||
'--force-with-cache': Boolean,
|
||||
'--with-cache': Boolean,
|
||||
'--public': Boolean,
|
||||
'--no-clipboard': Boolean,
|
||||
'--env': [String],
|
||||
|
||||
@@ -506,7 +506,7 @@ export default async function main(
|
||||
env: deploymentEnv,
|
||||
build: { env: deploymentBuildEnv },
|
||||
forceNew: argv['--force'],
|
||||
forceNewWithCache: argv['--force-with-cache'],
|
||||
withCache: argv['--with-cache'],
|
||||
quiet,
|
||||
wantsPublic: argv['--public'] || localConfig.public,
|
||||
isFile,
|
||||
@@ -666,7 +666,7 @@ export default async function main(
|
||||
if (err instanceof BuildError) {
|
||||
output.error('Build failed');
|
||||
output.error(
|
||||
`Check your logs at ${now.url}/_logs or run ${code(
|
||||
`Check your logs at https://${now.url}/_logs or run ${code(
|
||||
`now logs ${now.url}`,
|
||||
{
|
||||
// Backticks are interpreted as part of the URL, causing CMD+Click
|
||||
|
||||
@@ -93,7 +93,7 @@ let paths: string[];
|
||||
|
||||
// Options
|
||||
let forceNew: boolean;
|
||||
let forceNewWithCache: boolean;
|
||||
let withCache: boolean;
|
||||
let deploymentName: string;
|
||||
let sessionAffinity: string;
|
||||
let log: any;
|
||||
@@ -239,7 +239,7 @@ export default async function main(
|
||||
|
||||
// Options
|
||||
forceNew = argv.force;
|
||||
forceNewWithCache = argv['force-with-cache'];
|
||||
withCache = argv['with-cache'];
|
||||
deploymentName = argv.name;
|
||||
sessionAffinity = argv['session-affinity'];
|
||||
debugEnabled = argv.debug;
|
||||
@@ -743,7 +743,7 @@ async function sync({
|
||||
meta: metadata,
|
||||
followSymlinks,
|
||||
forceNew,
|
||||
forceNewWithCache,
|
||||
withCache,
|
||||
forwardNpm,
|
||||
quiet,
|
||||
scale,
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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}`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,7 +190,10 @@ export default async function main(ctx) {
|
||||
}
|
||||
|
||||
aliases = await Promise.all(
|
||||
deployments.map(depl => getAliases(client, depl.uid))
|
||||
deployments.map(async depl => {
|
||||
const { aliases } = await getAliases(client, depl.uid);
|
||||
return aliases;
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
cancelWait();
|
||||
|
||||
@@ -110,7 +110,7 @@ const main = async ctx => {
|
||||
}
|
||||
|
||||
try {
|
||||
await run({ output, token, contextName, currentTeam });
|
||||
await run({ output, token, contextName, currentTeam, ctx });
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
exit(1);
|
||||
@@ -126,7 +126,7 @@ export default async ctx => {
|
||||
}
|
||||
};
|
||||
|
||||
async function run({ output, token, contextName, currentTeam }) {
|
||||
async function run({ output, token, contextName, currentTeam, ctx }) {
|
||||
const secrets = new NowSecrets({ apiUrl, token, debug, currentTeam });
|
||||
const args = argv._.slice(1);
|
||||
const start = Date.now();
|
||||
@@ -260,7 +260,28 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
const [name, value] = args;
|
||||
const [name, parsedValue] = args;
|
||||
const [originalName, originalValue] = ctx.argv.slice(-2);
|
||||
|
||||
let value = parsedValue;
|
||||
if (
|
||||
name === originalName &&
|
||||
typeof parsedValue === 'boolean' &&
|
||||
parsedValue !== originalValue
|
||||
) {
|
||||
// Corner case where `mri` transforms the secret value into a boolean because
|
||||
// it starts with a `-` so it thinks its a flag, so we use the original value instead.
|
||||
value = originalValue;
|
||||
}
|
||||
|
||||
if (typeof value === 'boolean') {
|
||||
const example = chalk.cyan(`$ now secret add -- "${name}"`);
|
||||
console.log(
|
||||
`If your secret starts with '-', make sure to terminate command options with double dash and wrap it in quotes. Example: \n ${example} `
|
||||
);
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
await secrets.add(name, value);
|
||||
const elapsed = ms(new Date() - start);
|
||||
|
||||
|
||||
@@ -216,6 +216,33 @@ export type DNSRecordData =
|
||||
| SRVRecordData
|
||||
| MXRecordData;
|
||||
|
||||
export interface Secret {
|
||||
uid: string;
|
||||
name: string;
|
||||
value: string;
|
||||
teamId?: string;
|
||||
userId?: string;
|
||||
projectId?: string;
|
||||
created: string;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
export enum ProjectEnvTarget {
|
||||
Production = 'production',
|
||||
Preview = 'preview',
|
||||
Development = 'development',
|
||||
}
|
||||
|
||||
export interface ProjectEnvVariable {
|
||||
key: string;
|
||||
value: string;
|
||||
configurationId?: string | null;
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
target?: ProjectEnvTarget;
|
||||
system?: boolean;
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
@@ -21,7 +21,7 @@ export default class Client extends EventEmitter {
|
||||
_apiUrl: string;
|
||||
_debug: boolean;
|
||||
_forceNew: boolean;
|
||||
_forceNewWithCache: boolean;
|
||||
_withCache: boolean;
|
||||
_output: Output;
|
||||
_token: string;
|
||||
currentTeam?: string;
|
||||
@@ -31,21 +31,21 @@ export default class Client extends EventEmitter {
|
||||
token,
|
||||
currentTeam,
|
||||
forceNew = false,
|
||||
forceNewWithCache = false,
|
||||
withCache = false,
|
||||
debug = false,
|
||||
}: {
|
||||
apiUrl: string;
|
||||
token: string;
|
||||
currentTeam?: string;
|
||||
forceNew?: boolean;
|
||||
forceNewWithCache?: boolean;
|
||||
withCache?: boolean;
|
||||
debug?: boolean;
|
||||
}) {
|
||||
super();
|
||||
this._token = token;
|
||||
this._debug = debug;
|
||||
this._forceNew = forceNew;
|
||||
this._forceNewWithCache = forceNewWithCache;
|
||||
this._withCache = withCache;
|
||||
this._output = createOutput({ debug });
|
||||
this._apiUrl = apiUrl;
|
||||
this._onRetry = this._onRetry.bind(this);
|
||||
|
||||
@@ -66,7 +66,7 @@ export default async function processDeployment({
|
||||
quiet: boolean;
|
||||
nowConfig?: NowConfig;
|
||||
force?: boolean;
|
||||
forceNewWithCache?: boolean;
|
||||
withCache?: boolean;
|
||||
org: Org;
|
||||
projectName: string;
|
||||
isSettingUpProject: boolean;
|
||||
@@ -83,7 +83,7 @@ export default async function processDeployment({
|
||||
requestBody,
|
||||
deployStamp,
|
||||
force,
|
||||
forceNewWithCache,
|
||||
withCache,
|
||||
nowConfig,
|
||||
quiet,
|
||||
} = args;
|
||||
@@ -101,7 +101,7 @@ export default async function processDeployment({
|
||||
userAgent: ua,
|
||||
path: paths[0],
|
||||
force,
|
||||
forceNewWithCache,
|
||||
withCache,
|
||||
skipAutoDetectionConfirmation,
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ import {
|
||||
BuilderOutput,
|
||||
BuildResultV3,
|
||||
BuilderOutputs,
|
||||
BuilderParams,
|
||||
EnvConfigs,
|
||||
} from './types';
|
||||
import { normalizeRoutes } from '@now/routing-utils';
|
||||
import getUpdateCommand from '../get-update-command';
|
||||
@@ -45,7 +47,7 @@ const treeKill = promisify(_treeKill);
|
||||
|
||||
async function createBuildProcess(
|
||||
match: BuildMatch,
|
||||
buildEnv: EnvConfig,
|
||||
envConfigs: EnvConfigs,
|
||||
workPath: string,
|
||||
output: Output,
|
||||
yarnPath?: string
|
||||
@@ -64,7 +66,7 @@ async function createBuildProcess(
|
||||
const env: EnvConfig = {
|
||||
...process.env,
|
||||
PATH,
|
||||
...buildEnv,
|
||||
...envConfigs.allEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
};
|
||||
|
||||
@@ -109,7 +111,7 @@ export async function executeBuild(
|
||||
builderWithPkg: { runInProcess, builder, package: pkg },
|
||||
} = match;
|
||||
const { entrypoint } = match;
|
||||
const { env, debug, buildEnv, yarnPath, cwd: workPath } = devServer;
|
||||
const { debug, envConfigs, yarnPath, cwd: workPath } = devServer;
|
||||
|
||||
const startTime = Date.now();
|
||||
const showBuildTimestamp =
|
||||
@@ -131,14 +133,14 @@ export async function executeBuild(
|
||||
devServer.output.debug(`Creating build process for ${entrypoint}`);
|
||||
buildProcess = await createBuildProcess(
|
||||
match,
|
||||
buildEnv,
|
||||
envConfigs,
|
||||
workPath,
|
||||
devServer.output,
|
||||
yarnPath
|
||||
);
|
||||
}
|
||||
|
||||
const buildParams = {
|
||||
const buildParams: BuilderParams = {
|
||||
files,
|
||||
entrypoint,
|
||||
workPath,
|
||||
@@ -148,8 +150,10 @@ export async function executeBuild(
|
||||
requestPath,
|
||||
filesChanged,
|
||||
filesRemoved,
|
||||
env,
|
||||
buildEnv,
|
||||
// This env distiniction is only necessary to maintain
|
||||
// backwards compatibility with the `@now/next` builder.
|
||||
env: envConfigs.runEnv,
|
||||
buildEnv: envConfigs.buildEnv,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -361,7 +365,7 @@ export async function executeBuild(
|
||||
Variables: {
|
||||
...nowConfig.env,
|
||||
...asset.environment,
|
||||
...env,
|
||||
...envConfigs.runEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -54,16 +54,18 @@ export async function devRouter(
|
||||
missRoutes?: RouteConfig[],
|
||||
phase?: HandleValue | null
|
||||
): Promise<RouteResult> {
|
||||
let found: RouteResult | undefined;
|
||||
let result: RouteResult | undefined;
|
||||
let { query, pathname: reqPathname = '/' } = url.parse(reqUrl, true);
|
||||
const combinedHeaders: HttpHeadersConfig = { ...previousHeaders };
|
||||
let status: number | undefined;
|
||||
let isContinue = false;
|
||||
|
||||
// Try route match
|
||||
if (routes) {
|
||||
let idx = -1;
|
||||
for (const routeConfig of routes) {
|
||||
idx++;
|
||||
isContinue = false;
|
||||
|
||||
if (isHandler(routeConfig)) {
|
||||
// We don't expect any Handle, only Source routes
|
||||
@@ -110,6 +112,7 @@ export async function devRouter(
|
||||
status = routeConfig.status;
|
||||
}
|
||||
reqPathname = destPath;
|
||||
isContinue = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -149,9 +152,10 @@ export async function devRouter(
|
||||
|
||||
const isDestUrl = isURL(destPath);
|
||||
if (isDestUrl) {
|
||||
found = {
|
||||
result = {
|
||||
found: true,
|
||||
dest: destPath,
|
||||
continue: isContinue,
|
||||
userDest: false,
|
||||
isDestUrl,
|
||||
status: routeConfig.status || status,
|
||||
@@ -167,9 +171,10 @@ export async function devRouter(
|
||||
destPath = `/${destPath}`;
|
||||
}
|
||||
const { pathname, query } = url.parse(destPath, true);
|
||||
found = {
|
||||
result = {
|
||||
found: true,
|
||||
dest: pathname || '/',
|
||||
continue: isContinue,
|
||||
userDest: Boolean(routeConfig.dest),
|
||||
isDestUrl,
|
||||
status: routeConfig.status || status,
|
||||
@@ -185,10 +190,11 @@ export async function devRouter(
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
found = {
|
||||
if (!result) {
|
||||
result = {
|
||||
found: false,
|
||||
dest: reqPathname,
|
||||
continue: isContinue,
|
||||
status,
|
||||
isDestUrl: false,
|
||||
uri_args: query,
|
||||
@@ -197,5 +203,5 @@ export async function devRouter(
|
||||
};
|
||||
}
|
||||
|
||||
return found;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -85,6 +85,8 @@ import {
|
||||
ListenSpec,
|
||||
RouteConfig,
|
||||
RouteResult,
|
||||
HttpHeadersConfig,
|
||||
EnvConfigs,
|
||||
} from './types';
|
||||
|
||||
interface FSEvent {
|
||||
@@ -108,8 +110,7 @@ export default class DevServer {
|
||||
public cwd: string;
|
||||
public debug: boolean;
|
||||
public output: Output;
|
||||
public env: EnvConfig;
|
||||
public buildEnv: EnvConfig;
|
||||
public envConfigs: EnvConfigs;
|
||||
public frameworkSlug: string | null;
|
||||
public files: BuilderInputs;
|
||||
public yarnPath: string;
|
||||
@@ -141,8 +142,7 @@ export default class DevServer {
|
||||
this.cwd = cwd;
|
||||
this.debug = options.debug;
|
||||
this.output = options.output;
|
||||
this.env = {};
|
||||
this.buildEnv = {};
|
||||
this.envConfigs = { buildEnv: {}, runEnv: {}, allEnv: {} };
|
||||
this.files = {};
|
||||
this.address = '';
|
||||
this.devCommand = options.devCommand;
|
||||
@@ -739,13 +739,13 @@ export default class DevServer {
|
||||
// Retrieve the path of the native module
|
||||
const nowConfig = await this.getNowConfig(false);
|
||||
const nowConfigBuild = nowConfig.build || {};
|
||||
const [env, buildEnv] = await Promise.all([
|
||||
const [runEnv, buildEnv] = await Promise.all([
|
||||
this.getLocalEnv('.env', nowConfig.env),
|
||||
this.getLocalEnv('.env.build', nowConfigBuild.env),
|
||||
]);
|
||||
Object.assign(process.env, buildEnv);
|
||||
this.env = env;
|
||||
this.buildEnv = buildEnv;
|
||||
const allEnv = { ...buildEnv, ...runEnv };
|
||||
Object.assign(process.env, allEnv);
|
||||
this.envConfigs = { buildEnv, runEnv, allEnv };
|
||||
|
||||
const opts = { output: this.output, isBuilds: true };
|
||||
const files = await getFiles(this.cwd, nowConfig, opts);
|
||||
@@ -1264,19 +1264,25 @@ export default class DevServer {
|
||||
let routeResult: RouteResult | null = null;
|
||||
let match: BuildMatch | null = null;
|
||||
let statusCode: number | undefined;
|
||||
let prevUrl = req.url;
|
||||
let prevHeaders: HttpHeadersConfig = {};
|
||||
|
||||
for (const phase of phases) {
|
||||
statusCode = undefined;
|
||||
const phaseRoutes = handleMap.get(phase) || [];
|
||||
routeResult = await devRouter(
|
||||
req.url,
|
||||
prevUrl,
|
||||
req.method,
|
||||
phaseRoutes,
|
||||
this,
|
||||
undefined,
|
||||
prevHeaders,
|
||||
missRoutes,
|
||||
phase
|
||||
);
|
||||
prevUrl =
|
||||
routeResult.continue && routeResult.dest ? routeResult.dest : req.url;
|
||||
prevHeaders =
|
||||
routeResult.continue && routeResult.headers ? routeResult.headers : {};
|
||||
|
||||
if (routeResult.isDestUrl) {
|
||||
// Mix the `routes` result dest query params into the req path
|
||||
@@ -1682,8 +1688,7 @@ export default class DevServer {
|
||||
FORCE_COLOR: process.stdout.isTTY ? '1' : '0',
|
||||
...(this.frameworkSlug === 'create-react-app' ? { BROWSER: 'none' } : {}),
|
||||
...process.env,
|
||||
...this.buildEnv,
|
||||
...(this.frameworkSlug === 'nextjs' ? this.env : {}),
|
||||
...this.envConfigs.allEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
PORT: `${port}`,
|
||||
};
|
||||
|
||||
@@ -26,6 +26,23 @@ export interface EnvConfig {
|
||||
[name: string]: string | undefined;
|
||||
}
|
||||
|
||||
export interface EnvConfigs {
|
||||
/**
|
||||
* environment variables from `.env.build` file (deprecated)
|
||||
*/
|
||||
buildEnv: EnvConfig;
|
||||
|
||||
/**
|
||||
* environment variables from `.env` file
|
||||
*/
|
||||
runEnv: EnvConfig;
|
||||
|
||||
/**
|
||||
* environment variables from `.env` and `.env.build`
|
||||
*/
|
||||
allEnv: EnvConfig;
|
||||
}
|
||||
|
||||
export interface BuildMatch extends BuildConfig {
|
||||
entrypoint: string;
|
||||
builderWithPkg: BuilderWithPackage;
|
||||
@@ -147,6 +164,8 @@ export interface RouteResult {
|
||||
found: boolean;
|
||||
// "dest": <string of the dest, either file for lambda or full url for remote>
|
||||
dest: string;
|
||||
// `true` if last route in current phase matched but set `continue: true`
|
||||
continue: boolean;
|
||||
// "status": <integer in case exit code is intended to be changed>
|
||||
status?: number;
|
||||
// "headers": <object of the added response header values>
|
||||
|
||||
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',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
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(' ')}` : '';
|
||||
}
|
||||
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(
|
||||
' | '
|
||||
)}`;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ export default class Now extends EventEmitter {
|
||||
token,
|
||||
currentTeam,
|
||||
forceNew = false,
|
||||
forceNewWithCache = false,
|
||||
withCache = false,
|
||||
debug = false,
|
||||
}) {
|
||||
super();
|
||||
@@ -42,7 +42,7 @@ export default class Now extends EventEmitter {
|
||||
this._token = token;
|
||||
this._debug = debug;
|
||||
this._forceNew = forceNew;
|
||||
this._forceNewWithCache = forceNewWithCache;
|
||||
this._withCache = withCache;
|
||||
this._output = createOutput({ debug });
|
||||
this._apiUrl = apiUrl;
|
||||
this._onRetry = this._onRetry.bind(this);
|
||||
@@ -72,7 +72,7 @@ export default class Now extends EventEmitter {
|
||||
env,
|
||||
build,
|
||||
forceNew = false,
|
||||
forceNewWithCache = false,
|
||||
withCache = false,
|
||||
target = null,
|
||||
deployStamp,
|
||||
projectSettings,
|
||||
@@ -164,7 +164,7 @@ export default class Now extends EventEmitter {
|
||||
meta,
|
||||
public: wantsPublic || nowConfig.public,
|
||||
forceNew,
|
||||
forceNewWithCache,
|
||||
withCache,
|
||||
name,
|
||||
project,
|
||||
description,
|
||||
@@ -190,7 +190,7 @@ export default class Now extends EventEmitter {
|
||||
quiet,
|
||||
nowConfig,
|
||||
force: forceNew,
|
||||
forceNewWithCache: forceNewWithCache,
|
||||
withCache,
|
||||
org,
|
||||
projectName: name,
|
||||
isSettingUpProject,
|
||||
@@ -388,7 +388,12 @@ export default class Now extends EventEmitter {
|
||||
if (!app && !Object.keys(meta).length) {
|
||||
// Get the 20 latest projects and their latest deployment
|
||||
const query = new URLSearchParams({ limit: (20).toString() });
|
||||
const projects = await fetchRetry(`/v2/projects/?${query}`);
|
||||
if (nextTimestamp) {
|
||||
query.set('until', String(nextTimestamp));
|
||||
}
|
||||
const { projects, pagination } = await fetchRetry(
|
||||
`/v4/projects/?${query}`
|
||||
);
|
||||
|
||||
const deployments = await Promise.all(
|
||||
projects.map(async ({ id: projectId }) => {
|
||||
@@ -400,7 +405,7 @@ export default class Now extends EventEmitter {
|
||||
})
|
||||
);
|
||||
|
||||
return { deployments: deployments.filter(x => x) };
|
||||
return { deployments: deployments.filter(x => x), pagination };
|
||||
}
|
||||
|
||||
const query = new URLSearchParams();
|
||||
|
||||
13
packages/now-cli/src/util/input/read-standard-input.ts
Normal file
13
packages/now-cli/src/util/input/read-standard-input.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export default async function readStandardInput(): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
setTimeout(() => resolve(''), 500);
|
||||
|
||||
if (process.stdin.isTTY) {
|
||||
// found tty so we know there is nothing piped to stdin
|
||||
resolve('');
|
||||
} else {
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.once('data', resolve);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import chalk from 'chalk';
|
||||
import { Output } from './output';
|
||||
|
||||
async function promptBool(output: Output, message: string) {
|
||||
return new Promise(resolve => {
|
||||
async function promptBool(output: Output, message: string): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
output.print(`${chalk.gray('>')} ${message} ${chalk.gray('[y/N] ')}`);
|
||||
process.stdin
|
||||
.on('data', d => {
|
||||
|
||||
14
packages/now-cli/test/dev-router.unit.js
vendored
14
packages/now-cli/test/dev-router.unit.js
vendored
@@ -10,6 +10,7 @@ test('[dev-router] 301 redirection', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/redirect',
|
||||
continue: false,
|
||||
status: 301,
|
||||
headers: { location: 'https://zeit.co' },
|
||||
uri_args: {},
|
||||
@@ -28,6 +29,7 @@ test('[dev-router] captured groups', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/endpoints/user.js',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
@@ -46,6 +48,7 @@ test('[dev-router] named groups', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/user.js',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: { id: '123' },
|
||||
@@ -69,6 +72,7 @@ test('[dev-router] optional named groups', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/api/functions/hello/index.js',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: { name: '' },
|
||||
@@ -88,6 +92,7 @@ test('[dev-router] proxy_pass', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: 'https://zeit.co',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
@@ -109,6 +114,7 @@ test('[dev-router] methods', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/get',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
@@ -123,6 +129,7 @@ test('[dev-router] methods', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/post',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
@@ -141,6 +148,7 @@ test('[dev-router] match without prefix slash', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/endpoints/user.js',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
@@ -164,6 +172,7 @@ test('[dev-router] match with needed prefixed slash', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/some/dest',
|
||||
continue: false,
|
||||
userDest: true,
|
||||
isDestUrl: false,
|
||||
phase: undefined,
|
||||
@@ -197,6 +206,7 @@ test('[dev-router] `continue: true` with fallthrough', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: false,
|
||||
dest: '/_next/static/chunks/0.js',
|
||||
continue: true,
|
||||
isDestUrl: false,
|
||||
phase: undefined,
|
||||
status: undefined,
|
||||
@@ -230,6 +240,7 @@ test('[dev-router] `continue: true` with match', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/hi',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
userDest: true,
|
||||
isDestUrl: false,
|
||||
@@ -253,6 +264,7 @@ test('[dev-router] match with catch-all with prefix slash', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/www/',
|
||||
continue: false,
|
||||
userDest: true,
|
||||
isDestUrl: false,
|
||||
phase: undefined,
|
||||
@@ -271,6 +283,7 @@ test('[dev-router] match with catch-all with no prefix slash', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: '/www/',
|
||||
continue: false,
|
||||
userDest: true,
|
||||
isDestUrl: false,
|
||||
phase: undefined,
|
||||
@@ -295,6 +308,7 @@ test('[dev-router] `continue: true` with `dest`', async t => {
|
||||
t.deepEqual(result, {
|
||||
found: true,
|
||||
dest: 'http://localhost:5000/a/foo',
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
SKIP_PREFLIGHT_CHECK=true
|
||||
BROWSER=none
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"url": "https://github.com/marko-js-samples/marko-starter-demo"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production marko-starter build && mv dist public",
|
||||
"build": "NODE_ENV=production marko-starter build && rm -rf public && mv dist public",
|
||||
"format": "prettier src/**/*.{js,css,less} --write && marko-prettyprint src",
|
||||
"lint": "eslint src",
|
||||
"serve": "NODE_ENV=production marko-starter serve-static",
|
||||
|
||||
1
packages/now-cli/test/dev/fixtures/23-docusaurus/.env
Normal file
1
packages/now-cli/test/dev/fixtures/23-docusaurus/.env
Normal file
@@ -0,0 +1 @@
|
||||
BROWSER=none
|
||||
@@ -0,0 +1 @@
|
||||
FOO="build-and-runtime"
|
||||
@@ -0,0 +1,2 @@
|
||||
public
|
||||
.now
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = (_req, res) => res.end(process.env.FOO);
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "mkdir public && echo $FOO > public/index.html"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = (req, res) => res.json(req.query)
|
||||
@@ -0,0 +1 @@
|
||||
Index Page should not be served
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"routes": [
|
||||
{
|
||||
"src": "/(.*)",
|
||||
"dest": "/api/$1",
|
||||
"headers": {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "*"
|
||||
},
|
||||
"continue": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -137,7 +137,7 @@ async function getPackedBuilderPath(builderDirName) {
|
||||
}
|
||||
|
||||
async function testPath(t, port, status, path, expectedText, headers = {}) {
|
||||
const opts = { redirect: 'manual' };
|
||||
const opts = { redirect: 'manual-dont-change' };
|
||||
const res = await fetch(`http://localhost:${port}${path}`, opts);
|
||||
const msg = `Testing path ${path}`;
|
||||
t.is(res.status, status, msg);
|
||||
@@ -146,9 +146,15 @@ async function testPath(t, port, status, path, expectedText, headers = {}) {
|
||||
t.is(actualText.trim(), expectedText.trim(), msg);
|
||||
}
|
||||
if (headers) {
|
||||
Object.keys(headers).forEach(key => {
|
||||
const k = key.toLowerCase();
|
||||
t.is(headers[k], res.headers[k], msg);
|
||||
Object.entries(headers).forEach(([key, expectedValue]) => {
|
||||
let actualValue = res.headers.get(key);
|
||||
if (key.toLowerCase() === 'location' && actualValue === '//') {
|
||||
// HACK: `node-fetch` has strang behavior for location header so fix it
|
||||
// with `manual-dont-change` opt and convert double slash to single.
|
||||
// See https://github.com/node-fetch/node-fetch/issues/417#issuecomment-587233352
|
||||
actualValue = '/';
|
||||
}
|
||||
t.is(actualValue, expectedValue, msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -468,6 +474,19 @@ test(
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] should allow user rewrites for path segment files',
|
||||
testFixtureStdio('test-zero-config-rewrite', async (t, port, testPath) => {
|
||||
await testPath(404, '/');
|
||||
await testPath(200, '/echo/1', '{"id":"1"}', {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
});
|
||||
await testPath(200, '/echo/2', '{"id":"2"}', {
|
||||
'Access-Control-Allow-Headers': '*',
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] validate builds', async t => {
|
||||
const directory = fixture('invalid-builds');
|
||||
const output = await exec(directory);
|
||||
@@ -717,18 +736,15 @@ test(
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] 01-node', async t => {
|
||||
const tester = testFixtureStdio('01-node', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
test(
|
||||
'[now dev] 01-node',
|
||||
testFixtureStdio('01-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /A simple deployment with the Now API!/gm);
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// Angular has `engines: { node: "10.x" }` in its `package.json`
|
||||
test('[now dev] 02-angular-node', async t => {
|
||||
@@ -768,26 +784,21 @@ test('[now dev] 02-angular-node', async t => {
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] 03-aurelia', async t => {
|
||||
const tester = testFixtureStdio('03-aurelia', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
test(
|
||||
'[now dev] 03-aurelia',
|
||||
testFixtureStdio('03-aurelia', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Aurelia Navigation Skeleton/gm);
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 04-create-react-app',
|
||||
testFixtureStdio('04-create-react-app', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /React App/gm);
|
||||
})
|
||||
@@ -797,10 +808,8 @@ test('[now dev] 05-gatsby', async t => {
|
||||
if (shouldSkip(t, '05-gatsby', '>^6.14.0 || ^8.10.0 || >=9.10.0')) return;
|
||||
|
||||
const tester = testFixtureStdio('05-gatsby', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Gatsby Default Starter/gm);
|
||||
});
|
||||
@@ -808,24 +817,20 @@ test('[now dev] 05-gatsby', async t => {
|
||||
await tester(t);
|
||||
});
|
||||
|
||||
test('[now dev] 06-gridsome', async t => {
|
||||
const tester = testFixtureStdio('06-gridsome', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
test(
|
||||
'[now dev] 06-gridsome',
|
||||
testFixtureStdio('06-gridsome', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
t.is(response.status, 200, await response.text());
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 07-hexo-node',
|
||||
testFixtureStdio('07-hexo-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Hexo \+ Node.js API/gm);
|
||||
})
|
||||
@@ -834,175 +839,77 @@ test(
|
||||
test(
|
||||
'[now dev] 08-hugo',
|
||||
testFixtureStdio('08-hugo', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
// const body = await response.text();
|
||||
// t.regex(body, /Hugo on ZEIT Now/gm);
|
||||
t.is(response.status, 200, await response.text());
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] 10-nextjs-node', async t => {
|
||||
const tester = testFixtureStdio('10-nextjs-node', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
test(
|
||||
'[now dev] 10-nextjs-node',
|
||||
testFixtureStdio('10-nextjs-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Next.js \+ Node.js API/gm);
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
|
||||
// test('[now dev] 11-nuxtjs-node', async t => {
|
||||
// const directory = fixture('11-nuxtjs-node');
|
||||
// const { dev, port } = await testFixture(directory);
|
||||
|
||||
// try {
|
||||
// // start `now dev` detached in child_process
|
||||
// dev.unref();
|
||||
|
||||
// const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
|
||||
// validateResponseHeaders(t, response);
|
||||
|
||||
// const body = await response.text();
|
||||
// t.regex(body, /Nuxt.js \+ Node.js API/gm);
|
||||
|
||||
// } finally {
|
||||
// dev.kill('SIGTERM')
|
||||
// }
|
||||
// });
|
||||
|
||||
test('[now dev] 12-polymer-node', async t => {
|
||||
const directory = fixture('12-polymer-node');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 12-polymer-node',
|
||||
testFixtureStdio('12-polymer-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Polymer \+ Node.js API/gm);
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] 13-preact-node', async t => {
|
||||
const directory = fixture('13-preact-node');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 13-preact-node',
|
||||
testFixtureStdio('13-preact-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Preact \+ Node.js API/gm);
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] 14-svelte-node', async t => {
|
||||
const directory = fixture('14-svelte-node');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 80);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 14-svelte-node',
|
||||
testFixtureStdio('14-svelte-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Svelte \+ Node.js API/gm);
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
// test('[now dev] 15-umijs-node', async t => {
|
||||
// const directory = fixture('15-umijs-node');
|
||||
// const { dev, port } = await testFixture(directory);
|
||||
|
||||
// try {
|
||||
// // start `now dev` detached in child_process
|
||||
// dev.unref();
|
||||
|
||||
// const response = await fetchWithRetry(`http://localhost:${port}`, 80);
|
||||
|
||||
// validateResponseHeaders(t, response);
|
||||
|
||||
// const body = await response.text();
|
||||
// t.regex(body, /UmiJS \+ Node.js API/gm);
|
||||
|
||||
// } finally {
|
||||
// dev.kill('SIGTERM')
|
||||
// }
|
||||
// });
|
||||
|
||||
test('[now dev] 16-vue-node', async t => {
|
||||
const directory = fixture('16-vue-node');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 16-vue-node',
|
||||
testFixtureStdio('16-vue-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Vue.js \+ Node.js API/gm);
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] 17-vuepress-node', async t => {
|
||||
const directory = fixture('17-vuepress-node');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 17-vuepress-node',
|
||||
testFixtureStdio('17-vuepress-node', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /VuePress \+ Node.js API/gm);
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] double slashes redirect', async t => {
|
||||
const directory = fixture('01-node');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
// Wait for `now dev` to boot up
|
||||
await sleep(ms('10s'));
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] double slashes redirect',
|
||||
testFixtureStdio('01-node', async (t, port) => {
|
||||
{
|
||||
const res = await fetch(`http://localhost:${port}////?foo=bar`, {
|
||||
redirect: 'manual',
|
||||
@@ -1041,31 +948,24 @@ test('[now dev] double slashes redirect', async t => {
|
||||
body.startsWith('December')
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] 18-marko', async t => {
|
||||
const tester = testFixtureStdio('18-marko', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 18-marko',
|
||||
testFixtureStdio('18-marko', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Marko Starter/gm);
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 19-mithril',
|
||||
testFixtureStdio('19-mithril', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Mithril on ZEIT Now/gm);
|
||||
})
|
||||
@@ -1074,52 +974,42 @@ test(
|
||||
test(
|
||||
'[now dev] 20-riot',
|
||||
testFixtureStdio('20-riot', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Riot on ZEIT Now/gm);
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] 21-charge', async t => {
|
||||
const tester = testFixtureStdio('21-charge', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
test(
|
||||
'[now dev] 21-charge',
|
||||
testFixtureStdio('21-charge', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Welcome to my new Charge site/gm);
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 22-brunch',
|
||||
testFixtureStdio('22-brunch', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 50);
|
||||
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Bon Appétit./gm);
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] 23-docusaurus', async t => {
|
||||
const tester = testFixtureStdio('23-docusaurus', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
test(
|
||||
'[now dev] 23-docusaurus',
|
||||
testFixtureStdio('23-docusaurus', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /My Site/gm);
|
||||
});
|
||||
|
||||
await tester(t);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] 24-ember', async t => {
|
||||
if (shouldSkip(t, '24-ember', '>^6.14.0 || ^8.10.0 || >=9.10.0')) return;
|
||||
@@ -1136,16 +1026,12 @@ test('[now dev] 24-ember', async t => {
|
||||
tester(t);
|
||||
});
|
||||
|
||||
test('[now dev] temporary directory listing', async t => {
|
||||
const directory = fixture('temporary-directory-listing');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
test(
|
||||
'[now dev] temporary directory listing',
|
||||
testFixtureStdio('temporary-directory-listing', async (t, port) => {
|
||||
const directory = fixture('temporary-directory-listing');
|
||||
await fs.unlink(path.join(directory, 'index.txt')).catch(() => null);
|
||||
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
await sleep(ms('20s'));
|
||||
|
||||
const firstResponse = await fetch(`http://localhost:${port}`);
|
||||
@@ -1166,10 +1052,8 @@ test('[now dev] temporary directory listing', async t => {
|
||||
|
||||
await sleep(ms('1s'));
|
||||
}
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] add a `package.json` to trigger `@now/static-build`', async t => {
|
||||
const directory = fixture('trigger-static-build');
|
||||
@@ -1240,13 +1124,9 @@ test('[now dev] no build matches warning', async t => {
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] do not recursivly check the path', async t => {
|
||||
const directory = fixture('handle-filesystem-missing');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
dev.unref();
|
||||
|
||||
test(
|
||||
'[now dev] do not recursivly check the path',
|
||||
testFixtureStdio('handle-filesystem-missing', async (t, port) => {
|
||||
{
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
validateResponseHeaders(t, response);
|
||||
@@ -1259,10 +1139,8 @@ test('[now dev] do not recursivly check the path', async t => {
|
||||
validateResponseHeaders(t, response);
|
||||
t.is(response.status, 404);
|
||||
}
|
||||
} finally {
|
||||
dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] render warning for empty cwd dir', async t => {
|
||||
const directory = fixture('empty');
|
||||
@@ -1356,24 +1234,15 @@ test('[now dev] do not rebuild for changes in the output directory', async t =>
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] 25-nextjs-src-dir', async t => {
|
||||
const directory = fixture('25-nextjs-src-dir');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 80);
|
||||
|
||||
test(
|
||||
'[now dev] 25-nextjs-src-dir',
|
||||
testFixtureStdio('25-nextjs-src-dir', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 10);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Next.js \+ Node.js API/gm);
|
||||
} finally {
|
||||
dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 26-nextjs-secrets',
|
||||
@@ -1389,6 +1258,16 @@ test(
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] 27-zero-config-env',
|
||||
testFixtureStdio('27-zero-config-env', async (t, port) => {
|
||||
const api = await fetchWithRetry(`http://localhost:${port}/api/print`);
|
||||
const index = await fetchWithRetry(`http://localhost:${port}`);
|
||||
t.regex(await api.text(), new RegExp('build-and-runtime'));
|
||||
t.regex(await index.text(), new RegExp('build-and-runtime'));
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] Use `@now/python` with Flask requirements.txt',
|
||||
testFixtureStdio('python-flask', async (t, port) => {
|
||||
|
||||
@@ -323,6 +323,31 @@ CMD ["node", "index.js"]`,
|
||||
'index.html': 'Home page',
|
||||
'secret/file.txt': 'my secret',
|
||||
},
|
||||
'build-secret': {
|
||||
'package.json': JSON.stringify({
|
||||
private: true,
|
||||
scripts: {
|
||||
build: 'mkdir public && echo $MY_SECRET > public/index.txt',
|
||||
},
|
||||
}),
|
||||
'now.json': JSON.stringify({
|
||||
build: {
|
||||
env: {
|
||||
MY_SECRET: '@mysecret',
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
'api-env': {
|
||||
'api/get-env.js': 'module.exports = (_, res) => res.json(process.env)',
|
||||
'print.js': 'console.log(JSON.stringify(process.env))',
|
||||
'package.json': JSON.stringify({
|
||||
private: true,
|
||||
scripts: {
|
||||
build: 'mkdir public && node print.js > public/index.json',
|
||||
},
|
||||
}),
|
||||
},
|
||||
'alias-rules': {
|
||||
'rules.json': JSON.stringify({
|
||||
rules: [
|
||||
|
||||
272
packages/now-cli/test/integration.js
vendored
272
packages/now-cli/test/integration.js
vendored
@@ -135,8 +135,11 @@ const apiFetch = (url, { headers, ...options } = {}) => {
|
||||
};
|
||||
|
||||
const waitForPrompt = (cp, assertion) =>
|
||||
new Promise(resolve => {
|
||||
new Promise((resolve, reject) => {
|
||||
console.log('Waiting for prompt...');
|
||||
setTimeout(() => reject(new Error('timeout in waitForPrompt')), 60000);
|
||||
const listener = chunk => {
|
||||
console.log('> ' + chunk);
|
||||
if (assertion(chunk)) {
|
||||
cp.stdout.off && cp.stdout.off('data', listener);
|
||||
cp.stderr.off && cp.stderr.off('data', listener);
|
||||
@@ -325,6 +328,197 @@ test('deploy using --local-config flag above target', async t => {
|
||||
t.regex(host, /root-level/gm, `Expected "root-level" but received "${host}"`);
|
||||
});
|
||||
|
||||
test('Deploy `api-env` fixture and test `now env` command', async t => {
|
||||
const target = fixture('api-env');
|
||||
|
||||
async function nowDeploy() {
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
[...defaultArgs, '--confirm'],
|
||||
{
|
||||
reject: false,
|
||||
cwd: target,
|
||||
}
|
||||
);
|
||||
console.log({ stdout });
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
}
|
||||
|
||||
async function nowEnvLsIsEmpty() {
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
['env', 'ls', ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
cwd: target,
|
||||
}
|
||||
);
|
||||
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
t.regex(stderr, /0 Environment Variables found in Project/gm);
|
||||
}
|
||||
|
||||
async function nowEnvAdd() {
|
||||
const now = execa(binaryPath, ['env', 'add', ...defaultArgs], {
|
||||
reject: false,
|
||||
cwd: target,
|
||||
});
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('What’s the name of the variable?')
|
||||
);
|
||||
now.stdin.write('MY_ENV_VAR\n');
|
||||
await waitForPrompt(
|
||||
now,
|
||||
chunk =>
|
||||
chunk.includes('What’s the value of') && chunk.includes('MY_ENV_VAR')
|
||||
);
|
||||
now.stdin.write('MY_VALUE\n');
|
||||
|
||||
await waitForPrompt(
|
||||
now,
|
||||
chunk =>
|
||||
chunk.includes('which Environments') && chunk.includes('MY_ENV_VAR')
|
||||
);
|
||||
now.stdin.write('a\n'); // select all
|
||||
|
||||
const { exitCode, stderr, stdout } = await now;
|
||||
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
}
|
||||
|
||||
async function nowEnvAddFromStdin() {
|
||||
const now = execa(
|
||||
binaryPath,
|
||||
['env', 'add', 'MY_STDIN_VAR', 'preview', ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
cwd: target,
|
||||
}
|
||||
);
|
||||
now.stdin.end('MY_STDIN_VALUE');
|
||||
const { exitCode, stderr, stdout } = await now;
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
}
|
||||
|
||||
async function nowEnvLsIncludesVar() {
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
['env', 'ls', ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
cwd: target,
|
||||
}
|
||||
);
|
||||
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
t.regex(stderr, /4 Environment Variables found in Project/gm);
|
||||
|
||||
const lines = stdout.split('\n');
|
||||
|
||||
const myEnvVars = lines.filter(line => line.includes('MY_ENV_VAR'));
|
||||
t.is(myEnvVars.length, 3);
|
||||
t.regex(myEnvVars.join('\n'), /development/gm);
|
||||
t.regex(myEnvVars.join('\n'), /preview/gm);
|
||||
t.regex(myEnvVars.join('\n'), /production/gm);
|
||||
|
||||
const myStdinVars = lines.filter(line => line.includes('MY_STDIN_VAR'));
|
||||
t.is(myStdinVars.length, 1);
|
||||
t.regex(myStdinVars.join('\n'), /preview/gm);
|
||||
}
|
||||
|
||||
async function nowEnvPull() {
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
['env', 'pull', '-y', ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
cwd: target,
|
||||
}
|
||||
);
|
||||
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
t.regex(stderr, /Created .env file/gm);
|
||||
|
||||
const contents = fs.readFileSync(path.join(target, '.env'), 'utf8');
|
||||
t.is(contents, 'MY_ENV_VAR="MY_VALUE"\n');
|
||||
}
|
||||
|
||||
async function nowDeployWithVar() {
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
[...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
cwd: target,
|
||||
}
|
||||
);
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
const { host } = new URL(stdout);
|
||||
|
||||
const apiUrl = `https://${host}/api/get-env`;
|
||||
console.log({ apiUrl });
|
||||
const apiRes = await fetch(apiUrl);
|
||||
t.is(apiRes.status, 200, formatOutput({ stderr, stdout }));
|
||||
const apiJson = await apiRes.json();
|
||||
t.is(apiJson['MY_ENV_VAR'], 'MY_VALUE');
|
||||
t.is(apiJson['MY_STDIN_VAR'], 'MY_STDIN_VALUE');
|
||||
|
||||
const homeUrl = `https://${host}`;
|
||||
console.log({ homeUrl });
|
||||
const homeRes = await fetch(homeUrl);
|
||||
t.is(homeRes.status, 200, formatOutput({ stderr, stdout }));
|
||||
const homeJson = await homeRes.json();
|
||||
t.is(homeJson['MY_ENV_VAR'], 'MY_VALUE');
|
||||
t.is(homeJson['MY_STDIN_VAR'], 'MY_STDIN_VALUE');
|
||||
}
|
||||
|
||||
async function nowEnvRemove() {
|
||||
const now = execa(binaryPath, ['env', 'rm', '-y', ...defaultArgs], {
|
||||
reject: false,
|
||||
cwd: target,
|
||||
});
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('What’s the name of the variable?')
|
||||
);
|
||||
now.stdin.write('MY_ENV_VAR\n');
|
||||
|
||||
await waitForPrompt(
|
||||
now,
|
||||
chunk =>
|
||||
chunk.includes('which Environments') && chunk.includes('MY_ENV_VAR')
|
||||
);
|
||||
now.stdin.write('a\n'); // select all
|
||||
|
||||
const { exitCode, stderr, stdout } = await now;
|
||||
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
}
|
||||
|
||||
async function nowEnvRemoveWithArgs() {
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
['env', 'rm', 'MY_STDIN_VAR', 'preview', '-y', ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
cwd: target,
|
||||
}
|
||||
);
|
||||
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
}
|
||||
|
||||
await nowDeploy();
|
||||
await nowEnvLsIsEmpty();
|
||||
await nowEnvAdd();
|
||||
await nowEnvAddFromStdin();
|
||||
await nowEnvLsIncludesVar();
|
||||
await nowEnvPull();
|
||||
await nowDeployWithVar();
|
||||
await nowEnvRemove();
|
||||
await nowEnvRemoveWithArgs();
|
||||
await nowEnvLsIsEmpty();
|
||||
});
|
||||
|
||||
test('print the deploy help message', async t => {
|
||||
const { stderr, stdout, exitCode } = await execa(
|
||||
binaryPath,
|
||||
@@ -390,6 +584,50 @@ test('should error with suggestion for secrets subcommand', async t => {
|
||||
);
|
||||
});
|
||||
|
||||
test('should add secret with hyphen prefix', async t => {
|
||||
const target = fixture('build-secret');
|
||||
const key = 'mysecret';
|
||||
const value = '-foo_bar';
|
||||
|
||||
let secretCall = await execa(
|
||||
binaryPath,
|
||||
['secrets', 'add', ...defaultArgs, key, value],
|
||||
{
|
||||
cwd: target,
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
t.is(
|
||||
secretCall.exitCode,
|
||||
0,
|
||||
formatOutput({ stderr: secretCall.stderr, stdout: secretCall.stdout })
|
||||
);
|
||||
|
||||
let targetCall = await execa(binaryPath, [...defaultArgs, '--confirm'], {
|
||||
cwd: target,
|
||||
reject: false,
|
||||
});
|
||||
|
||||
t.is(
|
||||
targetCall.exitCode,
|
||||
0,
|
||||
formatOutput({ stderr: targetCall.stderr, stdout: targetCall.stdout })
|
||||
);
|
||||
const { host } = new URL(targetCall.stdout);
|
||||
const response = await fetch(`https://${host}`);
|
||||
t.is(
|
||||
response.status,
|
||||
200,
|
||||
formatOutput({ stderr: targetCall.stderr, stdout: targetCall.stdout })
|
||||
);
|
||||
t.is(
|
||||
await response.text(),
|
||||
`${value}\n`,
|
||||
formatOutput({ stderr: targetCall.stderr, stdout: targetCall.stdout })
|
||||
);
|
||||
});
|
||||
|
||||
test('login with unregistered user', async t => {
|
||||
const { stdout, stderr, exitCode } = await execa(
|
||||
binaryPath,
|
||||
@@ -1676,6 +1914,38 @@ test('print correct link in legacy warning', async t => {
|
||||
t.regex(stderr, /migrate-to-zeit-now/);
|
||||
});
|
||||
|
||||
test('`now rm` removes a deployment', async t => {
|
||||
const directory = fixture('builds');
|
||||
|
||||
const { stdout } = await execa(
|
||||
binaryPath,
|
||||
[
|
||||
directory,
|
||||
'--public',
|
||||
'--name',
|
||||
session,
|
||||
...defaultArgs,
|
||||
'-V',
|
||||
2,
|
||||
'--force',
|
||||
'--confirm',
|
||||
],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
const { host } = new URL(stdout);
|
||||
const { exitCode, stdout: stdoutRemove } = await execute([
|
||||
'rm',
|
||||
host,
|
||||
'--yes',
|
||||
]);
|
||||
|
||||
t.truthy(stdoutRemove.includes(host));
|
||||
t.is(exitCode, 0);
|
||||
});
|
||||
|
||||
test('`now rm` 404 exits quickly', async t => {
|
||||
const start = Date.now();
|
||||
const { exitCode, stderr, stdout } = await execute([
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now-client",
|
||||
"version": "7.0.2-canary.0",
|
||||
"version": "7.1.0",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://zeit.co",
|
||||
|
||||
@@ -169,9 +169,9 @@ export async function* deploy(
|
||||
deploymentOptions.forceNew = clientOptions.force;
|
||||
}
|
||||
|
||||
if (clientOptions.forceNewWithCache) {
|
||||
if (clientOptions.withCache) {
|
||||
debug(
|
||||
`'forceNewWithCache' is provided. Force deploy will be performed with cache retention`
|
||||
`'withCache' is provided. Force deploy will be performed with cache retention`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import buildCreateDeployment from './create-deployment';
|
||||
|
||||
export { getNowIgnore } from './utils/index';
|
||||
export const createDeployment = buildCreateDeployment(2);
|
||||
export const createLegacyDeployment = buildCreateDeployment(1);
|
||||
export * from './errors';
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface NowClientOptions {
|
||||
teamId?: string;
|
||||
apiUrl?: string;
|
||||
force?: boolean;
|
||||
forceNewWithCache?: boolean;
|
||||
withCache?: boolean;
|
||||
userAgent?: string;
|
||||
defaultName?: string;
|
||||
isDirectory?: boolean;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { nodeFetch, zeitFetch } from './fetch';
|
||||
import { join, sep, relative } from 'path';
|
||||
import qs from 'querystring';
|
||||
import ignore from 'ignore';
|
||||
type Ignore = ReturnType<typeof ignore>;
|
||||
import { pkgVersion } from '../pkg';
|
||||
import { NowClientOptions, DeploymentOptions, NowConfig } from '../types';
|
||||
import { Sema } from 'async-sema';
|
||||
@@ -74,15 +75,17 @@ const maybeRead = async function<T>(path: string, default_: T) {
|
||||
}
|
||||
};
|
||||
|
||||
export async function getNowIgnore(path: string | string[]): Promise<any> {
|
||||
let ignores: string[] = [
|
||||
'.hg',
|
||||
'.git',
|
||||
export async function getNowIgnore(
|
||||
path: string | string[]
|
||||
): Promise<{ ig: Ignore; ignores: string[] }> {
|
||||
const ignores: string[] = [
|
||||
'.hg/',
|
||||
'.git/',
|
||||
'.gitmodules',
|
||||
'.svn',
|
||||
'.svn/',
|
||||
'.cache',
|
||||
'.next',
|
||||
'.now',
|
||||
'.next/',
|
||||
'.now/',
|
||||
'.npmignore',
|
||||
'.dockerignore',
|
||||
'.gitignore',
|
||||
@@ -95,7 +98,7 @@ export async function getNowIgnore(path: string | string[]): Promise<any> {
|
||||
'.venv',
|
||||
'npm-debug.log',
|
||||
'config.gypi',
|
||||
'node_modules',
|
||||
'node_modules/',
|
||||
'__pycache__/',
|
||||
'venv/',
|
||||
'CVS',
|
||||
|
||||
@@ -12,8 +12,8 @@ export function generateQueryString(clientOptions: NowClientOptions): string {
|
||||
options.set('forceNew', '1');
|
||||
}
|
||||
|
||||
if (clientOptions.forceNewWithCache) {
|
||||
options.set('forceNewWithCache', '1');
|
||||
if (clientOptions.withCache) {
|
||||
options.set('withCache', '1');
|
||||
}
|
||||
|
||||
if (clientOptions.skipAutoDetectionConfirmation) {
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
now "github.com/zeit/now/utils/go/bridge"
|
||||
now "github.com/zeit/now-go-bridge/go/bridge"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"__NOW_HANDLER_PACKAGE_NAME"
|
||||
|
||||
now "github.com/zeit/now/utils/go/bridge"
|
||||
now "github.com/zeit/now-go-bridge/go/bridge"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/go",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.7",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/go",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/next",
|
||||
"version": "2.5.1",
|
||||
"version": "2.5.3",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/next-js",
|
||||
|
||||
@@ -829,7 +829,7 @@ export const build = async ({
|
||||
} = await nodeFileTrace(apiPages, { base: workPath });
|
||||
|
||||
const { fileList, reasons: nonApiReasons } = await nodeFileTrace(
|
||||
Object.keys(pages).map(page => pages[page].fsPath),
|
||||
nonApiPages,
|
||||
{ base: workPath }
|
||||
);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"propes": [
|
||||
"probes": [
|
||||
{
|
||||
"path": "/api/memory",
|
||||
"status": 200,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"propes": [
|
||||
"probes": [
|
||||
{
|
||||
"path": "/",
|
||||
"status": 200,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/node",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/node-js",
|
||||
|
||||
@@ -15,3 +15,8 @@ export type NowResponse = ServerResponse & {
|
||||
json: (jsonBody: any) => NowResponse;
|
||||
status: (statusCode: number) => NowResponse;
|
||||
};
|
||||
|
||||
export type NowApiHandler = (
|
||||
req: NowRequest,
|
||||
res: NowResponse
|
||||
) => void;
|
||||
|
||||
8
packages/now-node/test/fixtures/15-helpers/ts/handler.ts
vendored
Normal file
8
packages/now-node/test/fixtures/15-helpers/ts/handler.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import { NowApiHandler } from './types';
|
||||
|
||||
const listener: NowApiHandler = (req, res) => {
|
||||
res.status(200);
|
||||
res.send('hello:RANDOMNESS_PLACEHOLDER');
|
||||
};
|
||||
|
||||
export default listener;
|
||||
@@ -1,14 +1,16 @@
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
|
||||
import sys
|
||||
import base64
|
||||
import json
|
||||
import inspect
|
||||
from importlib import util
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
|
||||
# Import relative path https://stackoverflow.com/a/67692/266535
|
||||
# Import relative path https://docs.python.org/3.6/library/importlib.html#importing-a-source-file-directly
|
||||
__now_spec = util.spec_from_file_location("__NOW_HANDLER_MODULE_NAME", "./__NOW_HANDLER_ENTRYPOINT")
|
||||
__now_module = util.module_from_spec(__now_spec)
|
||||
__now_spec.loader.exec_module(__now_module)
|
||||
sys.modules["__NOW_HANDLER_MODULE_NAME"] = __now_module
|
||||
__now_variables = dir(__now_module)
|
||||
|
||||
|
||||
@@ -83,7 +85,6 @@ elif 'app' in __now_variables:
|
||||
not inspect.iscoroutinefunction(__now_module.app.__call__)
|
||||
):
|
||||
print('using Web Server Gateway Interface (WSGI)')
|
||||
import sys
|
||||
from urllib.parse import urlparse, unquote
|
||||
from werkzeug._compat import BytesIO
|
||||
from werkzeug._compat import string_types
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/python",
|
||||
"version": "1.1.5",
|
||||
"version": "1.1.6",
|
||||
"main": "./dist/index.js",
|
||||
"license": "MIT",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/python",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/routing-utils",
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"description": "ZEIT Now routing utilities",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
@@ -49,22 +49,30 @@ export function convertRedirects(
|
||||
): Route[] {
|
||||
return redirects.map(r => {
|
||||
const { src, segments } = sourceToRegex(r.source);
|
||||
const loc = replaceSegments(segments, r.destination, true);
|
||||
const route: Route = {
|
||||
src,
|
||||
headers: { Location: loc },
|
||||
status: r.statusCode || defaultStatus,
|
||||
};
|
||||
return route;
|
||||
try {
|
||||
const loc = replaceSegments(segments, r.destination, true);
|
||||
const route: Route = {
|
||||
src,
|
||||
headers: { Location: loc },
|
||||
status: r.statusCode || defaultStatus,
|
||||
};
|
||||
return route;
|
||||
} catch (e) {
|
||||
throw new Error('Failed to parse destination: ' + r.destination);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function convertRewrites(rewrites: NowRewrite[]): Route[] {
|
||||
return rewrites.map(r => {
|
||||
const { src, segments } = sourceToRegex(r.source);
|
||||
const dest = replaceSegments(segments, r.destination);
|
||||
const route: Route = { src, dest, check: true };
|
||||
return route;
|
||||
try {
|
||||
const dest = replaceSegments(segments, r.destination);
|
||||
const route: Route = { src, dest, check: true };
|
||||
return route;
|
||||
} catch (e) {
|
||||
throw new Error('Failed to parse destination: ' + r.destination);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -172,19 +180,13 @@ function replaceSegments(
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, value] of Object.entries(indexes)) {
|
||||
if (
|
||||
isRedirect &&
|
||||
new RegExp(`\\${value}(?!\\d)`).test(pathname + (hash || ''))
|
||||
) {
|
||||
// Don't add segment to query if used in destination
|
||||
// and it's a redirect so that we don't pollute the query
|
||||
// with unwanted values
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(name in query)) {
|
||||
query[name] = value;
|
||||
// We only add path segments to redirect queries if manually
|
||||
// specified
|
||||
if (!isRedirect) {
|
||||
for (const [name, value] of Object.entries(indexes)) {
|
||||
if (!(name in query)) {
|
||||
query[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ test('convertRedirects', () => {
|
||||
},
|
||||
{
|
||||
src: '^\\/projects(?:\\/([^\\/]+?))(?:\\/([^\\/]+?))$',
|
||||
headers: { Location: '/projects.html?id=$1&action=$2' },
|
||||
headers: { Location: '/projects.html' },
|
||||
status: 308,
|
||||
},
|
||||
{
|
||||
@@ -236,7 +236,7 @@ test('convertRedirects', () => {
|
||||
},
|
||||
{
|
||||
src: '^\\/catchme(?:\\/((?:[^\\/]+?)(?:\\/(?:[^\\/]+?))*))?$',
|
||||
headers: { Location: '/api/user?id=$1' },
|
||||
headers: { Location: '/api/user' },
|
||||
status: 308,
|
||||
},
|
||||
{
|
||||
@@ -321,6 +321,8 @@ test('convertRewrites', () => {
|
||||
destination: '/another-catch/:hello+',
|
||||
},
|
||||
{ source: '/catchme/:id*', destination: '/api/user' },
|
||||
{ source: '/:path', destination: '/test?path=:path' },
|
||||
{ source: '/:path/:two', destination: '/test?path=:path' },
|
||||
]);
|
||||
|
||||
const expected = [
|
||||
@@ -386,6 +388,16 @@ test('convertRewrites', () => {
|
||||
dest: '/api/user?id=$1',
|
||||
check: true,
|
||||
},
|
||||
{
|
||||
src: '^(?:\\/([^\\/]+?))$',
|
||||
dest: '/test?path=$1',
|
||||
check: true,
|
||||
},
|
||||
{
|
||||
check: true,
|
||||
dest: '/test?path=$1&two=$2',
|
||||
src: '^(?:\\/([^\\/]+?))(?:\\/([^\\/]+?))$',
|
||||
},
|
||||
];
|
||||
|
||||
deepEqual(actual, expected);
|
||||
@@ -404,6 +416,8 @@ test('convertRewrites', () => {
|
||||
['/catchall/first/', '/catchall/first/second/'],
|
||||
['/another-catch/first/', '/another-catch/first/second/'],
|
||||
['/catchme/id-1', '/catchme/id/2'],
|
||||
['/first', '/another'],
|
||||
['/first/second', '/one/two'],
|
||||
];
|
||||
|
||||
const mustNotMatch = [
|
||||
@@ -420,6 +434,8 @@ test('convertRewrites', () => {
|
||||
['/random-catch/'],
|
||||
['/another-catch/'],
|
||||
['/catchm', '/random'],
|
||||
['/another/one'],
|
||||
['/not', '/these'],
|
||||
];
|
||||
|
||||
assertRegexMatches(actual, mustMatch, mustNotMatch);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/static-build",
|
||||
"version": "0.15.2-canary.1",
|
||||
"version": "0.16.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/static-builds",
|
||||
|
||||
@@ -197,6 +197,22 @@ const frameworkList: Framework[] = [
|
||||
buildCommand: 'ng build && scully',
|
||||
getOutputDirName: async () => 'dist/static',
|
||||
},
|
||||
{
|
||||
name: 'Ionic Angular',
|
||||
slug: 'ionic-angular',
|
||||
dependency: '@ionic/angular',
|
||||
buildCommand: 'ng build',
|
||||
getOutputDirName: async () => 'www',
|
||||
defaultRoutes: [
|
||||
{
|
||||
handle: 'filesystem',
|
||||
},
|
||||
{
|
||||
src: '/(.*)',
|
||||
dest: '/index.html',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Angular',
|
||||
slug: 'angular',
|
||||
@@ -263,6 +279,37 @@ const frameworkList: Framework[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Ionic React',
|
||||
slug: 'ionic-react',
|
||||
dependency: '@ionic/react',
|
||||
buildCommand: 'react-scripts build',
|
||||
getOutputDirName: async () => 'build',
|
||||
defaultRoutes: [
|
||||
{
|
||||
src: '/static/(.*)',
|
||||
headers: { 'cache-control': 's-maxage=31536000, immutable' },
|
||||
continue: true,
|
||||
},
|
||||
{
|
||||
src: '/service-worker.js',
|
||||
headers: { 'cache-control': 's-maxage=0' },
|
||||
continue: true,
|
||||
},
|
||||
{
|
||||
src: '/sockjs-node/(.*)',
|
||||
dest: '/sockjs-node/$1',
|
||||
},
|
||||
{
|
||||
handle: 'filesystem',
|
||||
},
|
||||
{
|
||||
src: '/(.*)',
|
||||
headers: { 'cache-control': 's-maxage=0' },
|
||||
dest: '/index.html',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Create React App',
|
||||
slug: 'create-react-app',
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
NowBuildError,
|
||||
} from '@now/build-utils';
|
||||
import { Route, Source } from '@now/routing-utils';
|
||||
import { getNowIgnore } from 'now-client';
|
||||
|
||||
const sleep = (n: number) => new Promise(resolve => setTimeout(resolve, n));
|
||||
|
||||
@@ -475,7 +476,15 @@ export async function build({
|
||||
routes.push(...frameworkRoutes);
|
||||
}
|
||||
|
||||
output = await glob('**', distPath, mountpoint);
|
||||
let ignore: string[] = [];
|
||||
if (config.zeroConfig) {
|
||||
const result = await getNowIgnore(distPath);
|
||||
ignore = result.ignores
|
||||
.map(file => (file.endsWith('/') ? `${file}**` : file))
|
||||
.concat(['yarn.lock', 'package-lock.json', 'package.json']);
|
||||
debug(`Using ignore: ${JSON.stringify(ignore)}`);
|
||||
}
|
||||
output = await glob('**', { cwd: distPath, ignore }, mountpoint);
|
||||
}
|
||||
|
||||
const watch = [path.join(mountpoint.replace(/^\.\/?/, ''), '**/*')];
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"build": "CI=false react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
|
||||
13
packages/now-static-build/test/fixtures/52-ionic-angular/.editorconfig
vendored
Normal file
13
packages/now-static-build/test/fixtures/52-ionic-angular/.editorconfig
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
5
packages/now-static-build/test/fixtures/52-ionic-angular/.firebaserc
vendored
Normal file
5
packages/now-static-build/test/fixtures/52-ionic-angular/.firebaserc
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"projects": {
|
||||
"default": "ionic-ng-conf-app"
|
||||
}
|
||||
}
|
||||
63
packages/now-static-build/test/fixtures/52-ionic-angular/.github/CONTRIBUTING.md
vendored
Normal file
63
packages/now-static-build/test/fixtures/52-ionic-angular/.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
# Contributing to the Ionic Conference Application
|
||||
|
||||
Thank you for taking the time to contribute! :tada::+1:
|
||||
|
||||
The following is a set of guidelines for contributing to the conference app. These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [How To Contribute](#how-to-contribute)
|
||||
- [Reporting Issues](#reporting-issues)
|
||||
- [Before Submitting an Issue](#before-submitting-an-issue)
|
||||
- [Determining the Repository](#determining-the-repository)
|
||||
- [Submitting the Issue](#submitting-the-issue)
|
||||
- [Submitting a Pull Request](#submitting-a-pull-request)
|
||||
- [Guidelines for Submitting](#guidelines-for-submitting)
|
||||
- [Code Style](#code-style)
|
||||
|
||||
## How To Contribute
|
||||
|
||||
### Reporting Issues
|
||||
|
||||
Before submitting an issue, please go through [the list below](#before-submitting-an-issue) as you might find a solution to your issue.
|
||||
|
||||
#### Before Submitting an Issue
|
||||
|
||||
- Make sure you get the latest version of the code and run through the [Getting Started](https://github.com/ionic-team/ionic-conference-app#getting-started) steps to see if this resolves your issue.
|
||||
- Check the [forum](https://forum.ionicframework.com) for similar questions and answers.
|
||||
- Go through [all issues](https://github.com/ionic-team/ionic-conference-app/issues?utf8=%E2%9C%93&q=is%3Aissue) on this repository to see if the issue has already been created. It could have been closed with a resolution, so check closed issues, too.
|
||||
- Chat with us in the [#ionic-v2](https://ionic-worldwide.slack.com/messages/ionic-v2/) channel on [Slack](http://ionicworldwide.herokuapp.com/) to see if we can find a solution to the problem!
|
||||
- [Determine which repository](#determining-the-repository) the problem should be reported in.
|
||||
|
||||
#### Determining the Repository
|
||||
|
||||
There are several repositories being used for Ionic, which makes it difficult to determine which one to report an issue to. Don't worry if you aren't sure, we can always move it!
|
||||
|
||||
- The [Ionic repository](https://github.com/ionic-team/ionic) is a repository for all things related to the Ionic Framework. If you are able to reproduce the issue in any of the Ionic starters (or an existing project), you'll want to submit the issue [here](http://ionicframework.com/submit-issue/).
|
||||
- The [Ionic CLI repository](https://github.com/ionic-team/ionic-cli) contains all of the code that allows you to run `ionic` commands from a terminal window. It is safe to put any issues [here](https://github.com/ionic-team/ionic-cli/issues) that relate to running an `ionic` command.
|
||||
- [This repository](https://github.com/ionic-team/ionic-conference-app) is a demo of the Ionic Framework. If you find an issue with this app that does not occur on [a new app](http://ionicframework.com/docs/v2/getting-started/installation/), please submit the issue [here](https://github.com/ionic-team/ionic-conference-app/issues).
|
||||
|
||||
#### Submitting the Issue
|
||||
|
||||
- **Use a clear and descriptive title** for the issue to identify the problem. This makes it easier for others to find.
|
||||
- **Describe the exact steps to reproduce the problem** with as many details as needed.
|
||||
- **Provide your configuration** by running `ionic info` in a terminal from _within_ the project folder and pasting this information in the issue.
|
||||
|
||||
### Submitting a Pull Request
|
||||
|
||||
#### Guidelines for Submitting
|
||||
|
||||
When in doubt, keep your pull requests small. To give a PR the best chance of getting accepted, do not bundle more than one "feature" or bug fix per PR. Doing so makes it very hard to accept it if one of the fixes has issues.
|
||||
|
||||
It's always best to create two smaller PRs than one big one.
|
||||
|
||||
Talk to us before creating a PR that refactors the code or directory structure of the project. This project is constantly changing to reflect the latest version of Ionic Framework so sometimes it will be in the process of getting fixed.
|
||||
|
||||
#### Code Style
|
||||
|
||||
Make sure to follow the existing code style as much as possible.
|
||||
|
||||
- No underscores prefixing JS functions.
|
||||
- Use flat Sass.
|
||||
- **Don't** use [BEM conventions](https://css-tricks.com/bem-101/).
|
||||
- Avoid nesting selectors. This is done to make it easier for users without Sass experience to understand and read.
|
||||
41
packages/now-static-build/test/fixtures/52-ionic-angular/.github/ISSUE_TEMPLATE.md
vendored
Normal file
41
packages/now-static-build/test/fixtures/52-ionic-angular/.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<!--
|
||||
IF YOU DON'T FILL OUT THE FOLLOWING INFORMATION WE MIGHT CLOSE YOUR ISSUE WITHOUT INVESTIGATING
|
||||
|
||||
If you are having problems formatting your issue please refer to this article on using markdown in Github: https://guides.github.com/features/mastering-markdown/
|
||||
-->
|
||||
|
||||
**I'm submitting a ...** (check one with "x")
|
||||
[ ] bug report
|
||||
[ ] feature request
|
||||
[ ] support request => Please do not submit support requests here, use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/
|
||||
|
||||
**Current behavior:**
|
||||
|
||||
<!-- Describe how the bug manifests. -->
|
||||
|
||||
**Expected behavior:**
|
||||
|
||||
<!-- Describe what the behavior would be without the bug. -->
|
||||
|
||||
**Steps to reproduce:**
|
||||
|
||||
<!-- If you are able to illustrate the bug or feature request with an example, please provide steps to reproduce and if possible a demo using the following template:
|
||||
|
||||
http://plnkr.co/edit/GJte2b?p=preview
|
||||
-->
|
||||
|
||||
**Related code:**
|
||||
|
||||
```
|
||||
insert any relevant code here
|
||||
```
|
||||
|
||||
**Other information:**
|
||||
|
||||
<!-- List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to fix, Stack Overflow links, forum links, etc. -->
|
||||
|
||||
**Ionic info:** (run `ionic info` from a terminal/cmd prompt and paste output below):
|
||||
|
||||
```
|
||||
insert the output from ionic info here
|
||||
```
|
||||
133
packages/now-static-build/test/fixtures/52-ionic-angular/.github/ionic-issue-bot.yml
vendored
Normal file
133
packages/now-static-build/test/fixtures/52-ionic-angular/.github/ionic-issue-bot.yml
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
triage:
|
||||
label: triage
|
||||
removeLabelWhenProjectAssigned: true
|
||||
dryRun: false
|
||||
|
||||
closeAndLock:
|
||||
labels:
|
||||
- label: "ionitron: support"
|
||||
message: >
|
||||
Thanks for the issue! This issue appears to be a support request. We use this issue tracker exclusively for
|
||||
bug reports and feature requests. Please use our [forum](https://forum.ionicframework.com) or our
|
||||
[slack channel](https://ionicworldwide.herokuapp.com/) for questions about the framework.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
- label: "ionitron: ionic pro"
|
||||
message: >
|
||||
Thanks for the issue! This issue appears to be related to Ionic Pro. We use this issue tracker exclusively for
|
||||
bug reports and feature requests. Please use the [Ionic Pro Support Forum](https://ionic.zendesk.com/hc/en-us/requests/new)
|
||||
to report this issue.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
- label: "ionitron: missing template"
|
||||
message: >
|
||||
Thanks for the issue! It appears that you have not filled out the provided issue template. We use this issue
|
||||
template in order to gather more information and further assist you. Please create a new issue and ensure the
|
||||
template is fully filled out.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
close: true
|
||||
lock: true
|
||||
dryRun: false
|
||||
|
||||
lockClosed:
|
||||
days: 30
|
||||
maxIssuesPerRun: 100
|
||||
message: >
|
||||
Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue.
|
||||
If this is still an issue with the latest version of the Ionic Conference App, please create a new issue and ensure
|
||||
the template is fully filled out.
|
||||
dryRun: false
|
||||
|
||||
stale:
|
||||
days: 365
|
||||
maxIssuesPerRun: 100
|
||||
exemptLabels:
|
||||
- good first issue
|
||||
- triage
|
||||
exemptAssigned: true
|
||||
exemptProjects: true
|
||||
exemptMilestones: true
|
||||
label: "ionitron: stale issue"
|
||||
message: >
|
||||
Thanks for the issue! This issue is being closed due to inactivity. If this is still
|
||||
an issue with the latest version of the Ionic Conference App, please create a new issue
|
||||
and ensure the template is fully filled out.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
close: true
|
||||
lock: true
|
||||
dryRun: false
|
||||
|
||||
noReply:
|
||||
days: 30
|
||||
maxIssuesPerRun: 100
|
||||
label: needs reply
|
||||
responseLabel: triage
|
||||
exemptProjects: true
|
||||
exemptMilestones: true
|
||||
message: >
|
||||
Thanks for the issue! This issue is being closed due to the lack of a reply. If this is still
|
||||
an issue with the latest version of the Ionic Conference App, please create a new issue
|
||||
and ensure the template is fully filled out.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
close: true
|
||||
lock: true
|
||||
dryRun: false
|
||||
|
||||
wrongRepo:
|
||||
repos:
|
||||
- label: "ionitron: ionic"
|
||||
repo: ionic
|
||||
message: >
|
||||
Thanks for the issue! We use this issue tracker exclusively for bug reports and feature requests
|
||||
associated with the Ionic Conference App. It appears that this issue is associated with the Ionic Framework.
|
||||
I am moving this issue to the Ionic Framework repository. Please track this issue over there.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
- label: "ionitron: cli"
|
||||
repo: ionic-cli
|
||||
message: >
|
||||
Thanks for the issue! We use this issue tracker exclusively for bug reports and feature requests
|
||||
associated with the Ionic Conference App. It appears that this issue is associated with the Ionic CLI.
|
||||
I am moving this issue to the Ionic CLI repository. Please track this issue over there.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
- label: "ionitron: docs"
|
||||
repo: ionic-docs
|
||||
message: >
|
||||
Thanks for the issue! We use this issue tracker exclusively for bug reports and feature requests
|
||||
associated with the Ionic Conference App. It appears that this issue is associated with the Ionic Documentation.
|
||||
I am moving this issue to the Ionic Docs repository. Please track this issue over there.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
- label: "ionitron: stencil"
|
||||
repo: stencil
|
||||
message: >
|
||||
Thanks for the issue! We use this issue tracker exclusively for bug reports and feature requests
|
||||
associated with the Ionic Conference App. It appears that this issue is associated with Stencil.
|
||||
I am moving this issue to the Stencil repository. Please track this issue over there.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
- label: "ionitron: native"
|
||||
repo: ionic-native
|
||||
message: >
|
||||
Thanks for the issue! We use this issue tracker exclusively for bug reports and feature requests
|
||||
associated with the Ionic Conference App. It appears that this issue is associated with Ionic Native.
|
||||
I am moving this issue to the Ionic Native repository. Please track this issue over there.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
close: true
|
||||
lock: true
|
||||
dryRun: false
|
||||
33
packages/now-static-build/test/fixtures/52-ionic-angular/.github/workflows/CI.yml
vendored
Normal file
33
packages/now-static-build/test/fixtures/52-ionic-angular/.github/workflows/CI.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# GitHub Actions docs
|
||||
# https://help.github.com/en/articles/about-github-actions
|
||||
# https://help.github.com/en/articles/workflow-syntax-for-github-actions
|
||||
|
||||
name: Install Dependencies, Lint, Build and Test
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
node_version: [12]
|
||||
os: [windows-latest, macOS-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node_version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
- name: Build
|
||||
run: npm run build -- --prod
|
||||
- name: Test
|
||||
run: npm test -- --configuration=ci
|
||||
- name: E2E
|
||||
run: npm run e2e -- --configuration=ci
|
||||
35
packages/now-static-build/test/fixtures/52-ionic-angular/.gitignore
vendored
Normal file
35
packages/now-static-build/test/fixtures/52-ionic-angular/.gitignore
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# Specifies intentionally untracked files to ignore when using Git
|
||||
# http://git-scm.com/docs/gitignore
|
||||
www/
|
||||
|
||||
*~
|
||||
*.sw[mnpcod]
|
||||
*.log
|
||||
*.tmp
|
||||
*.tmp.*
|
||||
log.txt
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.vscode/
|
||||
npm-debug.log*
|
||||
.firebase/
|
||||
.idea/
|
||||
.sourcemaps/
|
||||
.sass-cache/
|
||||
.tmp/
|
||||
.versions/
|
||||
coverage/
|
||||
dist/
|
||||
node_modules/
|
||||
tmp/
|
||||
temp/
|
||||
hooks/
|
||||
platforms/
|
||||
plugins/
|
||||
plugins/android.json
|
||||
plugins/ios.json
|
||||
$RECYCLE.BIN/
|
||||
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
UserInterfaceState.xcuserstate
|
||||
202
packages/now-static-build/test/fixtures/52-ionic-angular/LICENSE
vendored
Normal file
202
packages/now-static-build/test/fixtures/52-ionic-angular/LICENSE
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2015 Drifty Co.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
78
packages/now-static-build/test/fixtures/52-ionic-angular/README.md
vendored
Normal file
78
packages/now-static-build/test/fixtures/52-ionic-angular/README.md
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
# Ionic Angular Conference Application
|
||||
|
||||
This application is purely a kitchen-sink demo of the Ionic Framework and Angular.
|
||||
|
||||
**There is not an actual Ionic Conference at this time.** This project is just to show off Ionic components in a real-world application. Please go through the steps in [CONTRIBUTING](https://github.com/ionic-team/ionic-conference-app/blob/master/.github/CONTRIBUTING.md) before submitting an issue.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Getting Started](#getting-started)
|
||||
- [Contributing](#contributing)
|
||||
- [App Preview](#app-preview)
|
||||
- [Deploying](#deploying)
|
||||
- [Progressive Web App](#progressive-web-app)
|
||||
- [Android](#android)
|
||||
- [iOS](#ios)
|
||||
|
||||
## Getting Started
|
||||
|
||||
- [Download the installer](https://nodejs.org/) for Node LTS.
|
||||
- Install the ionic CLI globally: `npm install -g ionic`
|
||||
- Clone this repository: `git clone https://github.com/ionic-team/ionic-conference-app.git`.
|
||||
- Run `npm install` from the project root.
|
||||
- Run `ionic serve` in a terminal from the project root.
|
||||
- Profit. :tada:
|
||||
|
||||
_Note: See [How to Prevent Permissions Errors](https://docs.npmjs.com/getting-started/fixing-npm-permissions) if you are running into issues when trying to install packages globally._
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](https://github.com/ionic-team/ionic-conference-app/blob/master/.github/CONTRIBUTING.md) :tada::+1:
|
||||
|
||||
## App Preview
|
||||
|
||||
### [Menu](https://github.com/ionic-team/ionic-conference-app/blob/master/src/app/pages/menu/menu.html)
|
||||
|
||||
| Material Design | iOS |
|
||||
| -------------------------------------------------------- | ------------------------------------------------ |
|
||||
|  |  |
|
||||
|
||||
### [Schedule Page](https://github.com/ionic-team/ionic-conference-app/blob/master/src/app/pages/schedule/schedule.html)
|
||||
|
||||
| Material Design | iOS |
|
||||
| ---------------------------------------------------------------- | -------------------------------------------------------- |
|
||||
|  |  |
|
||||
|
||||
### [Speakers Page](https://github.com/ionic-team/ionic-conference-app/blob/master/src/app/pages/speaker-list/speaker-list.html)
|
||||
|
||||
| Material Design | iOS |
|
||||
| ---------------------------------------------------------------- | -------------------------------------------------------- |
|
||||
|  |  |
|
||||
|
||||
### [Speaker Detail Page](https://github.com/ionic-team/ionic-conference-app/blob/master/src/app/pages/speaker-detail/speaker-detail.html)
|
||||
|
||||
| Material Design | iOS |
|
||||
| ---------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
||||
|  |  |
|
||||
|
||||
### [About Page](https://github.com/ionic-team/ionic-conference-app/blob/master/src/app/pages/about/about.html)
|
||||
|
||||
| Material Design | iOS |
|
||||
| ---------------------------------------------------------- | -------------------------------------------------- |
|
||||
|  |  |
|
||||
|
||||
## Deploying
|
||||
|
||||
### Progressive Web App
|
||||
|
||||
1. Un-comment [these lines](https://github.com/ionic-team/ionic2-app-base/blob/master/src/index.html#L21)
|
||||
2. Run `npm run ionic:build --prod`
|
||||
3. Push the `www` folder to your hosting service
|
||||
|
||||
### Android
|
||||
|
||||
1. Run `ionic cordova run android --prod`
|
||||
|
||||
### iOS
|
||||
|
||||
1. Run `ionic cordova run ios --prod`
|
||||
196
packages/now-static-build/test/fixtures/52-ionic-angular/angular.json
vendored
Normal file
196
packages/now-static-build/test/fixtures/52-ionic-angular/angular.json
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"defaultProject": "app",
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"app": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"schematics": {},
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "www",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "src/assets",
|
||||
"output": "assets"
|
||||
},
|
||||
{
|
||||
"glob": "**/*.svg",
|
||||
"input": "node_modules/ionicons/dist/ionicons/svg",
|
||||
"output": "./svg"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
{
|
||||
"input": "src/theme/variables.scss"
|
||||
},
|
||||
{
|
||||
"input": "src/global.scss"
|
||||
}
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"serviceWorker": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ci": {
|
||||
"progress": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "app:build",
|
||||
"disableHostCheck": true
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "app:build:production"
|
||||
},
|
||||
"ci": {
|
||||
"progress": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "app:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "src/karma.conf.js",
|
||||
"styles": [
|
||||
{
|
||||
"input": "src/theme/variables.scss"
|
||||
},
|
||||
{
|
||||
"input": "src/global.scss"
|
||||
}
|
||||
],
|
||||
"scripts": [],
|
||||
"assets": [
|
||||
{
|
||||
"glob": "favicon.ico",
|
||||
"input": "src/",
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "src/assets",
|
||||
"output": "/assets"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"progress": false,
|
||||
"watch": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.spec.json",
|
||||
"e2e/tsconfig.json"
|
||||
],
|
||||
"exclude": ["**/node_modules/**"]
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "app:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "app:serve:production"
|
||||
},
|
||||
"ci": {
|
||||
"devServerTarget": "app:serve:ci"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ionic-cordova-build": {
|
||||
"builder": "@ionic/angular-toolkit:cordova-build",
|
||||
"options": {
|
||||
"browserTarget": "app:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "app:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ionic-cordova-serve": {
|
||||
"builder": "@ionic/angular-toolkit:cordova-serve",
|
||||
"options": {
|
||||
"cordovaBuildTarget": "app:ionic-cordova-build",
|
||||
"devServerTarget": "app:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"cordovaBuildTarget": "app:ionic-cordova-build:production",
|
||||
"devServerTarget": "app:serve:production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"defaultCollection": "@ionic/angular-toolkit"
|
||||
},
|
||||
"schematics": {
|
||||
"@ionic/angular-toolkit:component": {
|
||||
"styleext": "scss"
|
||||
},
|
||||
"@ionic/angular-toolkit:page": {
|
||||
"styleext": "scss"
|
||||
}
|
||||
}
|
||||
}
|
||||
12
packages/now-static-build/test/fixtures/52-ionic-angular/browserslist
vendored
Normal file
12
packages/now-static-build/test/fixtures/52-ionic-angular/browserslist
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
> 0.5%
|
||||
last 2 versions
|
||||
Firefox ESR
|
||||
not dead
|
||||
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
||||
88
packages/now-static-build/test/fixtures/52-ionic-angular/config.xml
vendored
Normal file
88
packages/now-static-build/test/fixtures/52-ionic-angular/config.xml
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<widget id="com.ionicframework.conferenceapp" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||
<name>Ionic Conference App</name>
|
||||
<description>Schedule, speakers, venue.</description>
|
||||
<author email="hi@ionicframework" href="http://ionicframework.com/">Ionic Framework Team</author>
|
||||
<content src="index.html" />
|
||||
<access origin="*" />
|
||||
<allow-intent href="http://*/*" />
|
||||
<allow-intent href="https://*/*" />
|
||||
<allow-intent href="tel:*" />
|
||||
<allow-intent href="sms:*" />
|
||||
<allow-intent href="mailto:*" />
|
||||
<allow-intent href="geo:*" />
|
||||
<preference name="ScrollEnabled" value="false" />
|
||||
<preference name="android-minSdkVersion" value="19" />
|
||||
<preference name="BackupWebStorage" value="none" />
|
||||
<preference name="SplashMaintainAspectRatio" value="true" />
|
||||
<preference name="FadeSplashScreenDuration" value="300" />
|
||||
<preference name="SplashShowOnlyFirstTime" value="false" />
|
||||
<preference name="SplashScreen" value="screen" />
|
||||
<preference name="SplashScreenDelay" value="3000" />
|
||||
<platform name="android">
|
||||
<allow-intent href="market:*" />
|
||||
<icon density="ldpi" src="resources/android/icon/drawable-ldpi-icon.png" />
|
||||
<icon density="mdpi" src="resources/android/icon/drawable-mdpi-icon.png" />
|
||||
<icon density="hdpi" src="resources/android/icon/drawable-hdpi-icon.png" />
|
||||
<icon density="xhdpi" src="resources/android/icon/drawable-xhdpi-icon.png" />
|
||||
<icon density="xxhdpi" src="resources/android/icon/drawable-xxhdpi-icon.png" />
|
||||
<icon density="xxxhdpi" src="resources/android/icon/drawable-xxxhdpi-icon.png" />
|
||||
<splash density="land-ldpi" src="resources/android/splash/drawable-land-ldpi-screen.png" />
|
||||
<splash density="land-mdpi" src="resources/android/splash/drawable-land-mdpi-screen.png" />
|
||||
<splash density="land-hdpi" src="resources/android/splash/drawable-land-hdpi-screen.png" />
|
||||
<splash density="land-xhdpi" src="resources/android/splash/drawable-land-xhdpi-screen.png" />
|
||||
<splash density="land-xxhdpi" src="resources/android/splash/drawable-land-xxhdpi-screen.png" />
|
||||
<splash density="land-xxxhdpi" src="resources/android/splash/drawable-land-xxxhdpi-screen.png" />
|
||||
<splash density="port-ldpi" src="resources/android/splash/drawable-port-ldpi-screen.png" />
|
||||
<splash density="port-mdpi" src="resources/android/splash/drawable-port-mdpi-screen.png" />
|
||||
<splash density="port-hdpi" src="resources/android/splash/drawable-port-hdpi-screen.png" />
|
||||
<splash density="port-xhdpi" src="resources/android/splash/drawable-port-xhdpi-screen.png" />
|
||||
<splash density="port-xxhdpi" src="resources/android/splash/drawable-port-xxhdpi-screen.png" />
|
||||
<splash density="port-xxxhdpi" src="resources/android/splash/drawable-port-xxxhdpi-screen.png" />
|
||||
</platform>
|
||||
<platform name="ios">
|
||||
<allow-intent href="itms:*" />
|
||||
<allow-intent href="itms-apps:*" />
|
||||
<icon height="57" src="resources/ios/icon/icon.png" width="57" />
|
||||
<icon height="114" src="resources/ios/icon/icon@2x.png" width="114" />
|
||||
<icon height="40" src="resources/ios/icon/icon-40.png" width="40" />
|
||||
<icon height="80" src="resources/ios/icon/icon-40@2x.png" width="80" />
|
||||
<icon height="120" src="resources/ios/icon/icon-40@3x.png" width="120" />
|
||||
<icon height="50" src="resources/ios/icon/icon-50.png" width="50" />
|
||||
<icon height="100" src="resources/ios/icon/icon-50@2x.png" width="100" />
|
||||
<icon height="60" src="resources/ios/icon/icon-60.png" width="60" />
|
||||
<icon height="120" src="resources/ios/icon/icon-60@2x.png" width="120" />
|
||||
<icon height="180" src="resources/ios/icon/icon-60@3x.png" width="180" />
|
||||
<icon height="72" src="resources/ios/icon/icon-72.png" width="72" />
|
||||
<icon height="144" src="resources/ios/icon/icon-72@2x.png" width="144" />
|
||||
<icon height="76" src="resources/ios/icon/icon-76.png" width="76" />
|
||||
<icon height="152" src="resources/ios/icon/icon-76@2x.png" width="152" />
|
||||
<icon height="167" src="resources/ios/icon/icon-83.5@2x.png" width="167" />
|
||||
<icon height="29" src="resources/ios/icon/icon-small.png" width="29" />
|
||||
<icon height="58" src="resources/ios/icon/icon-small@2x.png" width="58" />
|
||||
<icon height="87" src="resources/ios/icon/icon-small@3x.png" width="87" />
|
||||
<icon height="1024" src="resources/ios/icon/icon-1024.png" width="1024" />
|
||||
<splash height="1136" src="resources/ios/splash/Default-568h@2x~iphone.png" width="640" />
|
||||
<splash height="1334" src="resources/ios/splash/Default-667h.png" width="750" />
|
||||
<splash height="2208" src="resources/ios/splash/Default-736h.png" width="1242" />
|
||||
<splash height="1242" src="resources/ios/splash/Default-Landscape-736h.png" width="2208" />
|
||||
<splash height="1536" src="resources/ios/splash/Default-Landscape@2x~ipad.png" width="2048" />
|
||||
<splash height="2048" src="resources/ios/splash/Default-Landscape@~ipadpro.png" width="2732" />
|
||||
<splash height="768" src="resources/ios/splash/Default-Landscape~ipad.png" width="1024" />
|
||||
<splash height="2048" src="resources/ios/splash/Default-Portrait@2x~ipad.png" width="1536" />
|
||||
<splash height="2732" src="resources/ios/splash/Default-Portrait@~ipadpro.png" width="2048" />
|
||||
<splash height="1024" src="resources/ios/splash/Default-Portrait~ipad.png" width="768" />
|
||||
<splash height="960" src="resources/ios/splash/Default@2x~iphone.png" width="640" />
|
||||
<splash height="480" src="resources/ios/splash/Default~iphone.png" width="320" />
|
||||
<splash height="2732" src="resources/ios/splash/Default@2x~universal~anyany.png" width="2732" />
|
||||
</platform>
|
||||
<plugin name="cordova-plugin-device" spec="^2.0.2" />
|
||||
<plugin name="cordova-plugin-inappbrowser" spec="^3.0.0" />
|
||||
<plugin name="cordova-plugin-splashscreen" spec="^5.0.2" />
|
||||
<plugin name="cordova-plugin-whitelist" spec="^1.3.2" />
|
||||
<plugin name="cordova-plugin-ionic-webview" spec="2.0.0-beta.1" />
|
||||
<plugin name="cordova-plugin-statusbar" spec="^2.4.2" />
|
||||
<plugin name="cordova-plugin-ionic-keyboard" spec="^2.1.2" />
|
||||
<engine name="ios" spec="^4.5.5" />
|
||||
<engine name="android" spec="7.0.0" />
|
||||
</widget>
|
||||
11
packages/now-static-build/test/fixtures/52-ionic-angular/e2e/protractor-ci.conf.js
vendored
Normal file
11
packages/now-static-build/test/fixtures/52-ionic-angular/e2e/protractor-ci.conf.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
const config = require('./protractor.conf').config;
|
||||
|
||||
config.capabilities = {
|
||||
browserName: 'chrome',
|
||||
chromeOptions: {
|
||||
args: ['--headless', '--no-sandbox', '--disable-gpu'],
|
||||
binary: require('puppeteer').executablePath(),
|
||||
},
|
||||
};
|
||||
|
||||
exports.config = config;
|
||||
28
packages/now-static-build/test/fixtures/52-ionic-angular/e2e/protractor.conf.js
vendored
Normal file
28
packages/now-static-build/test/fixtures/52-ionic-angular/e2e/protractor.conf.js
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: ['./src/**/*.e2e-spec.ts'],
|
||||
capabilities: {
|
||||
browserName: 'chrome',
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {},
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: require('path').join(__dirname, './tsconfig.json'),
|
||||
});
|
||||
jasmine
|
||||
.getEnv()
|
||||
.addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
},
|
||||
};
|
||||
24
packages/now-static-build/test/fixtures/52-ionic-angular/e2e/src/app.e2e-spec.ts
vendored
Normal file
24
packages/now-static-build/test/fixtures/52-ionic-angular/e2e/src/app.e2e-spec.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import { AppPage } from './app.po';
|
||||
|
||||
describe('new App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display the menu', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getMenu()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should get the slides text', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getFirstSlide()).toBe('ion-slide');
|
||||
});
|
||||
|
||||
it('should create a router outlet', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getRouter()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
39
packages/now-static-build/test/fixtures/52-ionic-angular/e2e/src/app.po.ts
vendored
Normal file
39
packages/now-static-build/test/fixtures/52-ionic-angular/e2e/src/app.po.ts
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
browser,
|
||||
by,
|
||||
element,
|
||||
ElementFinder,
|
||||
ExpectedConditions,
|
||||
} from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo() {
|
||||
return browser.get('/');
|
||||
}
|
||||
|
||||
async getMenu() {
|
||||
const el = this.getElement('app-root ion-menu');
|
||||
await this.waitForSelector(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
async getFirstSlide() {
|
||||
const el = this.getElement('app-root ion-slides ion-slide:first-child');
|
||||
await this.waitForSelector(el);
|
||||
return el.getTagName();
|
||||
}
|
||||
|
||||
async getRouter() {
|
||||
const el = this.getElement('app-root ion-router-outlet');
|
||||
await this.waitForSelector(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
async waitForSelector(el: ElementFinder) {
|
||||
return browser.wait(ExpectedConditions.presenceOf(el), 3000);
|
||||
}
|
||||
|
||||
getElement(selector) {
|
||||
return element(by.css(selector));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user