mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-30 03:39:11 +00:00
Compare commits
23 Commits
@vercel/ne
...
loopless-3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc49bc335b | ||
|
|
58d3502f5b | ||
|
|
29775f8e05 | ||
|
|
9f05a1865c | ||
|
|
8d1afc026f | ||
|
|
130f36aad6 | ||
|
|
dd87c9b0c6 | ||
|
|
f813b3340b | ||
|
|
976b02e895 | ||
|
|
843be9658c | ||
|
|
ad501a4cd0 | ||
|
|
727ae587db | ||
|
|
536b15079b | ||
|
|
887882697b | ||
|
|
e2db7c7734 | ||
|
|
022504787c | ||
|
|
0f424de406 | ||
|
|
4819c3ac61 | ||
|
|
c28ca7ef2d | ||
|
|
068ea00615 | ||
|
|
7f8145ab40 | ||
|
|
1a12715096 | ||
|
|
5b6d565360 |
@@ -19,11 +19,6 @@ indent_style = space
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
|
||||
[*.asm]
|
||||
indent_size = 8
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||

|
||||
|
||||
This is a [Blitz.js](https://blitzjs.com/) project bootstrapped with `blitz new`.
|
||||
This is a [Blitz.js](https://blitzjs.com/) project bootstrapped with `npx blitz new`.
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"demo": "https://blitzjs.now-examples.now.sh",
|
||||
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/blitz.svg",
|
||||
"tagline": "Blitz.js: The Fullstack React Framework",
|
||||
"description": "A Blitz.js app, created with the `blitz new` command.",
|
||||
"description": "A brand new Blitz.js app - the result of running `npx blitz new`.",
|
||||
"website": "https://blitzjs.com",
|
||||
"detectors": {
|
||||
"every": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/frameworks",
|
||||
"version": "0.0.17-canary.1",
|
||||
"version": "0.0.17",
|
||||
"main": "frameworks.json",
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
out="dist"
|
||||
|
||||
rm -rf "$out"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "2.4.1",
|
||||
"version": "2.4.2",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
@@ -8,6 +8,8 @@ import { isOfficialRuntime } from './';
|
||||
interface ErrorResponse {
|
||||
code: string;
|
||||
message: string;
|
||||
action?: string;
|
||||
link?: string;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
@@ -608,20 +610,22 @@ function checkUnusedFunctions(
|
||||
} else {
|
||||
return {
|
||||
code: 'unused_function',
|
||||
message: `The function for ${fnKey} can't be handled by any builder`,
|
||||
message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions.`,
|
||||
action: 'Learn More',
|
||||
link: 'https://vercel.link/unmatched-function-pattern',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unusedFunctions.size) {
|
||||
const [unusedFunction] = Array.from(unusedFunctions);
|
||||
const [fnKey] = Array.from(unusedFunctions);
|
||||
|
||||
return {
|
||||
code: 'unused_function',
|
||||
message:
|
||||
`The function for ${unusedFunction} can't be handled by any builder. ` +
|
||||
`Make sure it is inside the api/ directory.`,
|
||||
message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
|
||||
action: 'Learn More',
|
||||
link: 'https://vercel.link/unmatched-function-pattern',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -349,20 +349,35 @@ export async function runPipInstall(
|
||||
);
|
||||
}
|
||||
|
||||
export function getScriptName(
|
||||
pkg: Pick<PackageJson, 'scripts'> | null | undefined,
|
||||
possibleNames: Iterable<string>
|
||||
): string | null {
|
||||
if (pkg && pkg.scripts) {
|
||||
for (const name of possibleNames) {
|
||||
if (name in pkg.scripts) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function runPackageJsonScript(
|
||||
destPath: string,
|
||||
scriptName: string,
|
||||
scriptNames: string | Iterable<string>,
|
||||
spawnOpts?: SpawnOptions
|
||||
) {
|
||||
assert(path.isAbsolute(destPath));
|
||||
const { packageJson, cliType } = await scanParentDirs(destPath, true);
|
||||
const hasScript = Boolean(
|
||||
packageJson &&
|
||||
packageJson.scripts &&
|
||||
scriptName &&
|
||||
packageJson.scripts[scriptName]
|
||||
const scriptName = getScriptName(
|
||||
packageJson,
|
||||
typeof scriptNames === 'string' ? [scriptNames] : scriptNames
|
||||
);
|
||||
if (!hasScript) return false;
|
||||
if (!scriptName) return false;
|
||||
|
||||
debug('Running user script...');
|
||||
const runScriptTime = Date.now();
|
||||
|
||||
if (cliType === 'npm') {
|
||||
const prettyCommand = `npm run ${scriptName}`;
|
||||
@@ -382,6 +397,7 @@ export async function runPackageJsonScript(
|
||||
});
|
||||
}
|
||||
|
||||
debug(`Script complete [${Date.now() - runScriptTime}ms]`);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
execCommand,
|
||||
spawnCommand,
|
||||
walkParentDirs,
|
||||
getScriptName,
|
||||
installDependencies,
|
||||
runPackageJsonScript,
|
||||
runNpmInstall,
|
||||
@@ -47,6 +48,7 @@ export {
|
||||
rename,
|
||||
execAsync,
|
||||
spawnAsync,
|
||||
getScriptName,
|
||||
installDependencies,
|
||||
runPackageJsonScript,
|
||||
execCommand,
|
||||
|
||||
@@ -775,8 +775,9 @@ describe('Test `detectBuilders`', () => {
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
code: 'unused_function',
|
||||
message:
|
||||
"The function for server/**/*.ts can't be handled by any builder. Make sure it is inside the api/ directory.",
|
||||
message: `The pattern "server/**/*.ts" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
|
||||
action: 'Learn More',
|
||||
link: 'https://vercel.link/unmatched-function-pattern',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -1878,8 +1879,9 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
code: 'unused_function',
|
||||
message:
|
||||
"The function for server/**/*.ts can't be handled by any builder. Make sure it is inside the api/ directory.",
|
||||
message: `The pattern "server/**/*.ts" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
|
||||
action: 'Learn More',
|
||||
link: 'https://vercel.link/unmatched-function-pattern',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
72
packages/now-build-utils/test/unit.get-script-name.test.ts
vendored
Normal file
72
packages/now-build-utils/test/unit.get-script-name.test.ts
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
import assert from 'assert';
|
||||
import { getScriptName } from '../src';
|
||||
|
||||
describe('Test `getScriptName()`', () => {
|
||||
it('should return "vercel-*"', () => {
|
||||
const pkg = {
|
||||
scripts: {
|
||||
'vercel-dev': '',
|
||||
'vercel-build': '',
|
||||
dev: '',
|
||||
build: '',
|
||||
},
|
||||
};
|
||||
assert.equal(
|
||||
getScriptName(pkg, ['vercel-dev', 'now-dev', 'dev']),
|
||||
'vercel-dev'
|
||||
);
|
||||
assert.equal(
|
||||
getScriptName(pkg, ['vercel-build', 'now-build', 'build']),
|
||||
'vercel-build'
|
||||
);
|
||||
assert.equal(getScriptName(pkg, ['dev']), 'dev');
|
||||
assert.equal(getScriptName(pkg, ['build']), 'build');
|
||||
});
|
||||
|
||||
it('should return "now-*"', () => {
|
||||
const pkg = {
|
||||
scripts: {
|
||||
'now-dev': '',
|
||||
'now-build': '',
|
||||
dev: '',
|
||||
build: '',
|
||||
},
|
||||
};
|
||||
assert.equal(
|
||||
getScriptName(pkg, ['vercel-dev', 'now-dev', 'dev']),
|
||||
'now-dev'
|
||||
);
|
||||
assert.equal(
|
||||
getScriptName(pkg, ['vercel-build', 'now-build', 'build']),
|
||||
'now-build'
|
||||
);
|
||||
assert.equal(getScriptName(pkg, ['dev']), 'dev');
|
||||
assert.equal(getScriptName(pkg, ['build']), 'build');
|
||||
});
|
||||
|
||||
it('should return base script name', () => {
|
||||
const pkg = {
|
||||
scripts: {
|
||||
dev: '',
|
||||
build: '',
|
||||
},
|
||||
};
|
||||
assert.equal(getScriptName(pkg, ['dev']), 'dev');
|
||||
assert.equal(getScriptName(pkg, ['build']), 'build');
|
||||
});
|
||||
|
||||
it('should return `null`', () => {
|
||||
assert.equal(getScriptName(undefined, ['build']), null);
|
||||
assert.equal(getScriptName({}, ['build']), null);
|
||||
assert.equal(getScriptName({ scripts: {} }, ['build']), null);
|
||||
|
||||
const pkg = {
|
||||
scripts: {
|
||||
dev: '',
|
||||
build: '',
|
||||
},
|
||||
};
|
||||
assert.equal(getScriptName(pkg, ['vercel-dev', 'now-dev']), null);
|
||||
assert.equal(getScriptName(pkg, ['vercel-build', 'now-build']), null);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "19.1.3-canary.1",
|
||||
"version": "19.2.0",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -62,13 +62,14 @@
|
||||
"node": ">= 10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.4.1",
|
||||
"@vercel/go": "1.1.4-canary.0",
|
||||
"@vercel/next": "2.6.12",
|
||||
"@vercel/node": "1.7.2",
|
||||
"@vercel/build-utils": "2.4.2",
|
||||
"@vercel/go": "1.1.4",
|
||||
"@vercel/next": "2.6.13",
|
||||
"@vercel/node": "1.7.3",
|
||||
"@vercel/python": "1.2.2",
|
||||
"@vercel/ruby": "1.2.3",
|
||||
"@vercel/static-build": "0.17.6-canary.1"
|
||||
"@vercel/static-build": "0.17.6",
|
||||
"update-notifier": "4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/node": "5.5.0",
|
||||
@@ -185,7 +186,6 @@
|
||||
"ts-node": "8.3.0",
|
||||
"typescript": "3.9.3",
|
||||
"universal-analytics": "0.4.20",
|
||||
"update-check": "1.5.3",
|
||||
"utility-types": "2.1.0",
|
||||
"which": "2.0.2",
|
||||
"which-promise": "1.0.0",
|
||||
|
||||
@@ -49,7 +49,13 @@ async function main() {
|
||||
// Do the initial `ncc` build
|
||||
console.log();
|
||||
const src = join(dirRoot, 'src');
|
||||
const args = ['@zeit/ncc', 'build', '--source-map'];
|
||||
const args = [
|
||||
'@zeit/ncc',
|
||||
'build',
|
||||
'--source-map',
|
||||
'--external',
|
||||
'update-notifier',
|
||||
];
|
||||
if (!isDev) {
|
||||
args.push('--minify');
|
||||
}
|
||||
@@ -86,7 +92,7 @@ async function main() {
|
||||
// A bunch of source `.ts` files from CLI's `util` directory
|
||||
await remove(join(dirRoot, 'dist', 'util'));
|
||||
|
||||
console.log('Finished building `now-cli`');
|
||||
console.log('Finished building Vercel CLI');
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
|
||||
|
||||
@@ -9,7 +9,9 @@ import { getLinkedProject } from '../../util/projects/link';
|
||||
import { getFrameworks } from '../../util/get-frameworks';
|
||||
import { isSettingValue } from '../../util/is-setting-value';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { ProjectSettings } from '../../types';
|
||||
import { ProjectSettings, ProjectEnvTarget } from '../../types';
|
||||
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
||||
import { Env } from '@vercel/build-utils';
|
||||
|
||||
type Options = {
|
||||
'--debug'?: boolean;
|
||||
@@ -54,6 +56,7 @@ export default async function dev(
|
||||
let devCommand: string | undefined;
|
||||
let frameworkSlug: string | undefined;
|
||||
let projectSettings: ProjectSettings | undefined;
|
||||
let environmentVars: Env | undefined;
|
||||
if (link.status === 'linked') {
|
||||
const { project, org } = link;
|
||||
client.currentTeam = org.type === 'team' ? org.id : undefined;
|
||||
@@ -80,6 +83,13 @@ export default async function dev(
|
||||
if (project.rootDirectory) {
|
||||
cwd = join(cwd, project.rootDirectory);
|
||||
}
|
||||
|
||||
environmentVars = await getDecryptedEnvRecords(
|
||||
output,
|
||||
client,
|
||||
project,
|
||||
ProjectEnvTarget.Development
|
||||
);
|
||||
}
|
||||
|
||||
const devServer = new DevServer(cwd, {
|
||||
@@ -88,6 +98,7 @@ export default async function dev(
|
||||
devCommand,
|
||||
frameworkSlug,
|
||||
projectSettings,
|
||||
environmentVars,
|
||||
});
|
||||
|
||||
process.once('SIGINT', () => devServer.stop());
|
||||
|
||||
60
packages/now-cli/src/commands/env/pull.ts
vendored
60
packages/now-cli/src/commands/env/pull.ts
vendored
@@ -4,8 +4,7 @@ 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 getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
||||
import param from '../../util/output/param';
|
||||
import withSpinner from '../../util/with-spinner';
|
||||
import { join } from 'path';
|
||||
@@ -13,6 +12,7 @@ import { promises, openSync, closeSync, readSync } from 'fs';
|
||||
import { emoji, prependEmoji } from '../../util/emoji';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
const { writeFile } = promises;
|
||||
import { Env } from '@vercel/build-utils';
|
||||
|
||||
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
|
||||
|
||||
@@ -84,45 +84,21 @@ export default async function pull(
|
||||
);
|
||||
const pullStamp = stamp();
|
||||
|
||||
const records = await withSpinner('Downloading', async () => {
|
||||
const dev = ProjectEnvTarget.Development;
|
||||
const envs = await getEnvVariables(output, client, project.id, 4, dev);
|
||||
const decryptedValues = await Promise.all(
|
||||
envs.map(async env => {
|
||||
try {
|
||||
const value = await getDecryptedSecret(output, client, env.value);
|
||||
return { value, found: true };
|
||||
} catch (error) {
|
||||
if (error && error.status === 404) {
|
||||
return { value: '', found: false };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
);
|
||||
const results: { key: string; value: string; found: boolean }[] = [];
|
||||
for (let i = 0; i < decryptedValues.length; i++) {
|
||||
const { key } = envs[i];
|
||||
const { value, found } = decryptedValues[i];
|
||||
results.push({ key, value, found });
|
||||
}
|
||||
return results;
|
||||
});
|
||||
const records: Env = await withSpinner(
|
||||
'Downloading',
|
||||
async () =>
|
||||
await getDecryptedEnvRecords(
|
||||
output,
|
||||
client,
|
||||
project,
|
||||
ProjectEnvTarget.Development
|
||||
)
|
||||
);
|
||||
|
||||
const contents =
|
||||
CONTENTS_PREFIX +
|
||||
records
|
||||
.filter(obj => {
|
||||
if (!obj.found) {
|
||||
output.print('');
|
||||
output.warn(
|
||||
`Unable to download variable ${obj.key} because associated secret was deleted`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(({ key, value }) => `${key}="${escapeValue(value)}"`)
|
||||
Object.entries(records)
|
||||
.map(([key, value]) => `${key}="${escapeValue(value)}"`)
|
||||
.join('\n') +
|
||||
'\n';
|
||||
|
||||
@@ -139,8 +115,10 @@ export default async function pull(
|
||||
return 0;
|
||||
}
|
||||
|
||||
function escapeValue(value: string) {
|
||||
function escapeValue(value: string | undefined) {
|
||||
return value
|
||||
.replace(new RegExp('\n', 'g'), '\\n') // combine newlines (unix) into one line
|
||||
.replace(new RegExp('\r', 'g'), '\\r'); // combine newlines (windows) into one line
|
||||
? value
|
||||
.replace(new RegExp('\n', 'g'), '\\n') // combine newlines (unix) into one line
|
||||
.replace(new RegExp('\r', 'g'), '\\r') // combine newlines (windows) into one line
|
||||
: '';
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@ import sourceMap from '@zeit/source-map-support';
|
||||
import { mkdirp } from 'fs-extra';
|
||||
import chalk from 'chalk';
|
||||
import epipebomb from 'epipebomb';
|
||||
import checkForUpdate from 'update-check';
|
||||
import ms from 'ms';
|
||||
import updateNotifier from 'update-notifier';
|
||||
import { URL } from 'url';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { NowBuildError } from '@vercel/build-utils';
|
||||
@@ -52,6 +51,14 @@ import getUpdateCommand from './util/get-update-command';
|
||||
import { metrics, shouldCollectMetrics } from './util/metrics.ts';
|
||||
import { getCommandName, getTitleName } from './util/pkg-name.ts';
|
||||
|
||||
const isCanary = pkg.version.includes('canary');
|
||||
|
||||
// Checks for available update and returns an instance
|
||||
const notifier = updateNotifier({
|
||||
pkg,
|
||||
distTag: isCanary ? 'canary' : 'latest',
|
||||
});
|
||||
|
||||
const VERCEL_DIR = getGlobalPathConfig();
|
||||
const VERCEL_CONFIG_PATH = configFiles.getConfigFilePath();
|
||||
const VERCEL_AUTH_CONFIG_PATH = configFiles.getAuthConfigFilePath();
|
||||
@@ -66,7 +73,7 @@ sourceMap.install();
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
release: `vercel-cli@${pkg.version}`,
|
||||
environment: pkg.version.includes('canary') ? 'canary' : 'stable',
|
||||
environment: isCanary ? 'canary' : 'stable',
|
||||
});
|
||||
|
||||
let debug = () => {};
|
||||
@@ -128,38 +135,20 @@ const main = async argv_ => {
|
||||
// (as in: `vercel ls`)
|
||||
const targetOrSubcommand = argv._[2];
|
||||
|
||||
let update = null;
|
||||
|
||||
try {
|
||||
if (targetOrSubcommand !== 'update') {
|
||||
update = await checkForUpdate(pkg, {
|
||||
interval: ms('1d'),
|
||||
distTag: pkg.version.includes('canary') ? 'canary' : 'latest',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
error(`Checking for updates failed${isDebugging ? ':' : ''}`)
|
||||
);
|
||||
|
||||
if (isDebugging) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (update && isTTY) {
|
||||
if (notifier.update && isTTY) {
|
||||
const { latest } = notifier.update;
|
||||
console.log(
|
||||
info(
|
||||
`${chalk.bgRed('UPDATE AVAILABLE')} ` +
|
||||
`Run ${cmd(
|
||||
await getUpdateCommand()
|
||||
)} to install ${getTitleName()} CLI ${update.latest}`
|
||||
)} to install ${getTitleName()} CLI ${latest}`
|
||||
)
|
||||
);
|
||||
|
||||
console.log(
|
||||
info(
|
||||
`Changelog: https://github.com/vercel/vercel/releases/tag/vercel@${update.latest}`
|
||||
`Changelog: https://github.com/vercel/vercel/releases/tag/vercel@${latest}`
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -169,7 +158,7 @@ const main = async argv_ => {
|
||||
`${getTitleName()} CLI ${pkg.version}${
|
||||
targetOrSubcommand === 'dev' ? ' dev (beta)' : ''
|
||||
}${
|
||||
pkg.version.includes('canary') || targetOrSubcommand === 'dev'
|
||||
isCanary || targetOrSubcommand === 'dev'
|
||||
? ' — https://vercel.com/feedback'
|
||||
: ''
|
||||
}`
|
||||
@@ -191,9 +180,7 @@ const main = async argv_ => {
|
||||
} catch (err) {
|
||||
console.error(
|
||||
error(
|
||||
`An unexpected error occurred while trying to find the global directory: ${
|
||||
err.message
|
||||
}`
|
||||
`An unexpected error occurred while trying to find the global directory: ${err.message}`
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -67,8 +67,6 @@ async function createBuildProcess(
|
||||
...process.env,
|
||||
PATH,
|
||||
...envConfigs.allEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
VERCEL_REGION: 'dev1',
|
||||
};
|
||||
|
||||
const buildProcess = fork(builderWorkerPath, [], {
|
||||
@@ -365,8 +363,6 @@ export async function executeBuild(
|
||||
...nowConfig.env,
|
||||
...asset.environment,
|
||||
...envConfigs.runEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
VERCEL_REGION: 'dev1',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -140,12 +140,16 @@ export default class DevServer {
|
||||
private blockingBuildsPromise: Promise<void> | null;
|
||||
private updateBuildersPromise: Promise<void> | null;
|
||||
private updateBuildersTimeout: NodeJS.Timeout | undefined;
|
||||
private startPromise: Promise<void> | null;
|
||||
|
||||
private environmentVars: Env | undefined;
|
||||
|
||||
constructor(cwd: string, options: DevServerOptions) {
|
||||
this.cwd = cwd;
|
||||
this.debug = options.debug;
|
||||
this.output = options.output;
|
||||
this.envConfigs = { buildEnv: {}, runEnv: {}, allEnv: {} };
|
||||
this.environmentVars = options.environmentVars;
|
||||
this.files = {};
|
||||
this.address = '';
|
||||
this.devCommand = options.devCommand;
|
||||
@@ -169,12 +173,13 @@ export default class DevServer {
|
||||
this.getNowConfigPromise = null;
|
||||
this.blockingBuildsPromise = null;
|
||||
this.updateBuildersPromise = null;
|
||||
this.startPromise = null;
|
||||
|
||||
this.watchAggregationId = null;
|
||||
this.watchAggregationEvents = [];
|
||||
this.watchAggregationTimeout = 500;
|
||||
|
||||
this.filter = (path) => Boolean(path);
|
||||
this.filter = path => Boolean(path);
|
||||
this.podId = Math.random().toString(32).slice(-5);
|
||||
|
||||
this.devServerPids = new Set();
|
||||
@@ -213,8 +218,8 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
|
||||
events = events.filter((event) =>
|
||||
distPaths.every((distPath) => !event.path.startsWith(distPath))
|
||||
events = events.filter(event =>
|
||||
distPaths.every(distPath => !event.path.startsWith(distPath))
|
||||
);
|
||||
|
||||
// First, update the `files` mapping of source files
|
||||
@@ -370,7 +375,7 @@ export default class DevServer {
|
||||
this,
|
||||
fileList
|
||||
);
|
||||
const sources = matches.map((m) => m.src);
|
||||
const sources = matches.map(m => m.src);
|
||||
|
||||
if (isInitial && fileList.length === 0) {
|
||||
this.output.warn('There are no files inside your deployment.');
|
||||
@@ -479,13 +484,16 @@ export default class DevServer {
|
||||
const dotenv = await fs.readFile(filePath, 'utf8');
|
||||
this.output.debug(`Using local env: ${filePath}`);
|
||||
env = parseDotenv(dotenv);
|
||||
env = this.populateVercelEnvVars(env);
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
try {
|
||||
return this.validateEnvConfig(fileName, base || {}, env);
|
||||
return {
|
||||
...this.validateEnvConfig(fileName, base || {}, env),
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof MissingDotenvVarsError) {
|
||||
this.output.error(err.message);
|
||||
@@ -543,7 +551,7 @@ export default class DevServer {
|
||||
const { projectSettings, cleanUrls, trailingSlash } = config;
|
||||
|
||||
const opts = { output: this.output, isBuilds: true };
|
||||
const files = (await getFiles(this.cwd, config, opts)).map((f) =>
|
||||
const files = (await getFiles(this.cwd, config, opts)).map(f =>
|
||||
relative(this.cwd, f)
|
||||
);
|
||||
|
||||
@@ -570,7 +578,7 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
if (warnings && warnings.length > 0) {
|
||||
warnings.forEach((warning) => this.output.warn(warning.message));
|
||||
warnings.forEach(warning => this.output.warn(warning.message));
|
||||
}
|
||||
|
||||
if (builders) {
|
||||
@@ -620,13 +628,20 @@ export default class DevServer {
|
||||
this.apiExtensions = detectApiExtensions(config.builds || []);
|
||||
|
||||
// Update the env vars configuration
|
||||
const [runEnv, buildEnv] = await Promise.all([
|
||||
let [runEnv, buildEnv] = await Promise.all([
|
||||
this.getLocalEnv('.env', config.env),
|
||||
this.getLocalEnv('.env.build', config.build?.env),
|
||||
]);
|
||||
const allEnv = { ...buildEnv, ...runEnv };
|
||||
this.envConfigs = { buildEnv, runEnv, allEnv };
|
||||
|
||||
let allEnv = { ...buildEnv, ...runEnv };
|
||||
|
||||
// If no .env/.build.env is present, fetch and use cloud environment variables
|
||||
if (Object.keys(allEnv).length === 0) {
|
||||
const cloudEnv = this.populateVercelEnvVars(this.environmentVars);
|
||||
allEnv = runEnv = buildEnv = cloudEnv;
|
||||
}
|
||||
|
||||
this.envConfigs = { buildEnv, runEnv, allEnv };
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -731,6 +746,26 @@ export default class DevServer {
|
||||
return merged;
|
||||
}
|
||||
|
||||
populateVercelEnvVars(env: Env | undefined): Env {
|
||||
if (!env) {
|
||||
return {};
|
||||
}
|
||||
|
||||
for (const name of Object.keys(env)) {
|
||||
if (name === 'VERCEL_URL') {
|
||||
const host = new URL(this.address).host;
|
||||
env['VERCEL_URL'] = host;
|
||||
} else if (name === 'VERCEL_REGION') {
|
||||
env['VERCEL_REGION'] = 'dev1';
|
||||
}
|
||||
}
|
||||
|
||||
// Always set NOW_REGION to match production
|
||||
env['NOW_REGION'] = 'dev1';
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of from builder inputs
|
||||
* and filter them
|
||||
@@ -739,10 +774,20 @@ export default class DevServer {
|
||||
return Object.keys(files).filter(this.filter);
|
||||
}
|
||||
|
||||
start(...listenSpec: ListenSpec): Promise<void> {
|
||||
if (!this.startPromise) {
|
||||
this.startPromise = this._start(...listenSpec).catch(err => {
|
||||
this.stop();
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
return this.startPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the `vercel dev` server.
|
||||
*/
|
||||
async start(...listenSpec: ListenSpec): Promise<void> {
|
||||
async _start(...listenSpec: ListenSpec): Promise<void> {
|
||||
if (!fs.existsSync(this.cwd)) {
|
||||
throw new Error(`${chalk.bold(this.cwd)} doesn't exist`);
|
||||
}
|
||||
@@ -754,7 +799,39 @@ export default class DevServer {
|
||||
const { ig } = await getVercelIgnore(this.cwd);
|
||||
this.filter = ig.createFilter();
|
||||
|
||||
let address: string | null = null;
|
||||
while (typeof address !== 'string') {
|
||||
try {
|
||||
address = await listen(this.server, ...listenSpec);
|
||||
} catch (err) {
|
||||
this.output.debug(`Got listen error: ${err.code}`);
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
if (typeof listenSpec[0] === 'number') {
|
||||
// Increase port and try again
|
||||
this.output.note(
|
||||
`Requested port ${chalk.yellow(
|
||||
String(listenSpec[0])
|
||||
)} is already in use`
|
||||
);
|
||||
listenSpec[0]++;
|
||||
} else {
|
||||
this.output.error(
|
||||
`Requested socket ${chalk.cyan(listenSpec[0])} is already in use`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.address = address
|
||||
.replace('[::]', 'localhost')
|
||||
.replace('127.0.0.1', 'localhost');
|
||||
|
||||
const nowConfig = await this.getNowConfig();
|
||||
const devCommandPromise = this.runDevCommand();
|
||||
|
||||
const opts = { output: this.output, isBuilds: true };
|
||||
const files = await getFiles(this.cwd, nowConfig, opts);
|
||||
@@ -782,11 +859,11 @@ export default class DevServer {
|
||||
// get their "build matches" invalidated so that the new version is used.
|
||||
this.updateBuildersTimeout = setTimeout(() => {
|
||||
this.updateBuildersPromise = updateBuilders(builders, this.output)
|
||||
.then((updatedBuilders) => {
|
||||
.then(updatedBuilders => {
|
||||
this.updateBuildersPromise = null;
|
||||
this.invalidateBuildMatches(nowConfig, updatedBuilders);
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
this.updateBuildersPromise = null;
|
||||
this.output.error(`Failed to update builders: ${err.message}`);
|
||||
this.output.debug(err.stack);
|
||||
@@ -846,39 +923,6 @@ export default class DevServer {
|
||||
this.proxy.ws(req, socket, head, { target });
|
||||
});
|
||||
|
||||
const devCommandPromise = this.runDevCommand();
|
||||
|
||||
let address: string | null = null;
|
||||
while (typeof address !== 'string') {
|
||||
try {
|
||||
address = await listen(this.server, ...listenSpec);
|
||||
} catch (err) {
|
||||
this.output.debug(`Got listen error: ${err.code}`);
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
if (typeof listenSpec[0] === 'number') {
|
||||
// Increase port and try again
|
||||
this.output.note(
|
||||
`Requested port ${chalk.yellow(
|
||||
String(listenSpec[0])
|
||||
)} is already in use`
|
||||
);
|
||||
listenSpec[0]++;
|
||||
} else {
|
||||
this.output.error(
|
||||
`Requested socket ${chalk.cyan(listenSpec[0])} is already in use`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.address = address
|
||||
.replace('[::]', 'localhost')
|
||||
.replace('127.0.0.1', 'localhost');
|
||||
|
||||
await devCommandPromise;
|
||||
|
||||
this.output.ready(`Available at ${link(this.address)}`);
|
||||
@@ -1087,10 +1131,7 @@ export default class DevServer {
|
||||
const allHeaders = {
|
||||
'cache-control': 'public, max-age=0, must-revalidate',
|
||||
...headers,
|
||||
server: 'now',
|
||||
'x-now-trace': 'dev1',
|
||||
'x-now-id': nowRequestId,
|
||||
'x-now-cache': 'MISS',
|
||||
server: 'Vercel',
|
||||
'x-vercel-id': nowRequestId,
|
||||
'x-vercel-cache': 'MISS',
|
||||
};
|
||||
@@ -1104,23 +1145,24 @@ export default class DevServer {
|
||||
*/
|
||||
getNowProxyHeaders(
|
||||
req: http.IncomingMessage,
|
||||
nowRequestId: string
|
||||
nowRequestId: string,
|
||||
xfwd: boolean
|
||||
): http.IncomingHttpHeaders {
|
||||
const ip = this.getRequestIp(req);
|
||||
const { host } = req.headers;
|
||||
return {
|
||||
...req.headers,
|
||||
Connection: 'close',
|
||||
'x-forwarded-host': host,
|
||||
'x-forwarded-proto': 'http',
|
||||
'x-forwarded-for': ip,
|
||||
const headers: http.IncomingHttpHeaders = {
|
||||
connection: 'close',
|
||||
'x-real-ip': ip,
|
||||
'x-now-trace': 'dev1',
|
||||
'x-now-deployment-url': host,
|
||||
'x-now-id': nowRequestId,
|
||||
'x-now-log-id': nowRequestId.split('-')[2],
|
||||
'x-zeit-co-forwarded-for': ip,
|
||||
'x-vercel-deployment-url': host,
|
||||
'x-vercel-forwarded-for': ip,
|
||||
'x-vercel-id': nowRequestId,
|
||||
};
|
||||
if (xfwd) {
|
||||
headers['x-forwarded-host'] = host;
|
||||
headers['x-forwarded-proto'] = 'http';
|
||||
headers['x-forwarded-for'] = ip;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
async triggerBuild(
|
||||
@@ -1202,6 +1244,8 @@ export default class DevServer {
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse
|
||||
) => {
|
||||
await this.startPromise;
|
||||
|
||||
let nowRequestId = generateRequestId(this.podId);
|
||||
|
||||
if (this.stopping) {
|
||||
@@ -1612,6 +1656,12 @@ export default class DevServer {
|
||||
query: parsed.query,
|
||||
});
|
||||
|
||||
// Add the Vercel platform proxy request headers
|
||||
const headers = this.getNowProxyHeaders(req, nowRequestId, false);
|
||||
for (const [name, value] of Object.entries(headers)) {
|
||||
req.headers[name] = value;
|
||||
}
|
||||
|
||||
this.setResponseHeaders(res, nowRequestId);
|
||||
return proxyPass(
|
||||
req,
|
||||
@@ -1724,7 +1774,10 @@ export default class DevServer {
|
||||
method: req.method || 'GET',
|
||||
host: req.headers.host,
|
||||
path,
|
||||
headers: this.getNowProxyHeaders(req, nowRequestId),
|
||||
headers: {
|
||||
...req.headers,
|
||||
...this.getNowProxyHeaders(req, nowRequestId, true),
|
||||
},
|
||||
encoding: 'base64',
|
||||
body: body.toString('base64'),
|
||||
};
|
||||
@@ -1781,7 +1834,7 @@ export default class DevServer {
|
||||
|
||||
const dirs: Set<string> = new Set();
|
||||
const files = Array.from(this.buildMatches.keys())
|
||||
.filter((p) => {
|
||||
.filter(p => {
|
||||
const base = basename(p);
|
||||
if (
|
||||
base === 'now.json' ||
|
||||
@@ -1802,7 +1855,7 @@ export default class DevServer {
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((p) => {
|
||||
.map(p => {
|
||||
let base = basename(p);
|
||||
let ext = '';
|
||||
let type = 'file';
|
||||
@@ -1894,8 +1947,6 @@ export default class DevServer {
|
||||
...(this.frameworkSlug === 'create-react-app' ? { BROWSER: 'none' } : {}),
|
||||
...process.env,
|
||||
...this.envConfigs.allEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
VERCEL_REGION: 'dev1',
|
||||
PORT: `${port}`,
|
||||
};
|
||||
|
||||
@@ -2214,7 +2265,7 @@ function isIndex(path: string): boolean {
|
||||
|
||||
function minimatches(files: string[], pattern: string): boolean {
|
||||
return files.some(
|
||||
(file) => file === pattern || minimatch(file, pattern, { dot: true })
|
||||
file => file === pattern || minimatch(file, pattern, { dot: true })
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2244,7 +2295,7 @@ function needsBlockingBuild(buildMatch: BuildMatch): boolean {
|
||||
}
|
||||
|
||||
async function sleep(n: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, n));
|
||||
return new Promise(resolve => setTimeout(resolve, n));
|
||||
}
|
||||
|
||||
async function checkForPort(
|
||||
|
||||
@@ -27,11 +27,12 @@ export interface DevServerOptions {
|
||||
devCommand?: string;
|
||||
frameworkSlug?: string;
|
||||
projectSettings?: ProjectSettings;
|
||||
environmentVars?: Env;
|
||||
}
|
||||
|
||||
export interface EnvConfigs {
|
||||
/**
|
||||
* environment variables from `.env.build` file (deprecated)
|
||||
* environment variables from `.env.build` file
|
||||
*/
|
||||
buildEnv: Env;
|
||||
|
||||
|
||||
46
packages/now-cli/src/util/get-decrypted-env-records.ts
Normal file
46
packages/now-cli/src/util/get-decrypted-env-records.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import getEnvVariables from './env/get-env-records';
|
||||
import getDecryptedSecret from './env/get-decrypted-secret';
|
||||
import Client from './client';
|
||||
import { Output } from './output/create-output';
|
||||
import { ProjectEnvTarget, Project } from '../types';
|
||||
|
||||
import { Env } from '@vercel/build-utils';
|
||||
|
||||
export default async function getDecryptedEnvRecords(
|
||||
output: Output,
|
||||
client: Client,
|
||||
project: Project,
|
||||
target: ProjectEnvTarget
|
||||
): Promise<Env> {
|
||||
const envs = await getEnvVariables(output, client, project.id, 4, target);
|
||||
const decryptedValues = await Promise.all(
|
||||
envs.map(async env => {
|
||||
try {
|
||||
const value = await getDecryptedSecret(output, client, env.value);
|
||||
return { value, found: true };
|
||||
} catch (error) {
|
||||
if (error && error.status === 404) {
|
||||
return { value: '', found: false };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const results: Env = {};
|
||||
for (let i = 0; i < decryptedValues.length; i++) {
|
||||
const { key } = envs[i];
|
||||
const { value, found } = decryptedValues[i];
|
||||
|
||||
if (!found) {
|
||||
output.print('');
|
||||
output.warn(
|
||||
`Unable to download variable ${key} because associated secret was deleted`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
results[key] = value ? value : '';
|
||||
}
|
||||
return results;
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { prependEmoji, emoji } from '../emoji';
|
||||
import AJV from 'ajv';
|
||||
import { isDirectory } from '../config/global-path';
|
||||
import { NowBuildError, getPlatformEnv } from '@vercel/build-utils';
|
||||
import outputCode from '../output/code';
|
||||
|
||||
const readFile = promisify(fs.readFile);
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
@@ -152,7 +153,9 @@ export async function getLinkedProject(
|
||||
if (err?.status === 403) {
|
||||
spinner();
|
||||
throw new NowBuildError({
|
||||
message: `Could not retrieve Project Settings. To link your project, remove the .vercel directory and deploy again.`,
|
||||
message: `Could not retrieve Project Settings. To link your Project, remove the ${outputCode(
|
||||
'.vercel'
|
||||
)} directory and deploy again.`,
|
||||
code: 'PROJECT_UNAUTHORIZED',
|
||||
link: 'https://vercel.link/cannot-load-project-settings',
|
||||
});
|
||||
|
||||
2
packages/now-cli/test/dev-server.unit.js
vendored
2
packages/now-cli/test/dev-server.unit.js
vendored
@@ -71,7 +71,7 @@ function testFixture(name, fn) {
|
||||
}
|
||||
|
||||
function validateResponseHeaders(t, res, podId = null) {
|
||||
t.is(res.headers.get('server'), 'now');
|
||||
t.is(res.headers.get('server'), 'Vercel');
|
||||
t.truthy(res.headers.get('cache-control').length > 0);
|
||||
t.truthy(
|
||||
/^dev1::(dev1::)?[0-9a-z]{5}-[1-9][0-9]+-[a-f0-9]{12}$/.test(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"functions": {
|
||||
"api/user.sh": {
|
||||
"runtime": "vercel-bash@3.0.7"
|
||||
"runtime": "vercel-bash@3.0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
.vercel
|
||||
@@ -1,10 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Req Path: %s", r.URL.Path)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package another
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Another(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "This is another page")
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "This is the index page")
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Created by Vercel CLI
|
||||
VERCEL_REGION=""
|
||||
VERCEL_URL=""
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = (req, res) => {
|
||||
res.send({
|
||||
env: process.env,
|
||||
headers: req.headers,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
from flask import Flask, Response, request
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/', defaults={'path': ''})
|
||||
@app.route('/<path:path>')
|
||||
def headers(path):
|
||||
url = request.headers.get('x-vercel-deployment-url')
|
||||
return Response(url, mimetype='text/plain')
|
||||
@@ -118,24 +118,9 @@ async function runNpmInstall(fixturePath) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getPackedBuilderPath(builderDirName) {
|
||||
const packagePath = join(__dirname, '..', '..', '..', builderDirName);
|
||||
const output = await execa('npm', ['pack'], {
|
||||
cwd: packagePath,
|
||||
shell: true,
|
||||
});
|
||||
|
||||
if (output.exitCode !== 0 || output.stdout.trim() === '') {
|
||||
throw new Error(
|
||||
`Failed to pack ${builderDirName}: ${formatOutput(output)}`
|
||||
);
|
||||
}
|
||||
|
||||
return join(packagePath, output.stdout.trim());
|
||||
}
|
||||
|
||||
async function testPath(
|
||||
t,
|
||||
isDev,
|
||||
origin,
|
||||
status,
|
||||
path,
|
||||
@@ -153,6 +138,9 @@ async function testPath(
|
||||
if (typeof expectedText === 'string') {
|
||||
const actualText = await res.text();
|
||||
t.is(actualText.trim(), expectedText.trim(), msg);
|
||||
} else if (typeof expectedText === 'function') {
|
||||
const actualText = await res.text();
|
||||
await expectedText(t, actualText, res, isDev);
|
||||
} else if (expectedText instanceof RegExp) {
|
||||
const actualText = await res.text();
|
||||
expectedText.lastIndex = 0; // reset since we test twice
|
||||
@@ -342,9 +330,9 @@ function testFixtureStdio(
|
||||
|
||||
const helperTestPath = async (...args) => {
|
||||
if (!skipDeploy) {
|
||||
await testPath(t, `https://${deploymentUrl}`, ...args);
|
||||
await testPath(t, false, `https://${deploymentUrl}`, ...args);
|
||||
}
|
||||
await testPath(t, `http://localhost:${port}`, ...args);
|
||||
await testPath(t, true, `http://localhost:${port}`, ...args);
|
||||
};
|
||||
await fn(helperTestPath, t, port);
|
||||
} finally {
|
||||
@@ -1480,22 +1468,6 @@ test('[vercel dev] render warning for empty cwd dir', async t => {
|
||||
test('[vercel dev] do not rebuild for changes in the output directory', async t => {
|
||||
const directory = fixture('output-is-source');
|
||||
|
||||
// Pack the builder and set it in the `vercel.json`
|
||||
const builder = await getPackedBuilderPath('now-static-build');
|
||||
|
||||
await fs.writeFile(
|
||||
join(directory, 'vercel.json'),
|
||||
JSON.stringify({
|
||||
builds: [
|
||||
{
|
||||
src: 'package.json',
|
||||
use: `file://${builder}`,
|
||||
config: { zeroConfig: true },
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
const { dev, port } = await testFixture(directory, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
@@ -1581,6 +1553,10 @@ test(
|
||||
await testPath(200, `/api/user?name=${name}`, new RegExp(`Hello ${name}`));
|
||||
await testPath(200, `/api/date`, new RegExp(`Current date is ${year}`));
|
||||
await testPath(200, `/api/date.py`, new RegExp(`Current date is ${year}`));
|
||||
await testPath(200, `/api/headers`, (t, body, res) => {
|
||||
const { host } = new URL(res.url);
|
||||
t.is(body, host);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1624,28 +1600,3 @@ test(
|
||||
await testPath(200, '/index.css', 'This is index.css');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Should support `*.go` API serverless functions',
|
||||
testFixtureStdio('go', async testPath => {
|
||||
await testPath(200, `/api`, 'This is the index page');
|
||||
await testPath(200, `/api/index`, 'This is the index page');
|
||||
await testPath(200, `/api/index.go`, 'This is the index page');
|
||||
await testPath(200, `/api/another`, 'This is another page');
|
||||
await testPath(200, '/api/another.go', 'This is another page');
|
||||
await testPath(200, `/api/foo`, 'Req Path: /api/foo');
|
||||
await testPath(200, `/api/bar`, 'Req Path: /api/bar');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Should set the `ts-node` "target" to match Node.js version',
|
||||
testFixtureStdio('node-ts-node-target', async testPath => {
|
||||
await testPath(200, `/api/subclass`, '{"ok":true}');
|
||||
await testPath(
|
||||
200,
|
||||
`/api/array`,
|
||||
'{"months":[1,2,3,4,5,6,7,8,9,10,11,12]}'
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -365,7 +365,7 @@ CMD ["node", "index.js"]`,
|
||||
'package.json': JSON.stringify({
|
||||
private: true,
|
||||
scripts: {
|
||||
build: 'mkdir public && node print.js > public/index.json',
|
||||
build: 'mkdir -p public && node print.js > public/index.json',
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
88
packages/now-cli/test/integration.js
vendored
88
packages/now-cli/test/integration.js
vendored
@@ -593,7 +593,86 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
|
||||
t.is(homeRes.status, 200, formatOutput({ stderr, stdout }));
|
||||
const homeJson = await homeRes.json();
|
||||
t.is(homeJson['MY_ENV_VAR'], 'MY_VALUE');
|
||||
t.is(apiJson['VERCEL_URL'], host);
|
||||
t.is(homeJson['VERCEL_URL'], host);
|
||||
}
|
||||
|
||||
async function nowDevWithEnv() {
|
||||
const vc = execa(binaryPath, ['dev', ...defaultArgs], {
|
||||
reject: false,
|
||||
cwd: target,
|
||||
});
|
||||
|
||||
let localhost = undefined;
|
||||
await waitForPrompt(vc, chunk => {
|
||||
if (chunk.includes('Ready! Available at')) {
|
||||
localhost = /(https?:[^\s]+)/g.exec(chunk);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const localhostNoProtocol = localhost[0].slice('http://'.length);
|
||||
|
||||
const apiUrl = `${localhost[0]}/api/get-env`;
|
||||
const apiRes = await fetch(apiUrl);
|
||||
|
||||
t.is(apiRes.status, 200);
|
||||
|
||||
const apiJson = await apiRes.json();
|
||||
|
||||
t.is(apiJson['MY_ENV_VAR'], 'MY_VALUE');
|
||||
t.is(apiJson['VERCEL_URL'], localhostNoProtocol);
|
||||
|
||||
const homeUrl = localhost[0];
|
||||
|
||||
const homeRes = await fetch(homeUrl);
|
||||
const homeJson = await homeRes.json();
|
||||
t.is(homeJson['MY_ENV_VAR'], 'MY_VALUE');
|
||||
t.is(homeJson['VERCEL_URL'], localhostNoProtocol);
|
||||
|
||||
vc.kill('SIGTERM', { forceKillAfterTimeout: 2000 });
|
||||
|
||||
const { exitCode, stderr, stdout } = await vc;
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
}
|
||||
|
||||
async function nowDevAndFetchCloudVars() {
|
||||
const vc = execa(binaryPath, ['dev', ...defaultArgs], {
|
||||
reject: false,
|
||||
cwd: target,
|
||||
});
|
||||
|
||||
let localhost = undefined;
|
||||
await waitForPrompt(vc, chunk => {
|
||||
if (chunk.includes('Ready! Available at')) {
|
||||
localhost = /(https?:[^\s]+)/g.exec(chunk);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const apiUrl = `${localhost[0]}/api/get-env`;
|
||||
const apiRes = await fetch(apiUrl);
|
||||
|
||||
const localhostNoProtocol = localhost[0].slice('http://'.length);
|
||||
|
||||
t.is(apiRes.status, 200);
|
||||
|
||||
const apiJson = await apiRes.json();
|
||||
|
||||
t.is(apiJson['VERCEL_URL'], localhostNoProtocol);
|
||||
t.is(apiJson['MY_ENV_VAR'], 'MY_VALUE');
|
||||
|
||||
const homeUrl = localhost[0];
|
||||
const homeRes = await fetch(homeUrl);
|
||||
const homeJson = await homeRes.json();
|
||||
t.is(homeJson['MY_ENV_VAR'], 'MY_VALUE');
|
||||
t.is(homeJson['VERCEL_URL'], localhostNoProtocol);
|
||||
|
||||
vc.kill('SIGTERM', { forceKillAfterTimeout: 2000 });
|
||||
|
||||
const { exitCode, stderr, stdout } = await vc;
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
}
|
||||
|
||||
async function nowEnvRemove() {
|
||||
@@ -662,11 +741,13 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
|
||||
await nowEnvPullOverwrite();
|
||||
await nowEnvPullConfirm();
|
||||
await nowDeployWithVar();
|
||||
await nowDevWithEnv();
|
||||
fs.unlinkSync(path.join(target, '.env'));
|
||||
await nowDevAndFetchCloudVars();
|
||||
await nowEnvRemove();
|
||||
await nowEnvRemoveWithArgs();
|
||||
await nowEnvRemoveWithNameOnly();
|
||||
await nowEnvLsIsEmpty();
|
||||
fs.unlinkSync(path.join(target, '.env'));
|
||||
});
|
||||
|
||||
test('deploy with metadata containing "=" in the value', async t => {
|
||||
@@ -1761,6 +1842,7 @@ test('deploying a file should not show prompts and display deprecation', async t
|
||||
|
||||
t.is(contentType, 'image/png');
|
||||
t.deepEqual(await readFile(file), await response.buffer());
|
||||
console.log('>>>>>', stdout);
|
||||
});
|
||||
|
||||
test('deploying more than 1 path should fail', async t => {
|
||||
@@ -3083,7 +3165,7 @@ test('reject deploying with wrong team .vercel config', async t => {
|
||||
t.is(exitCode, 1, formatOutput({ stderr, stdout }));
|
||||
t.true(
|
||||
stderr.includes(
|
||||
'Could not retrieve Project Settings. To link your project, remove the .vercel directory and deploy again.'
|
||||
'Could not retrieve Project Settings. To link your Project, remove the `.vercel` directory and deploy again.'
|
||||
),
|
||||
formatOutput({ stderr, stdout })
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/client",
|
||||
"version": "8.2.0",
|
||||
"version": "8.2.1",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://vercel.com",
|
||||
@@ -38,7 +38,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.4.1",
|
||||
"@vercel/build-utils": "2.4.2",
|
||||
"@zeit/fetch": "5.2.0",
|
||||
"async-retry": "1.2.3",
|
||||
"async-sema": "3.0.0",
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Start fresh
|
||||
rm -rf dist
|
||||
|
||||
# Build with `ncc`
|
||||
ncc build index.ts -e @vercel/build-utils -e @now/build-utils -o dist
|
||||
ncc build install.ts -e @vercel/build-utils -e @now/build-utils -o dist/install
|
||||
|
||||
# Move `install.js` to dist
|
||||
mv dist/install/index.js dist/install.js
|
||||
rm -rf dist/install
|
||||
|
||||
@@ -3,10 +3,14 @@ import execa from 'execa';
|
||||
import fetch from 'node-fetch';
|
||||
import { mkdirp, pathExists } from 'fs-extra';
|
||||
import { dirname, join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import buildUtils from './build-utils';
|
||||
import stringArgv from 'string-argv';
|
||||
const { debug } = buildUtils;
|
||||
const archMap = new Map([['x64', 'amd64'], ['x86', '386']]);
|
||||
const archMap = new Map([
|
||||
['x64', 'amd64'],
|
||||
['x86', '386'],
|
||||
]);
|
||||
const platformMap = new Map([['win32', 'windows']]);
|
||||
|
||||
// Location where the `go` binary will be installed after `postinstall`
|
||||
@@ -126,35 +130,50 @@ export async function downloadGo(
|
||||
platform = process.platform,
|
||||
arch = process.arch
|
||||
) {
|
||||
// Check if `go` is already installed in user's `$PATH`
|
||||
const { failed, stdout } = await execa('go', ['version'], { reject: false });
|
||||
// Check default `Go` in user machine
|
||||
const isUserGo = await pathExists(join(homedir(), 'go'));
|
||||
|
||||
if (!failed && parseInt(stdout.split('.')[1]) >= 11) {
|
||||
debug('Using system installed version of `go`: %o', stdout.trim());
|
||||
return createGo(dir, platform, arch);
|
||||
}
|
||||
// If we found GOPATH in ENV, or default `Go` path exists
|
||||
// asssume that user have `Go` installed
|
||||
if (isUserGo || process.env.GOPATH !== undefined) {
|
||||
const { stdout } = await execa('go', ['version']);
|
||||
|
||||
// Check `go` bin in builder CWD
|
||||
const isGoExist = await pathExists(join(dir, 'bin'));
|
||||
if (!isGoExist) {
|
||||
debug('Installing `go` v%s to %o for %s %s', version, dir, platform, arch);
|
||||
const url = getGoUrl(version, platform, arch);
|
||||
debug('Downloading `go` URL: %o', url);
|
||||
const res = await fetch(url);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to download: ${url} (${res.status})`);
|
||||
if (parseInt(stdout.split('.')[1]) >= 11) {
|
||||
return createGo(dir, platform, arch);
|
||||
}
|
||||
|
||||
// TODO: use a zip extractor when `ext === "zip"`
|
||||
await mkdirp(dir);
|
||||
await new Promise((resolve, reject) => {
|
||||
res.body
|
||||
.on('error', reject)
|
||||
.pipe(tar.extract({ cwd: dir, strip: 1 }))
|
||||
.on('error', reject)
|
||||
.on('finish', resolve);
|
||||
});
|
||||
throw new Error(
|
||||
`Your current ${stdout} doesn't support Go Modules. Please update.`
|
||||
);
|
||||
} else {
|
||||
// Check `Go` bin in builder CWD
|
||||
const isGoExist = await pathExists(join(dir, 'bin'));
|
||||
if (!isGoExist) {
|
||||
debug(
|
||||
'Installing `go` v%s to %o for %s %s',
|
||||
version,
|
||||
dir,
|
||||
platform,
|
||||
arch
|
||||
);
|
||||
const url = getGoUrl(version, platform, arch);
|
||||
debug('Downloading `go` URL: %o', url);
|
||||
const res = await fetch(url);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to download: ${url} (${res.status})`);
|
||||
}
|
||||
|
||||
// TODO: use a zip extractor when `ext === "zip"`
|
||||
await mkdirp(dir);
|
||||
await new Promise((resolve, reject) => {
|
||||
res.body
|
||||
.on('error', reject)
|
||||
.pipe(tar.extract({ cwd: dir, strip: 1 }))
|
||||
.on('error', reject)
|
||||
.on('finish', resolve);
|
||||
});
|
||||
}
|
||||
return createGo(dir, platform, arch);
|
||||
}
|
||||
return createGo(dir, platform, arch);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,8 @@
|
||||
import { join, sep, dirname, basename, normalize } from 'path';
|
||||
import { readFile, writeFile, pathExists, move } from 'fs-extra';
|
||||
import { homedir } from 'os';
|
||||
import execa from 'execa';
|
||||
import retry from 'async-retry';
|
||||
import { homedir, tmpdir } from 'os';
|
||||
import { spawn } from 'child_process';
|
||||
import { Readable } from 'stream';
|
||||
import once from '@tootallnate/once';
|
||||
import { join, dirname, basename, normalize, sep } from 'path';
|
||||
import {
|
||||
readFile,
|
||||
writeFile,
|
||||
pathExists,
|
||||
mkdirp,
|
||||
move,
|
||||
remove,
|
||||
} from 'fs-extra';
|
||||
import {
|
||||
BuildOptions,
|
||||
Meta,
|
||||
Files,
|
||||
StartDevServerOptions,
|
||||
StartDevServerResult,
|
||||
} from '@vercel/build-utils';
|
||||
import { BuildOptions, Meta, Files, shouldServe } from '@vercel/build-utils';
|
||||
import buildUtils from './build-utils';
|
||||
|
||||
const {
|
||||
@@ -27,17 +10,12 @@ const {
|
||||
download,
|
||||
createLambda,
|
||||
getWriteableDirectory,
|
||||
shouldServe,
|
||||
debug,
|
||||
} = buildUtils;
|
||||
|
||||
const TMP = tmpdir();
|
||||
|
||||
import { createGo, getAnalyzedEntrypoint, OUT_EXTENSION } from './go-helpers';
|
||||
const handlerFileName = `handler${OUT_EXTENSION}`;
|
||||
|
||||
export { shouldServe };
|
||||
|
||||
interface Analyzed {
|
||||
found?: boolean;
|
||||
packageName: string;
|
||||
@@ -45,22 +23,16 @@ interface Analyzed {
|
||||
watch: string[];
|
||||
}
|
||||
|
||||
interface PortInfo {
|
||||
port: number;
|
||||
}
|
||||
|
||||
// Initialize private git repo for Go Modules
|
||||
async function initPrivateGit(credentials: string) {
|
||||
const gitCredentialsPath = join(homedir(), '.git-credentials');
|
||||
|
||||
await execa('git', [
|
||||
'config',
|
||||
'--global',
|
||||
'credential.helper',
|
||||
`store --file ${gitCredentialsPath}`,
|
||||
`store --file ${join(homedir(), '.git-credentials')}`,
|
||||
]);
|
||||
|
||||
await writeFile(gitCredentialsPath, credentials);
|
||||
await writeFile(join(homedir(), '.git-credentials'), credentials);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -463,160 +435,4 @@ Learn more: https://vercel.com/docs/runtimes#official-runtimes/go
|
||||
};
|
||||
}
|
||||
|
||||
function isPortInfo(v: any): v is PortInfo {
|
||||
return v && typeof v.port === 'number';
|
||||
}
|
||||
|
||||
function isReadable(v: any): v is Readable {
|
||||
return v && v.readable === true;
|
||||
}
|
||||
|
||||
async function copyEntrypoint(entrypoint: string, dest: string): Promise<void> {
|
||||
const data = await readFile(entrypoint, 'utf8');
|
||||
|
||||
// Modify package to `package main`
|
||||
const patched = data.replace(/\bpackage\W+\S+\b/, 'package main');
|
||||
|
||||
await writeFile(join(dest, 'entrypoint.go'), patched);
|
||||
}
|
||||
|
||||
async function copyDevServer(
|
||||
functionName: string,
|
||||
dest: string
|
||||
): Promise<void> {
|
||||
const data = await readFile(join(__dirname, 'dev-server.go'), 'utf8');
|
||||
|
||||
// Populate the handler function name
|
||||
const patched = data.replace('__HANDLER_FUNC_NAME', functionName);
|
||||
|
||||
await writeFile(join(dest, 'vercel-dev-server-main.go'), patched);
|
||||
}
|
||||
|
||||
export async function startDevServer(
|
||||
opts: StartDevServerOptions
|
||||
): Promise<StartDevServerResult> {
|
||||
const { entrypoint, workPath, meta = {} } = opts;
|
||||
const { devCacheDir = join(workPath, '.vercel', 'cache') } = meta;
|
||||
const entrypointDir = dirname(entrypoint);
|
||||
|
||||
// For some reason, if `entrypoint` is a path segment (filename contains `[]`
|
||||
// brackets) then the `.go` suffix on the entrypoint is missing. Fix that here…
|
||||
let entrypointWithExt = entrypoint;
|
||||
if (!entrypoint.endsWith('.go')) {
|
||||
entrypointWithExt += '.go';
|
||||
}
|
||||
|
||||
const tmp = join(devCacheDir, 'go', Math.random().toString(32).substring(2));
|
||||
const tmpPackage = join(tmp, entrypointDir);
|
||||
await mkdirp(tmpPackage);
|
||||
|
||||
let goModAbsPathDir = '';
|
||||
if (await pathExists(join(workPath, 'go.mod'))) {
|
||||
goModAbsPathDir = workPath;
|
||||
}
|
||||
const analyzedRaw = await getAnalyzedEntrypoint(
|
||||
entrypointWithExt,
|
||||
goModAbsPathDir
|
||||
);
|
||||
if (!analyzedRaw) {
|
||||
throw new Error(
|
||||
`Could not find an exported function in "${entrypointWithExt}"
|
||||
Learn more: https://vercel.com/docs/runtimes#official-runtimes/go`
|
||||
);
|
||||
}
|
||||
const analyzed: Analyzed = JSON.parse(analyzedRaw);
|
||||
|
||||
await Promise.all([
|
||||
copyEntrypoint(entrypointWithExt, tmpPackage),
|
||||
copyDevServer(analyzed.functionName, tmpPackage),
|
||||
]);
|
||||
|
||||
const portFile = join(
|
||||
TMP,
|
||||
`vercel-dev-port-${Math.random().toString(32).substring(2)}`
|
||||
);
|
||||
|
||||
const env: typeof process.env = {
|
||||
...process.env,
|
||||
...meta.env,
|
||||
VERCEL_DEV_PORT_FILE: portFile,
|
||||
};
|
||||
|
||||
const tmpRelative = `.${sep}${entrypointDir}`;
|
||||
const child = spawn('go', ['run', tmpRelative], {
|
||||
cwd: tmp,
|
||||
env,
|
||||
stdio: ['ignore', 'inherit', 'inherit', 'pipe'],
|
||||
});
|
||||
|
||||
child.once('exit', () => {
|
||||
retry(() => remove(tmp)).catch((err: Error) => {
|
||||
console.error('Could not delete tmp directory: %j: %s', tmp, err);
|
||||
});
|
||||
});
|
||||
|
||||
const portPipe = child.stdio[3];
|
||||
if (!isReadable(portPipe)) {
|
||||
throw new Error('File descriptor 3 is not readable');
|
||||
}
|
||||
|
||||
// `dev-server.go` writes the ephemeral port number to FD 3 to be consumed here
|
||||
const onPort = new Promise<PortInfo>(resolve => {
|
||||
portPipe.setEncoding('utf8');
|
||||
portPipe.once('data', d => {
|
||||
resolve({ port: Number(d) });
|
||||
});
|
||||
});
|
||||
const onPortFile = waitForPortFile(portFile);
|
||||
const onExit = once.spread<[number, string | null]>(child, 'exit');
|
||||
const result = await Promise.race([onPort, onPortFile, onExit]);
|
||||
onExit.cancel();
|
||||
onPortFile.cancel();
|
||||
|
||||
if (isPortInfo(result)) {
|
||||
return {
|
||||
port: result.port,
|
||||
pid: child.pid,
|
||||
};
|
||||
} else if (Array.isArray(result)) {
|
||||
// Got "exit" event from child process
|
||||
throw new Error(
|
||||
`Failed to start dev server for "${entrypointWithExt}" (code=${result[0]}, signal=${result[1]})`
|
||||
);
|
||||
} else {
|
||||
throw new Error(`Unexpected result type: ${typeof result}`);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CancelablePromise<T> extends Promise<T> {
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
function waitForPortFile(portFile: string) {
|
||||
const opts = { portFile, canceled: false };
|
||||
const promise = waitForPortFile_(opts) as CancelablePromise<PortInfo | void>;
|
||||
promise.cancel = () => {
|
||||
opts.canceled = true;
|
||||
};
|
||||
return promise;
|
||||
}
|
||||
|
||||
async function waitForPortFile_(opts: {
|
||||
portFile: string;
|
||||
canceled: boolean;
|
||||
}): Promise<PortInfo | void> {
|
||||
while (!opts.canceled) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
try {
|
||||
const port = Number(await readFile(opts.portFile, 'ascii'));
|
||||
retry(() => remove(opts.portFile)).catch((err: Error) => {
|
||||
console.error('Could not delete port file: %j: %s', opts.portFile, err);
|
||||
});
|
||||
return { port };
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export { shouldServe };
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
vc "github.com/vercel/go-bridge/go/bridge"
|
||||
"net/http"
|
||||
vc "github.com/vercel/go-bridge/go/bridge"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"__NOW_HANDLER_PACKAGE_NAME"
|
||||
"net/http"
|
||||
"net/http"
|
||||
"__NOW_HANDLER_PACKAGE_NAME"
|
||||
|
||||
vc "github.com/vercel/go-bridge/go/bridge"
|
||||
vc "github.com/vercel/go-bridge/go/bridge"
|
||||
)
|
||||
|
||||
func main() {
|
||||
vc.Start(http.HandlerFunc(__NOW_HANDLER_FUNC_NAME))
|
||||
vc.Start(http.HandlerFunc(__NOW_HANDLER_FUNC_NAME))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/go",
|
||||
"version": "1.1.4-canary.0",
|
||||
"version": "1.1.4",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
|
||||
@@ -19,8 +19,6 @@
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@tootallnate/once": "1.1.2",
|
||||
"@types/async-retry": "1.4.2",
|
||||
"@types/execa": "^0.9.0",
|
||||
"@types/fs-extra": "^5.0.5",
|
||||
"@types/node-fetch": "^2.3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/next",
|
||||
"version": "2.6.12",
|
||||
"version": "2.6.13",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
|
||||
@@ -26,7 +26,7 @@
|
||||
"@types/resolve-from": "5.0.1",
|
||||
"@types/semver": "6.0.0",
|
||||
"@types/yazl": "2.4.1",
|
||||
"@zeit/node-file-trace": "0.6.5",
|
||||
"@zeit/node-file-trace": "0.8.0",
|
||||
"async-sema": "3.0.1",
|
||||
"buffer-crc32": "0.2.13",
|
||||
"escape-string-regexp": "3.0.0",
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
convertRedirects,
|
||||
convertRewrites,
|
||||
} from '@vercel/routing-utils/dist/superstatic';
|
||||
import nodeFileTrace, { NodeFileTraceReasons } from '@zeit/node-file-trace';
|
||||
import { nodeFileTrace, NodeFileTraceReasons } from '@zeit/node-file-trace';
|
||||
import { ChildProcess, fork } from 'child_process';
|
||||
import escapeStringRegexp from 'escape-string-regexp';
|
||||
import {
|
||||
@@ -91,7 +91,7 @@ export const version = 2;
|
||||
const htmlContentType = 'text/html; charset=utf-8';
|
||||
const nowDevChildProcesses = new Set<ChildProcess>();
|
||||
|
||||
['SIGINT', 'SIGTERM'].forEach((signal) => {
|
||||
['SIGINT', 'SIGTERM'].forEach(signal => {
|
||||
process.once(signal as NodeJS.Signals, () => {
|
||||
for (const child of nowDevChildProcesses) {
|
||||
debug(
|
||||
@@ -485,7 +485,7 @@ export const build = async ({
|
||||
`${(ssgDataRoute && ssgDataRoute.dataRoute) || dataRoute.page}${
|
||||
dataRoute.routeKeys
|
||||
? `?${Object.keys(dataRoute.routeKeys)
|
||||
.map((key) => `${dataRoute.routeKeys![key]}=$${key}`)
|
||||
.map(key => `${dataRoute.routeKeys![key]}=$${key}`)
|
||||
.join('&')}`
|
||||
: ''
|
||||
}`
|
||||
@@ -579,8 +579,6 @@ export const build = async ({
|
||||
return {
|
||||
output,
|
||||
routes: [
|
||||
// TODO: low priority: handle trailingSlash
|
||||
|
||||
// User headers
|
||||
...headers,
|
||||
|
||||
@@ -716,7 +714,7 @@ export const build = async ({
|
||||
);
|
||||
const nodeModules = excludeFiles(
|
||||
await glob('node_modules/**', entryPath),
|
||||
(file) => file.startsWith('node_modules/.cache')
|
||||
file => file.startsWith('node_modules/.cache')
|
||||
);
|
||||
const launcherFiles = {
|
||||
'now__bridge.js': new FileFsRef({
|
||||
@@ -745,7 +743,7 @@ export const build = async ({
|
||||
const launcherData = await readFile(launcherPath, 'utf8');
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(pages).map(async (page) => {
|
||||
Object.keys(pages).map(async page => {
|
||||
// These default pages don't have to be handled as they'd always 404
|
||||
if (['_app.js', '_error.js', '_document.js'].includes(page)) {
|
||||
return;
|
||||
@@ -903,7 +901,7 @@ export const build = async ({
|
||||
|
||||
const apiPages: string[] = [];
|
||||
const nonApiPages: string[] = [];
|
||||
const allPagePaths = Object.keys(pages).map((page) => pages[page].fsPath);
|
||||
const allPagePaths = Object.keys(pages).map(page => pages[page].fsPath);
|
||||
|
||||
for (const page of allPagePaths) {
|
||||
if (isApiPage(page)) {
|
||||
@@ -996,7 +994,7 @@ export const build = async ({
|
||||
debug(
|
||||
'detected (legacy) assets to be bundled with serverless function:'
|
||||
);
|
||||
assetKeys.forEach((assetFile) => debug(`\t${assetFile}`));
|
||||
assetKeys.forEach(assetFile => debug(`\t${assetFile}`));
|
||||
debug(
|
||||
'\nPlease upgrade to Next.js 9.1 to leverage modern asset handling.'
|
||||
);
|
||||
@@ -1136,7 +1134,7 @@ export const build = async ({
|
||||
}
|
||||
} else {
|
||||
await Promise.all(
|
||||
pageKeys.map(async (page) => {
|
||||
pageKeys.map(async page => {
|
||||
// These default pages don't have to be handled as they'd always 404
|
||||
if (['_app.js', '_document.js'].includes(page)) {
|
||||
return;
|
||||
@@ -1217,8 +1215,8 @@ export const build = async ({
|
||||
false,
|
||||
routesManifest,
|
||||
new Set(prerenderManifest.omittedRoutes)
|
||||
).then((arr) =>
|
||||
arr.map((route) => {
|
||||
).then(arr =>
|
||||
arr.map(route => {
|
||||
route.src = route.src.replace('^', `^${dynamicPrefix}`);
|
||||
return route;
|
||||
})
|
||||
@@ -1236,8 +1234,8 @@ export const build = async ({
|
||||
dynamicPages,
|
||||
false,
|
||||
routesManifest
|
||||
).then((arr) =>
|
||||
arr.map((route) => {
|
||||
).then(arr =>
|
||||
arr.map(route => {
|
||||
route.src = route.src.replace('^', `^${dynamicPrefix}`);
|
||||
return route;
|
||||
})
|
||||
@@ -1257,7 +1255,7 @@ export const build = async ({
|
||||
const pages = {
|
||||
${groupPageKeys
|
||||
.map(
|
||||
(page) =>
|
||||
page =>
|
||||
`'${page}': require('./${path.join(
|
||||
'./',
|
||||
group.pages[page].pageFileName
|
||||
@@ -1302,7 +1300,7 @@ export const build = async ({
|
||||
// for prerendered dynamic routes (/blog/post-1) we need to
|
||||
// find the match since it won't match the page directly
|
||||
const dynamicRoutes = ${JSON.stringify(
|
||||
completeDynamicRoutes.map((route) => ({
|
||||
completeDynamicRoutes.map(route => ({
|
||||
src: route.src,
|
||||
dest: route.dest,
|
||||
}))
|
||||
@@ -1516,13 +1514,13 @@ export const build = async ({
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(prerenderManifest.staticRoutes).forEach((route) =>
|
||||
Object.keys(prerenderManifest.staticRoutes).forEach(route =>
|
||||
onPrerenderRoute(route, { isBlocking: false, isFallback: false })
|
||||
);
|
||||
Object.keys(prerenderManifest.fallbackRoutes).forEach((route) =>
|
||||
Object.keys(prerenderManifest.fallbackRoutes).forEach(route =>
|
||||
onPrerenderRoute(route, { isBlocking: false, isFallback: true })
|
||||
);
|
||||
Object.keys(prerenderManifest.legacyBlockingRoutes).forEach((route) =>
|
||||
Object.keys(prerenderManifest.legacyBlockingRoutes).forEach(route =>
|
||||
onPrerenderRoute(route, { isBlocking: true, isFallback: false })
|
||||
);
|
||||
|
||||
@@ -1586,7 +1584,7 @@ export const build = async ({
|
||||
// We need to delete lambdas from output instead of omitting them from the
|
||||
// start since we rely on them for powering Preview Mode (read above in
|
||||
// onPrerenderRoute).
|
||||
prerenderManifest.omittedRoutes.forEach((routeKey) => {
|
||||
prerenderManifest.omittedRoutes.forEach(routeKey => {
|
||||
// Get the route file as it'd be mounted in the builder output
|
||||
const routeFileNoExt = path.posix.join(
|
||||
entryDirectory,
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default () => 'hello from /[teamSlug]/[project]/[id]'
|
||||
export default () => 'hello from /[teamSlug]/[project]/[id]';
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
export const getStaticProps = ({ params }) => {
|
||||
return {
|
||||
props: {
|
||||
id: params.id
|
||||
}
|
||||
}
|
||||
}
|
||||
id: params.id,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticPaths = () => ({
|
||||
paths: ['first', 'second'].map(id => ({ params: { id }})),
|
||||
fallback: true
|
||||
})
|
||||
paths: ['first', 'second'].map(id => ({ params: { id } })),
|
||||
fallback: true,
|
||||
});
|
||||
|
||||
export default ({ id }) => useRouter().isFallback
|
||||
? `loading...`
|
||||
: `hello from /groups/[id] ${id}`
|
||||
export default ({ id }) =>
|
||||
useRouter().isFallback ? `loading...` : `hello from /groups/[id] ${id}`;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export const getServerSideProps = ({ params }) => {
|
||||
return {
|
||||
props: {
|
||||
code: params.inviteCode
|
||||
}
|
||||
}
|
||||
}
|
||||
code: params.inviteCode,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default ({ code }) => `hello from /teams/invite/[inviteCode] ${code}`
|
||||
export default ({ code }) => `hello from /teams/invite/[inviteCode] ${code}`;
|
||||
|
||||
1
packages/now-next/test/fixtures/00-trailing-slash-add/next.config.js
vendored
Normal file
1
packages/now-next/test/fixtures/00-trailing-slash-add/next.config.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = { trailingSlash: true };
|
||||
38
packages/now-next/test/fixtures/00-trailing-slash-add/now.json
vendored
Normal file
38
packages/now-next/test/fixtures/00-trailing-slash-add/now.json
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "package.json", "use": "@vercel/next" }],
|
||||
"probes": [
|
||||
{ "path": "/foo/", "status": 200, "mustContain": "foo page" },
|
||||
{
|
||||
"fetchOptions": { "redirect": "manual" },
|
||||
"path": "/foo",
|
||||
"status": 308,
|
||||
"responseHeaders": {
|
||||
"refresh": "/url=/foo/$/"
|
||||
}
|
||||
},
|
||||
{ "path": "/abc/def/", "status": 200, "mustContain": "nested page" },
|
||||
{
|
||||
"fetchOptions": { "redirect": "manual" },
|
||||
"path": "/abc/def",
|
||||
"status": 308,
|
||||
"responseHeaders": {
|
||||
"refresh": "/url=/abc/def/$/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fetchOptions": { "redirect": "manual" },
|
||||
"path": "/test.txt/",
|
||||
"status": 308,
|
||||
"responseHeaders": {
|
||||
"refresh": "/url=/test\\.txt$/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fetchOptions": { "redirect": "manual" },
|
||||
"path": "/test.txt",
|
||||
"status": 200,
|
||||
"mustContain": "this is a file"
|
||||
}
|
||||
]
|
||||
}
|
||||
7
packages/now-next/test/fixtures/00-trailing-slash-add/package.json
vendored
Normal file
7
packages/now-next/test/fixtures/00-trailing-slash-add/package.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"next": "canary",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
}
|
||||
}
|
||||
3
packages/now-next/test/fixtures/00-trailing-slash-add/pages/abc/def.js
vendored
Normal file
3
packages/now-next/test/fixtures/00-trailing-slash-add/pages/abc/def.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Page() {
|
||||
return <p>nested page</p>;
|
||||
}
|
||||
3
packages/now-next/test/fixtures/00-trailing-slash-add/pages/foo.js
vendored
Normal file
3
packages/now-next/test/fixtures/00-trailing-slash-add/pages/foo.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Page() {
|
||||
return <p>foo page</p>;
|
||||
}
|
||||
1
packages/now-next/test/fixtures/00-trailing-slash-add/public/test.txt
vendored
Normal file
1
packages/now-next/test/fixtures/00-trailing-slash-add/public/test.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
this is a file
|
||||
1
packages/now-next/test/fixtures/00-trailing-slash-remove/next.config.js
vendored
Normal file
1
packages/now-next/test/fixtures/00-trailing-slash-remove/next.config.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = { trailingSlash: false };
|
||||
38
packages/now-next/test/fixtures/00-trailing-slash-remove/now.json
vendored
Normal file
38
packages/now-next/test/fixtures/00-trailing-slash-remove/now.json
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "package.json", "use": "@vercel/next" }],
|
||||
"probes": [
|
||||
{ "path": "/foo", "status": 200, "mustContain": "foo page" },
|
||||
{
|
||||
"fetchOptions": { "redirect": "manual" },
|
||||
"path": "/foo/",
|
||||
"status": 308,
|
||||
"responseHeaders": {
|
||||
"refresh": "/url=/foo$/"
|
||||
}
|
||||
},
|
||||
{ "path": "/abc/def", "status": 200, "mustContain": "nested page" },
|
||||
{
|
||||
"fetchOptions": { "redirect": "manual" },
|
||||
"path": "/abc/def/",
|
||||
"status": 308,
|
||||
"responseHeaders": {
|
||||
"refresh": "/url=/abc/def$/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fetchOptions": { "redirect": "manual" },
|
||||
"path": "/test.txt/",
|
||||
"status": 308,
|
||||
"responseHeaders": {
|
||||
"refresh": "/url=/test\\.txt$/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fetchOptions": { "redirect": "manual" },
|
||||
"path": "/test.txt",
|
||||
"status": 200,
|
||||
"mustContain": "this is a file"
|
||||
}
|
||||
]
|
||||
}
|
||||
7
packages/now-next/test/fixtures/00-trailing-slash-remove/package.json
vendored
Normal file
7
packages/now-next/test/fixtures/00-trailing-slash-remove/package.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"next": "canary",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
}
|
||||
}
|
||||
3
packages/now-next/test/fixtures/00-trailing-slash-remove/pages/abc/def.js
vendored
Normal file
3
packages/now-next/test/fixtures/00-trailing-slash-remove/pages/abc/def.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Page() {
|
||||
return <p>nested page</p>;
|
||||
}
|
||||
3
packages/now-next/test/fixtures/00-trailing-slash-remove/pages/foo.js
vendored
Normal file
3
packages/now-next/test/fixtures/00-trailing-slash-remove/pages/foo.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Page() {
|
||||
return <p>foo page</p>;
|
||||
}
|
||||
1
packages/now-next/test/fixtures/00-trailing-slash-remove/public/test.txt
vendored
Normal file
1
packages/now-next/test/fixtures/00-trailing-slash-remove/public/test.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
this is a file
|
||||
@@ -1 +1 @@
|
||||
export default () => 'hi from final route'
|
||||
export default () => 'hi from final route';
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default () => 'hi from another route'
|
||||
export default () => 'hi from another route';
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default () => 'hi from deployment route'
|
||||
export default () => 'hi from deployment route';
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default () => 'hi from project route'
|
||||
export default () => 'hi from project route';
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default () => 'hi from team route'
|
||||
export default () => 'hi from team route';
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from 'react'
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticProps () {
|
||||
export async function unstable_getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
world: 'world',
|
||||
time: new Date().getTime()
|
||||
time: new Date().getTime(),
|
||||
},
|
||||
revalidate: 5
|
||||
}
|
||||
revalidate: 5,
|
||||
};
|
||||
}
|
||||
|
||||
export default ({ world, time }) => {
|
||||
@@ -17,5 +17,5 @@ export default ({ world, time }) => {
|
||||
<p>hello: {world}</p>
|
||||
<span>time: {time}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from 'react'
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticProps () {
|
||||
export async function unstable_getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
world: 'world',
|
||||
time: new Date().getTime()
|
||||
time: new Date().getTime(),
|
||||
},
|
||||
revalidate: 5
|
||||
}
|
||||
revalidate: 5,
|
||||
};
|
||||
}
|
||||
|
||||
export default ({ world, time }) => {
|
||||
@@ -17,5 +17,5 @@ export default ({ world, time }) => {
|
||||
<p>hello: {world}</p>
|
||||
<span>time: {time}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticPaths () {
|
||||
export async function unstable_getStaticPaths() {
|
||||
return [
|
||||
'/blog/post-1/comment-1',
|
||||
{ params: { post: 'post-2', comment: 'comment-2' } },
|
||||
@@ -10,7 +10,7 @@ export async function unstable_getStaticPaths () {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticProps ({ params }) {
|
||||
export async function unstable_getStaticProps({ params }) {
|
||||
return {
|
||||
props: {
|
||||
post: params.post,
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
import React from 'react'
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticPaths () {
|
||||
return [
|
||||
'/blog/post-1',
|
||||
{ params: { post: 'post-2' } },
|
||||
]
|
||||
export async function unstable_getStaticPaths() {
|
||||
return ['/blog/post-1', { params: { post: 'post-2' } }];
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticProps ({ params }) {
|
||||
export async function unstable_getStaticProps({ params }) {
|
||||
if (params.post === 'post-10') {
|
||||
await new Promise(resolve => {
|
||||
setTimeout(() => resolve(), 1000)
|
||||
})
|
||||
setTimeout(() => resolve(), 1000);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
post: params.post,
|
||||
time: (await import('perf_hooks')).performance.now()
|
||||
time: (await import('perf_hooks')).performance.now(),
|
||||
},
|
||||
revalidate: 10
|
||||
}
|
||||
revalidate: 10,
|
||||
};
|
||||
}
|
||||
|
||||
export default ({ post, time }) => {
|
||||
@@ -32,5 +28,5 @@ export default ({ post, time }) => {
|
||||
<p>Post: {post}</p>
|
||||
<span>time: {time}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from 'react'
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticProps () {
|
||||
export async function unstable_getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
world: 'world',
|
||||
time: new Date().getTime()
|
||||
time: new Date().getTime(),
|
||||
},
|
||||
revalidate: false
|
||||
}
|
||||
revalidate: false,
|
||||
};
|
||||
}
|
||||
|
||||
export default ({ world, time }) => {
|
||||
@@ -17,5 +17,5 @@ export default ({ world, time }) => {
|
||||
<p>hello: {world}</p>
|
||||
<span>time: {time}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const Page = ({ data }) => <p>{data} world</p>
|
||||
const Page = ({ data }) => <p>{data} world</p>;
|
||||
|
||||
Page.getInitialProps = () => ({ data: 'hello' })
|
||||
Page.getInitialProps = () => ({ data: 'hello' });
|
||||
|
||||
export default Page
|
||||
export default Page;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default function(req, res) {
|
||||
export default function (req, res) {
|
||||
res.end(`${process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE}`);
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default () => 'hello world!'
|
||||
export default () => 'hello world!';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const Page = () => {
|
||||
const { query } = useRouter()
|
||||
return <p>{JSON.stringify(query)}</p>
|
||||
}
|
||||
const { query } = useRouter();
|
||||
return <p>{JSON.stringify(query)}</p>;
|
||||
};
|
||||
|
||||
Page.getInitialProps = () => ({ a: 'b' })
|
||||
Page.getInitialProps = () => ({ a: 'b' });
|
||||
|
||||
export default Page
|
||||
export default Page;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const Page = () => 'hello world'
|
||||
const Page = () => 'hello world';
|
||||
|
||||
Page.getInitialProps = () => ({ hello: 'world' })
|
||||
Page.getInitialProps = () => ({ hello: 'world' });
|
||||
|
||||
export default Page
|
||||
export default Page;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const Page = () => {
|
||||
const { query } = useRouter()
|
||||
return <p>{JSON.stringify(query)}</p>
|
||||
}
|
||||
const { query } = useRouter();
|
||||
return <p>{JSON.stringify(query)}</p>;
|
||||
};
|
||||
|
||||
Page.getInitialProps = () => ({ a: 'b' })
|
||||
Page.getInitialProps = () => ({ a: 'b' });
|
||||
|
||||
export default Page
|
||||
export default Page;
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default (req, res) => res.end(`another slug: ${req.query.slug}`)
|
||||
export default (req, res) => res.end(`another slug: ${req.query.slug}`);
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default (req, res) => res.end(`index slug: ${req.query.slug}`)
|
||||
export default (req, res) => res.end(`index slug: ${req.query.slug}`);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export const getServerSideProps = ({ params }) => ({
|
||||
props: {
|
||||
post: params.post
|
||||
}
|
||||
})
|
||||
post: params.post,
|
||||
},
|
||||
});
|
||||
|
||||
export default function Comment({ post }) {
|
||||
return `comments post: ${post}`
|
||||
}
|
||||
return `comments post: ${post}`;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export const getServerSideProps = ({ params }) => ({
|
||||
props: {
|
||||
post: params.post
|
||||
}
|
||||
})
|
||||
post: params.post,
|
||||
},
|
||||
});
|
||||
|
||||
export default function Post({ post }) {
|
||||
return `index post: ${post}`
|
||||
}
|
||||
return `index post: ${post}`;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticPaths () {
|
||||
export async function unstable_getStaticPaths() {
|
||||
return {
|
||||
paths: [
|
||||
'/blog/post-1/comment-1',
|
||||
{ params: { post: 'post-2', comment: 'comment-2' } },
|
||||
'/blog/post-1337/comment-1337',
|
||||
]
|
||||
}
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticProps ({ params }) {
|
||||
export async function unstable_getStaticProps({ params }) {
|
||||
return {
|
||||
props: {
|
||||
post: params.post,
|
||||
@@ -24,7 +24,7 @@ export async function unstable_getStaticProps ({ params }) {
|
||||
}
|
||||
|
||||
export default ({ post, comment, time }) => {
|
||||
if (!post) return <p>loading...</p>
|
||||
if (!post) return <p>loading...</p>;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,40 +1,36 @@
|
||||
import React from 'react'
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticPaths () {
|
||||
export async function unstable_getStaticPaths() {
|
||||
return {
|
||||
paths: [
|
||||
'/blog/post-1',
|
||||
{ params: { post: 'post-2' } },
|
||||
]
|
||||
}
|
||||
paths: ['/blog/post-1', { params: { post: 'post-2' } }],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticProps ({ params }) {
|
||||
export async function unstable_getStaticProps({ params }) {
|
||||
if (params.post === 'post-10') {
|
||||
await new Promise(resolve => {
|
||||
setTimeout(() => resolve(), 1000)
|
||||
})
|
||||
setTimeout(() => resolve(), 1000);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
post: params.post,
|
||||
time: (await import('perf_hooks')).performance.now()
|
||||
time: (await import('perf_hooks')).performance.now(),
|
||||
},
|
||||
revalidate: 10
|
||||
}
|
||||
revalidate: 10,
|
||||
};
|
||||
}
|
||||
|
||||
export default ({ post, time }) => {
|
||||
if (!post) return <p>loading...</p>
|
||||
if (!post) return <p>loading...</p>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>Post: {post}</p>
|
||||
<span>time: {time}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function getServerSideProps({ params }) {
|
||||
if (params.post === 'post-10') {
|
||||
await new Promise((resolve) => {
|
||||
await new Promise(resolve => {
|
||||
setTimeout(() => resolve(), 1000);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import Error from 'next/error'
|
||||
import { useRouter } from 'next/router';
|
||||
import Error from 'next/error';
|
||||
|
||||
function loadArticle() {
|
||||
return {
|
||||
@@ -10,21 +10,22 @@ function loadArticle() {
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend.'
|
||||
}
|
||||
]
|
||||
}
|
||||
content:
|
||||
'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend.',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const Page = ({ path, article }) => {
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
|
||||
if (router.isFallback) {
|
||||
return <div>Loading...</div>
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (!article.content) {
|
||||
return <Error statusCode={404}/>
|
||||
return <Error statusCode={404} />;
|
||||
}
|
||||
|
||||
const [header, ...body] = article.content;
|
||||
@@ -34,13 +35,15 @@ const Page = ({ path, article }) => {
|
||||
<header>{header.content}</header>
|
||||
<small>path: {path.join('/')}</small>
|
||||
<main>
|
||||
{body.map(({ content }) => <p>{content}</p>)}
|
||||
{body.map(({ content }) => (
|
||||
<p>{content}</p>
|
||||
))}
|
||||
</main>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Page
|
||||
export default Page;
|
||||
|
||||
export async function getStaticProps({ params }) {
|
||||
const { path } = params;
|
||||
@@ -50,13 +53,13 @@ export async function getStaticProps({ params }) {
|
||||
props: {
|
||||
article,
|
||||
path,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
return {
|
||||
paths: [],
|
||||
fallback: true,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const fetch = require('node-fetch');
|
||||
const cheerio = require('cheerio');
|
||||
|
||||
module.exports = function(ctx) {
|
||||
module.exports = function (ctx) {
|
||||
it('should revalidate content properly from pathname', async () => {
|
||||
const res = await fetch(`${ctx.deploymentUrl}/another`);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
@@ -8,7 +8,7 @@ export async function getStaticProps() {
|
||||
random: Math.random(),
|
||||
time: new Date().getTime(),
|
||||
},
|
||||
unstable_revalidate: 1,
|
||||
revalidate: 1,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export async function getStaticProps() {
|
||||
world: 'world',
|
||||
time: new Date().getTime(),
|
||||
},
|
||||
unstable_revalidate: 5,
|
||||
revalidate: 5,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export async function getStaticPaths() {
|
||||
'/blog/post-1/comment-1',
|
||||
{ params: { post: 'post-2', comment: 'comment-2' } },
|
||||
'/blog/post-1337/comment-1337',
|
||||
'/blog/post-123/comment-321'
|
||||
'/blog/post-123/comment-321',
|
||||
],
|
||||
fallback: true,
|
||||
};
|
||||
@@ -22,7 +22,7 @@ export async function getStaticProps({ params }) {
|
||||
comment: params.comment,
|
||||
time: new Date().getTime(),
|
||||
},
|
||||
unstable_revalidate: 1,
|
||||
revalidate: 1,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ export default ({ post, comment, time, random }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<p id='post'>Post: {post}</p>
|
||||
<p id='comment'>Comment: {comment}</p>
|
||||
<span id='time'>time: {time}</span>
|
||||
<span id='random'>random: {random}</span>
|
||||
<p id="post">Post: {post}</p>
|
||||
<p id="comment">Comment: {comment}</p>
|
||||
<span id="time">time: {time}</span>
|
||||
<span id="random">random: {random}</span>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,11 +3,7 @@ import React from 'react';
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function getStaticPaths() {
|
||||
return {
|
||||
paths: [
|
||||
'/blog/post-1',
|
||||
{ params: { post: 'post-2' } },
|
||||
'/blog/post-123',
|
||||
],
|
||||
paths: ['/blog/post-1', { params: { post: 'post-2' } }, '/blog/post-123'],
|
||||
fallback: true,
|
||||
};
|
||||
}
|
||||
@@ -26,7 +22,7 @@ export async function getStaticProps({ params }) {
|
||||
random: Math.random(),
|
||||
time: (await import('perf_hooks')).performance.now(),
|
||||
},
|
||||
unstable_revalidate: 1,
|
||||
revalidate: 1,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,9 +31,9 @@ export default ({ post, time, random }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<p id='post'>Post: {post}</p>
|
||||
<span id='time'>time: {time}</span>
|
||||
<span id='random'>random: {random}</span>
|
||||
<p id="post">Post: {post}</p>
|
||||
<span id="time">time: {time}</span>
|
||||
<span id="random">random: {random}</span>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ export async function getStaticProps() {
|
||||
world: 'world',
|
||||
time: new Date().getTime(),
|
||||
},
|
||||
unstable_revalidate: false,
|
||||
revalidate: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export async function getStaticProps({ params }) {
|
||||
slug: params.slug,
|
||||
time: (await import('perf_hooks')).performance.now(),
|
||||
},
|
||||
unstable_revalidate: 10,
|
||||
revalidate: 10,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default () => 'hi'
|
||||
export default () => 'hi';
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default async (req, res) => res.json({ query: req.query })
|
||||
export default async (req, res) => res.json({ query: req.query });
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const Page = () => (
|
||||
<>
|
||||
<p>post: {useRouter().query.post}</p>
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
||||
Page.getInitialProps = () => ({ hello: 'world' })
|
||||
Page.getInitialProps = () => ({ hello: 'world' });
|
||||
|
||||
export default Page
|
||||
export default Page;
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<p>path: {useRouter().query['hello-world']?.join('/')}</p>
|
||||
)
|
||||
}
|
||||
return <p>path: {useRouter().query['hello-world']?.join('/')}</p>;
|
||||
};
|
||||
|
||||
export default Page
|
||||
export default Page;
|
||||
|
||||
export const getServerSideProps = () => {
|
||||
return {
|
||||
props: {
|
||||
hello: 'world'
|
||||
}
|
||||
}
|
||||
}
|
||||
hello: 'world',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default () => 'hi'
|
||||
export default () => 'hi';
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default function() {
|
||||
export default function () {
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ module.exports = function (ctx) {
|
||||
setCookieParser.splitCookiesString(res.headers.get('set-cookie'))
|
||||
);
|
||||
const bypassCookie = cookies.find(
|
||||
(cookie) => cookie.name === '__prerender_bypass'
|
||||
cookie => cookie.name === '__prerender_bypass'
|
||||
);
|
||||
const previewDataCookie = cookies.find(
|
||||
(cookie) => cookie.name === '__next_preview_data'
|
||||
cookie => cookie.name === '__next_preview_data'
|
||||
);
|
||||
|
||||
expect(bypassCookie).toBeDefined();
|
||||
@@ -38,10 +38,10 @@ module.exports = function (ctx) {
|
||||
setCookieParser.splitCookiesString(res.headers.get('set-cookie'))
|
||||
);
|
||||
const bypassCookie = cookies.find(
|
||||
(cookie) => cookie.name === '__prerender_bypass'
|
||||
cookie => cookie.name === '__prerender_bypass'
|
||||
);
|
||||
const previewDataCookie = cookies.find(
|
||||
(cookie) => cookie.name === '__next_preview_data'
|
||||
cookie => cookie.name === '__next_preview_data'
|
||||
);
|
||||
|
||||
expect(bypassCookie).toBeDefined();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const getStaticProps = (ctx) => {
|
||||
export const getStaticProps = ctx => {
|
||||
console.log('previewData', ctx.previewData);
|
||||
|
||||
return {
|
||||
@@ -12,7 +12,7 @@ export const getStaticProps = (ctx) => {
|
||||
|
||||
export const getStaticPaths = () => {
|
||||
return {
|
||||
paths: [['first'], ['second'], ['another', 'one']].map((rest) => ({
|
||||
paths: [['first'], ['second'], ['another', 'one']].map(rest => ({
|
||||
params: { rest },
|
||||
})),
|
||||
fallback: false,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const getStaticProps = (ctx) => {
|
||||
export const getStaticProps = ctx => {
|
||||
console.log('previewData', ctx.previewData);
|
||||
|
||||
return {
|
||||
|
||||
@@ -3,5 +3,5 @@ export default function Page() {
|
||||
}
|
||||
|
||||
export function getStaticProps() {
|
||||
return { props: {}, unstable_revalidate: 10 };
|
||||
return { props: {}, revalidate: 10 };
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/node",
|
||||
"version": "1.7.2",
|
||||
"version": "1.7.3",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
|
||||
@@ -33,7 +33,7 @@
|
||||
"@types/etag": "1.8.0",
|
||||
"@types/test-listen": "1.1.0",
|
||||
"@zeit/ncc": "0.20.4",
|
||||
"@zeit/node-file-trace": "0.6.5",
|
||||
"@zeit/node-file-trace": "0.8.0",
|
||||
"content-type": "1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
"etag": "1.8.1",
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
// @ts-ignore - `@types/mkdirp-promise` is broken
|
||||
import mkdirp from 'mkdirp-promise';
|
||||
import once from '@tootallnate/once';
|
||||
import nodeFileTrace from '@zeit/node-file-trace';
|
||||
import { nodeFileTrace } from '@zeit/node-file-trace';
|
||||
import buildUtils from './build-utils';
|
||||
import {
|
||||
File,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user