Compare commits

..

21 Commits

Author SHA1 Message Date
Joe Haddad
fb4d4b5953 Publish Stable
- @now/build-utils@2.1.0
 - @now/next@2.4.0
 - @now/routing-utils@1.7.0
2020-02-28 10:19:47 -05:00
Joe Haddad
de3701c045 Publish Canary
- @now/ruby@1.0.3-canary.1
2020-02-28 10:18:46 -05:00
m5o
9f9b7934cb [now-ruby] Fix ruby test indentation (#3854)
* fix ruby indentation example
  * 💅 indent with 2 spaces for consistency
2020-02-28 14:34:47 +00:00
Steven
bcded1dd17 Publish Canary
- @now/build-utils@2.0.1-canary.3
 - @now/next@2.3.19-canary.2
 - @now/python@1.1.5-canary.1
2020-02-28 08:57:36 -05:00
Steven
8503af75ba [now-python] Fix space encoding 2020-02-28 08:35:27 -05:00
Joe Haddad
158a50f1aa [now-next] Allow Immutable Fallback Artifact (#3850)
* [now-next] Allow Immutable Fallback Artifact

* trigger
2020-02-27 22:00:09 -05:00
Steven
61da552dd6 [now-build-utils] Add test for NODE_ENV behavior (#3852)
This PR adds a test for #3847.
2020-02-27 21:47:27 +00:00
Steven
fa838eecac Publish Canary
- @now/go@1.0.5-canary.0
 - @now/next@2.3.19-canary.1
 - @now/node@1.4.2-canary.0
 - @now/python@1.1.5-canary.0
 - @now/ruby@1.0.3-canary.0
 - @now/static-build@0.14.13-canary.1
2020-02-27 15:52:36 -05:00
Steven
71b6a58783 [all] Exclude @now/build-utils in bundled builder to fix tests (#3848)
This fixes a bug in our `@now/build-utils` tests that pair the current build-utils with a stable builder. Since ncc was bundling `@now/build-utils`, we weren't able to configure a different version and these tests were not actually testing the correct version of build-utils.

A nice side-effect is that each builder will be about 50% smaller (compared by measuring `dist`).
2020-02-27 15:48:03 -05:00
Joe Haddad
22dd78e286 Publish Canary
- @now/build-utils@2.0.1-canary.2
 - @now/next@2.3.19-canary.0
2020-02-27 12:35:52 -05:00
Joe Haddad
e63fcf2630 [now-next] Add Support for Prerender v2 (#3845)
* [now-next] Add Support for Prerender v2

* Copy test suite

* Test that fallback doesn't work for fallback: false

* record omitted lambdas

* Improve test case

* improve omitted routes logic

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-02-27 12:33:31 -05:00
Steven
98e1553c2e [now-build-utils] Install all deps regardless of NODE_ENV (#3847)
Some build utilities and SSG Frameworks instruct users to set `NODE_ENV=production` which typically means updating `now.json` to the following:

```json
{
  "build": {
    "env": {
      "NODE_ENV": "production"
    }
  }
}
```

The problem is that this environment variable is assigned during `npm install` or `yarn install` which is the equivalent of running install with the `--production` flag. This flag prevents `devDependencies` from installing. This is almost never what the user intends so they have to remove `now.json` and instead updating their build script to `NODE_ENV=production yarn build`.

This PR improves the experience by deleting `NODE_ENV` during the install step.
2020-02-27 12:14:06 -05:00
Steven
4dc506f17a Publish Canary
- @now/build-utils@2.0.1-canary.1
 - now@17.0.5-canary.5
 - @now/routing-utils@1.6.1-canary.1
2020-02-27 08:05:05 -05:00
Steven
3b396f29e9 [now-build-utils][now-routing-utils] Fix appendRoutesToPhase() when input is null (#3843)
This PR fixes `@now/routing-utils` when the input routes are null. It used to return the empty array but now it will still append.

I also added many more tests to `@now/build-utils` when `featureHandleMiss: true` and refactored the code a bit to make dynamic routes and api routes a little more clear.
2020-02-26 23:30:50 +00:00
JJ Kasper
1f128e69e6 Publish Stable
- @now/next@2.3.18
2020-02-26 15:56:41 -06:00
JJ Kasper
f1487c92cb Publish Canary
- now@17.0.5-canary.4
 - @now/next@2.3.18-canary.0
2020-02-26 15:51:27 -06:00
JJ Kasper
45066cdf44 [now-next] Add /_next/data routes for getServerProps pages (#3771)
This adds the `/_next/data` routes for `getServerProps` pages if they exist

x-ref: https://github.com/zeit/next.js/pull/10077
x-ref: https://github.com/zeit/next.js/pull/10622
2020-02-26 21:47:51 +00:00
Ana Trajkovska
7dde9c8207 Publish Canary
- now@17.0.5-canary.3
 - now-client@7.0.1-canary.1
2020-02-26 08:00:09 +01:00
Ana Trajkovska
fd964f825d Handle deployment cancellation (#3823)
Co-authored-by: Max <8418866+rdev@users.noreply.github.com>
2020-02-26 07:21:27 +01:00
Steven
1656c9874e Publish Canary
- @now/build-utils@2.0.1-canary.0
 - now@17.0.5-canary.2
 - @now/routing-utils@1.6.1-canary.0
2020-02-25 17:32:37 -05:00
Steven
d999a3b2ad [now-routing-utils][now-build-utils] Refactor zero-config rewrites (#3832)
This PR refactors the rewrites (the dynamic routes as well as the route that prevents directory listing for zero config deployments) so they are not in the `handle: miss` phase.

This is necessary because the behavior of `handle: miss` will change in an upcoming release.

The solution is to separate these into `rewriteRoutes` that can then be merged properly with the user's routes. They will be appended to the `handle: filesystem` phase (or add the phase if it doesn't exist).
2020-02-25 22:30:36 +00:00
86 changed files with 2672 additions and 412 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@now/build-utils",
"version": "2.0.0",
"version": "2.1.0",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",

View File

@@ -80,6 +80,7 @@ export async function detectBuilders(
warnings: ErrorResponse[];
defaultRoutes: Route[] | null;
redirectRoutes: Route[] | null;
rewriteRoutes: Route[] | null;
}> {
const errors: ErrorResponse[] = [];
const warnings: ErrorResponse[] = [];
@@ -87,9 +88,6 @@ export async function detectBuilders(
const apiBuilders: Builder[] = [];
let frontendBuilder: Builder | null = null;
const defaultRoutes: Route[] = [];
const redirectRoutes: Route[] = [];
const functionError = validateFunctions(options);
if (functionError) {
@@ -99,6 +97,7 @@ export async function detectBuilders(
warnings,
defaultRoutes: null,
redirectRoutes: null,
rewriteRoutes: null,
};
}
@@ -131,15 +130,15 @@ export async function detectBuilders(
let fallbackEntrypoint: string | null = null;
const preDefaultRoutes: Source[] = [];
const preDynamicRoutes: Source[] = [];
const apiRoutes: Source[] = [];
const dynamicRoutes: Source[] = [];
// API
for (const fileName of sortedFiles) {
const apiBuilder = maybeGetApiBuilder(fileName, apiMatches, options);
if (apiBuilder) {
const { routeError, defaultRoute, dynamicRoute } = getApiRoute(
const { routeError, apiRoute, isDynamic } = getApiRoute(
fileName,
apiSortedFiles,
options,
@@ -153,15 +152,15 @@ export async function detectBuilders(
warnings,
defaultRoutes: null,
redirectRoutes: null,
rewriteRoutes: null,
};
}
if (dynamicRoute) {
preDynamicRoutes.push(dynamicRoute);
}
if (defaultRoute) {
preDefaultRoutes.push(defaultRoute);
if (apiRoute) {
apiRoutes.push(apiRoute);
if (isDynamic) {
dynamicRoutes.push(apiRoute);
}
}
addToUsedFunctions(apiBuilder);
@@ -224,6 +223,7 @@ export async function detectBuilders(
builders: null,
redirectRoutes: null,
defaultRoutes: null,
rewriteRoutes: null,
};
}
@@ -264,6 +264,7 @@ export async function detectBuilders(
warnings,
redirectRoutes: null,
defaultRoutes: null,
rewriteRoutes: null,
};
}
@@ -285,24 +286,22 @@ export async function detectBuilders(
}
}
const routesResult = mergeRoutes(
preDefaultRoutes,
preDynamicRoutes,
const routesResult = getRouteResult(
apiRoutes,
dynamicRoutes,
usedOutputDirectory,
apiBuilders,
frontendBuilder,
options
);
defaultRoutes.push(...routesResult.defaultRoutes);
redirectRoutes.push(...routesResult.redirectRoutes);
return {
warnings,
builders: builders.length ? builders : null,
errors: errors.length ? errors : null,
redirectRoutes,
defaultRoutes,
redirectRoutes: routesResult.redirectRoutes,
defaultRoutes: routesResult.defaultRoutes,
rewriteRoutes: routesResult.rewriteRoutes,
};
}
@@ -622,16 +621,16 @@ function getApiRoute(
options: Options,
absolutePathCache: Map<string, string>
): {
defaultRoute: Source | null;
dynamicRoute: Source | null;
apiRoute: Source | null;
isDynamic: boolean;
routeError: ErrorResponse | null;
} {
const conflictingSegment = getConflictingSegment(fileName);
if (conflictingSegment) {
return {
defaultRoute: null,
dynamicRoute: null,
apiRoute: null,
isDynamic: false,
routeError: {
code: 'conflicting_path_segment',
message:
@@ -650,8 +649,8 @@ function getApiRoute(
);
return {
defaultRoute: null,
dynamicRoute: null,
apiRoute: null,
isDynamic: false,
routeError: {
code: 'conflicting_file_path',
message:
@@ -669,8 +668,8 @@ function getApiRoute(
);
return {
defaultRoute: out.route,
dynamicRoute: out.isDynamic ? out.route : null,
apiRoute: out.route,
isDynamic: out.isDynamic,
routeError: null,
};
}
@@ -835,10 +834,10 @@ function createRouteFromPath(
} else if (isLast) {
const { name: fileName, ext } = parsePath(segment);
const isIndex = fileName === 'index';
const prefix = isIndex ? '\\/' : '';
const prefix = isIndex ? '/' : '';
const names = [
isIndex ? prefix : `${fileName}\\/`,
isIndex ? prefix : `${fileName}/`,
prefix + escapeName(fileName),
featHandleMiss && cleanUrls
? ''
@@ -881,9 +880,9 @@ function createRouteFromPath(
return { route, isDynamic };
}
function mergeRoutes(
preDefaultRoutes: Source[],
preDynamicRoutes: Source[],
function getRouteResult(
apiRoutes: Source[],
dynamicRoutes: Source[],
outputDirectory: string,
apiBuilders: Builder[],
frontendBuilder: Builder | null,
@@ -891,14 +890,14 @@ function mergeRoutes(
): {
defaultRoutes: Route[];
redirectRoutes: Route[];
rewriteRoutes: Route[];
} {
const defaultRoutes: Route[] = [];
const redirectRoutes: Route[] = [];
const rewriteRoutes: Route[] = [];
if (preDefaultRoutes && preDefaultRoutes.length > 0) {
if (apiRoutes && apiRoutes.length > 0) {
if (options.featHandleMiss) {
defaultRoutes.push({ handle: 'miss' });
const extSet = detectApiExtensions(apiBuilders);
if (extSet.size > 0) {
@@ -923,6 +922,7 @@ function mergeRoutes(
status: 308,
});
} else {
defaultRoutes.push({ handle: 'miss' });
defaultRoutes.push({
src: `^/api/(.+)${extGroup}$`,
dest: '/api/$1',
@@ -931,21 +931,16 @@ function mergeRoutes(
}
}
if (preDynamicRoutes) {
defaultRoutes.push(...preDynamicRoutes);
}
if (preDefaultRoutes.length) {
defaultRoutes.push({
src: '^/api(/.*)?$',
status: 404,
continue: true,
});
}
rewriteRoutes.push(...dynamicRoutes);
rewriteRoutes.push({
src: '^/api(/.*)?$',
status: 404,
continue: true,
});
} else {
defaultRoutes.push(...preDefaultRoutes);
defaultRoutes.push(...apiRoutes);
if (preDefaultRoutes.length) {
if (apiRoutes.length) {
defaultRoutes.push({
status: 404,
src: '^/api(/.*)?$',
@@ -969,6 +964,7 @@ function mergeRoutes(
return {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
};
}

View File

@@ -206,10 +206,10 @@ export async function runNpmInstall(
debug(`Installing to ${destPath}`);
const { hasPackageLockJson } = await scanParentDirs(destPath);
const opts = { cwd: destPath, ...spawnOpts } || {
cwd: destPath,
env: process.env,
};
const opts: SpawnOptions = { cwd: destPath, ...spawnOpts };
const env = opts.env || { ...process.env };
delete env.NODE_ENV;
opts.env = env;
if (hasPackageLockJson) {
commandArgs = args.filter(a => a !== '--prefer-offline');
@@ -239,10 +239,7 @@ export async function runBundleInstall(
}
assert(path.isAbsolute(destPath));
const opts = { cwd: destPath, ...spawnOpts } || {
cwd: destPath,
env: process.env,
};
const opts = { cwd: destPath, ...spawnOpts };
await spawnAsync(
'bundle',
@@ -270,10 +267,7 @@ export async function runPipInstall(
}
assert(path.isAbsolute(destPath));
const opts = { cwd: destPath, ...spawnOpts } || {
cwd: destPath,
env: process.env,
};
const opts = { cwd: destPath, ...spawnOpts };
await spawnAsync(
'pip3',

View File

@@ -4,7 +4,7 @@ import FileRef from './file-ref';
import { Lambda } from './lambda';
interface PrerenderOptions {
expiration: number;
expiration: number | false;
lambda: Lambda;
fallback: FileBlob | FileFsRef | FileRef | null;
group?: number;
@@ -13,7 +13,7 @@ interface PrerenderOptions {
export class Prerender {
public type: 'Prerender';
public expiration: number;
public expiration: number | false;
public lambda: Lambda;
public fallback: FileBlob | FileFsRef | FileRef | null;
public group?: number;

View File

@@ -0,0 +1,21 @@
function checkPkgOrThrow(pkgname) {
try {
const dep = require(pkgname);
if (!dep) {
throw new Error('Undefined');
}
} catch (e) {
console.error(`Expected package "${pkgname}" to be installed.`);
process.exit(1);
}
}
// We expect both `dependencies` and `devDependencies` to be installed
// even when NODE_ENV=production.
checkPkgOrThrow('tls-check');
checkPkgOrThrow('exeggcute');
// This is to satisfy `@now/static-build` which needs a `dist` directory.
const { exec } = require('exeggcute');
exec('mkdir dist', __dirname);
exec('echo "node-env:RANDOMNESS_PLACEHOLDER" > dist/index.html', __dirname);

View File

@@ -0,0 +1,6 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/static-build" }],
"build": { "env": { "NODE_ENV": "production" } },
"probes": [{ "path": "/", "mustContain": "node-env:RANDOMNESS_PLACEHOLDER" }]
}

View File

@@ -0,0 +1,12 @@
{
"private": true,
"scripts": {
"build": "node build.js"
},
"dependencies": {
"tls-check": "1.0.0"
},
"devDependencies": {
"exeggcute": "1.0.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "now",
"version": "17.0.5-canary.1",
"version": "17.0.5-canary.5",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Now",

View File

@@ -566,6 +566,11 @@ export default async function main(
return 1;
}
if (deployment.readyState === 'CANCELED') {
output.log('The deployment has been canceled');
return 1;
}
const deploymentResponse = await getDeploymentByIdOrHost(
now,
contextName,

View File

@@ -202,6 +202,13 @@ export default async function processDeployment({
}
}
if (event.type === 'canceled') {
if (buildSpinner) {
buildSpinner();
}
return event.payload;
}
if (event.type === 'ready') {
if (queuedSpinner) {
queuedSpinner();

View File

@@ -133,6 +133,9 @@ export async function devRouter(
);
if (missResult.found) {
return missResult;
} else {
reqPathname = destPath;
continue;
}
} else {
if (routeConfig.status && phase === 'miss') {

View File

@@ -12,7 +12,11 @@ import serveHandler from 'serve-handler';
import { watch, FSWatcher } from 'chokidar';
import { parse as parseDotenv } from 'dotenv';
import { basename, dirname, extname, join } from 'path';
import { getTransformedRoutes, HandleValue } from '@now/routing-utils';
import {
getTransformedRoutes,
appendRoutesToPhase,
HandleValue,
} from '@now/routing-utils';
import directoryTemplate from 'serve-handler/src/directory';
import getPort from 'get-port';
import { ChildProcess } from 'child_process';
@@ -553,6 +557,7 @@ export default class DevServer {
errors,
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, pkg, {
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest',
functions: config.functions,
@@ -582,7 +587,13 @@ export default class DevServer {
const routes: RouteConfig[] = [];
const { routes: nowConfigRoutes } = config;
routes.push(...(redirectRoutes || []));
routes.push(...(nowConfigRoutes || []));
routes.push(
...appendRoutesToPhase({
routes: nowConfigRoutes,
newRoutes: rewriteRoutes,
phase: 'filesystem',
})
);
routes.push(...(defaultRoutes || []));
config.routes = routes;
}
@@ -1176,6 +1187,35 @@ export default class DevServer {
}
};
/**
* This is the equivalent to now-proxy exit_with_status() function.
*/
exitWithStatus = async (
match: BuildMatch | null,
routeResult: RouteResult,
phase: HandleValue | null,
req: http.IncomingMessage,
res: http.ServerResponse,
nowRequestId: string
): Promise<boolean> => {
const { status, headers, dest } = routeResult;
const location = headers['location'] || dest;
if (status && location && (300 <= status && status <= 399)) {
this.output.debug(`Route found with redirect status code ${status}`);
await this.sendRedirect(req, res, nowRequestId, location, status);
return true;
}
if (!match && status && phase !== 'miss') {
this.output.debug(`Route found with with status code ${status}`);
await this.sendError(req, res, nowRequestId, '', status);
return true;
}
return false;
};
/**
* Serve project directory as a Now v2 deployment.
*/
@@ -1259,6 +1299,19 @@ export default class DevServer {
this
);
if (
await this.exitWithStatus(
match,
routeResult,
phase,
req,
res,
nowRequestId
)
) {
return;
}
if (!match && missRoutes.length > 0) {
// Since there was no build match, enter the miss phase
routeResult = await devRouter(
@@ -1277,6 +1330,18 @@ export default class DevServer {
routeResult.dest,
this
);
if (
await this.exitWithStatus(
match,
routeResult,
phase,
req,
res,
nowRequestId
)
) {
return;
}
} else if (match && hitRoutes.length > 0) {
// Since there was a build match, enter the hit phase.
// The hit phase must not set status code.
@@ -1295,28 +1360,6 @@ export default class DevServer {
statusCode = routeResult.status;
if (match && statusCode === 404 && routeResult.phase === 'miss') {
statusCode = undefined;
}
const location = routeResult.headers['location'] || routeResult.dest;
if (statusCode && location && (300 <= statusCode && statusCode <= 399)) {
// Equivalent to now-proxy exit_with_status() function
this.output.debug(
`Route found with redirect status code ${statusCode}`
);
await this.sendRedirect(req, res, nowRequestId, location, statusCode);
return;
}
if (!match && statusCode && routeResult.phase !== 'miss') {
// Equivalent to now-proxy exit_with_status() function
this.output.debug(`Route found with with status code ${statusCode}`);
await this.sendError(req, res, nowRequestId, '', statusCode);
return;
}
if (match) {
// end the phase
break;

View File

@@ -1,10 +0,0 @@
{
"version": 2,
"routes": [
{ "handle": "filesystem" },
{ "src": "/([^/]+/dir/.+)", "dest": "/$1.html", "check": true },
{ "handle": "miss" },
{ "src": "/pathA(?:/.+)?", "status": 404, "continue": true },
{ "src": "/pathB(?:/.+)?", "status": 404, "continue": true }
]
}

View File

@@ -10,14 +10,14 @@
"dest": "/blog/$1",
"check": true
},
{
"handle": "miss"
},
{
"src": "/.*",
"status": 404,
"continue": true
},
{
"handle": "miss"
},
{
"src": "/(.*)",
"dest": "/src/$1",

View File

@@ -1,8 +0,0 @@
{
"version": 2,
"routes": [
{ "handle": "miss" },
{ "src": "/pathA(?:/.+)?", "status": 404, "continue": true },
{ "src": "/pathB(?:/.+)?", "status": 404, "continue": true }
]
}

View File

@@ -1,7 +0,0 @@
{
"functions": {
"server/**/*.js": {
"runtime": "@now/node@1.2.1"
}
}
}

View File

@@ -1,3 +0,0 @@
export default (req, res) => {
res.end(`current hour: ${Math.floor(Date.now() / 10000)}`);
};

View File

@@ -0,0 +1,2 @@
module.exports = (req, res) =>
res.send(req.query.username);

View File

@@ -0,0 +1 @@
module.exports = (req, res) => res.end('42');

View File

@@ -0,0 +1,2 @@
module.exports = (req, res) =>
res.send(req.query.id);

View File

@@ -0,0 +1,3 @@
{
"rewrites": [{ "source": "/rand", "destination": "/api/fourty-two" }]
}

View File

@@ -411,28 +411,6 @@ test(
})
);
test(
'[now dev] does not display directory listing after multiple 404',
testFixtureStdio('handle-miss-multiple-404', async (t, port) => {
t.is((await fetch(`http://localhost:${port}/pathA/dir`)).status, 404);
t.is((await fetch(`http://localhost:${port}/pathB/dir`)).status, 404);
t.is((await fetch(`http://localhost:${port}/pathC/dir`)).status, 200);
})
);
test(
'[now dev] does not display directory listing after `handle: miss` and 404',
testFixtureStdio('handle-miss-handle-filesystem-404', async (t, port) => {
t.is((await fetch(`http://localhost:${port}/pathA/dir`)).status, 404);
t.is((await fetch(`http://localhost:${port}/pathB/dir`)).status, 404);
t.is((await fetch(`http://localhost:${port}/pathC/dir`)).status, 200);
t.is((await fetch(`http://localhost:${port}/pathA/dir/one`)).status, 200);
t.is((await fetch(`http://localhost:${port}/pathB/dir/two`)).status, 200);
t.is((await fetch(`http://localhost:${port}/pathC/dir/three`)).status, 200);
})
);
test(
'[now dev] handles hit after handle: filesystem',
testFixtureStdio('handle-hit-after-fs', async (t, port) => {
@@ -483,6 +461,8 @@ test(
t.regex(await rand.text(), /random number/gm);
const rand2 = await fetchWithRetry(`http://localhost:${port}/api/rand.js`);
t.regex(await rand2.text(), /random number/gm);
const notfound = await fetch(`http://localhost:${port}/api`);
t.is(notfound.status, 404);
})
);
@@ -606,6 +586,31 @@ test('[now dev] validate env var names', async t => {
t.pass();
});
test(
'[now dev] test rewrites with segments serve correct content',
testFixtureStdio('test-rewrites-with-segments', async (t, port) => {
const users = await fetchWithRetry(
`http://localhost:${port}/api/users/first`,
3
);
t.regex(await users.text(), /first/gm);
const fourtytwo = await fetchWithRetry(
`http://localhost:${port}/api/fourty-two`,
3
);
t.regex(await fourtytwo.text(), /42/gm);
const rand = await fetchWithRetry(`http://localhost:${port}/rand`, 3);
t.regex(await rand.text(), /42/gm);
const dynamic = await fetchWithRetry(
`http://localhost:${port}/api/dynamic`,
3
);
t.regex(await dynamic.text(), /dynamic/gm);
const notfound = await fetch(`http://localhost:${port}/api`);
t.is(notfound.status, 404);
})
);
test(
'[now dev] test rewrites serve correct content',
testFixtureStdio('test-rewrites', async (t, port) => {

View File

@@ -69,9 +69,7 @@ const getStaticFiles = async (dir, isBuilds = false) => {
const normalizeWindowsPaths = files => {
if (process.platform === 'win32') {
const prefix = 'D:/a/now/now/packages/now-cli/test/fixtures/unit/';
return files.map(f =>
f.replace(/\\/g, '/').slice(prefix.length)
);
return files.map(f => f.replace(/\\/g, '/').slice(prefix.length));
}
return files;
};

View File

@@ -1,6 +1,6 @@
{
"name": "now-client",
"version": "7.0.1-canary.0",
"version": "7.0.1-canary.1",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://zeit.co",

View File

@@ -77,6 +77,15 @@ export async function* checkDeploymentStatus(
yield { type: 'building', payload: deploymentUpdate };
}
if (
deploymentUpdate.readyState === 'CANCELED' &&
!finishedEvents.has('canceled')
) {
debug('Deployment state changed to CANCELED');
finishedEvents.add('canceled');
yield { type: 'canceled', payload: deploymentUpdate };
}
if (isReady(deploymentUpdate) && !finishedEvents.has('ready')) {
debug('Deployment state changed to READY');
finishedEvents.add('ready');

View File

@@ -29,6 +29,7 @@ const EVENTS_ARRAY = [
'error',
'notice',
'tip',
'canceled',
] as const;
export type DeploymentEventType = (typeof EVENTS_ARRAY)[number];

View File

@@ -1,4 +1,4 @@
ncc build index.ts -o dist
ncc build install.ts -o dist/install
ncc build index.ts -e @now/build-utils -o dist
ncc build install.ts -e @now/build-utils -o dist/install
mv dist/install/index.js dist/install.js
rm -rf dist/install

View File

@@ -1,6 +1,6 @@
{
"name": "@now/go",
"version": "1.0.4",
"version": "1.0.5-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/go",

View File

@@ -7,10 +7,10 @@ cp -v "$bridge_defs" src/now__bridge.ts
tsc
ncc build src/dev-server.ts -o dist/dev
ncc build src/dev-server.ts -e @now/build-utils -o dist/dev
mv dist/dev/index.js dist/dev-server.js
rm -rf dist/dev
ncc build src/index.ts -o dist/main
ncc build src/index.ts -e @now/build-utils -o dist/main
mv dist/main/index.js dist/index.js
rm -rf dist/main

View File

@@ -1,6 +1,6 @@
{
"name": "@now/next",
"version": "2.3.17",
"version": "2.4.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/next-js",

View File

@@ -18,7 +18,7 @@ import {
runNpmInstall,
runPackageJsonScript,
} from '@now/build-utils';
import { Route, Source } from '@now/routing-utils';
import { Route } from '@now/routing-utils';
import {
convertHeaders,
convertRedirects,
@@ -336,11 +336,17 @@ export const build = async ({
env.NODE_OPTIONS = `--max_old_space_size=${memoryToConsume}`;
await runPackageJsonScript(entryPath, shouldRunScript, { ...spawnOpts, env });
const appMountPrefixNoTrailingSlash = path.posix
.join('/', entryDirectory)
.replace(/\/+$/, '');
const routesManifest = await getRoutesManifest(entryPath, realNextVersion);
const prerenderManifest = await getPrerenderManifest(entryPath);
const headers: Route[] = [];
const rewrites: Route[] = [];
const redirects: Route[] = [];
const nextBasePathRoute: Route[] = [];
const dataRoutes: Route[] = [];
let nextBasePath: string | undefined;
// whether they have enabled pages/404.js as the custom 404 page
let hasPages404 = false;
@@ -356,6 +362,40 @@ export const build = async ({
headers.push(...convertHeaders(routesManifest.headers));
}
if (routesManifest.dataRoutes) {
// Load the /_next/data routes for both dynamic SSG and SSP pages.
// These must be combined and sorted to prevent conflicts
for (const dataRoute of routesManifest.dataRoutes) {
const ssgDataRoute =
prerenderManifest.fallbackRoutes[dataRoute.page] ||
prerenderManifest.legacyBlockingRoutes[dataRoute.page];
// we don't need to add routes for non-lazy SSG routes since
// they have outputs which would override the routes anyways
if (
prerenderManifest.staticRoutes[dataRoute.page] ||
prerenderManifest.omittedRoutes.includes(dataRoute.page)
) {
continue;
}
dataRoutes.push({
src: dataRoute.dataRouteRegex.replace(
/^\^/,
`^${appMountPrefixNoTrailingSlash}`
),
dest: path.join(
'/',
entryDirectory,
// make sure to route SSG data route to the data prerender
// output, we don't do this for SSP routes since they don't
// have a separate data output
(ssgDataRoute && ssgDataRoute.dataRoute) || dataRoute.page
),
});
}
}
if (routesManifest.pages404) {
hasPages404 = true;
}
@@ -521,13 +561,8 @@ export const build = async ({
const prerenders: { [key: string]: Prerender | FileFsRef } = {};
const staticPages: { [key: string]: FileFsRef } = {};
const dynamicPages: string[] = [];
const dynamicDataRoutes: Array<Source> = [];
let static404Page: string | undefined;
const appMountPrefixNoTrailingSlash = path.posix
.join('/', entryDirectory)
.replace(/\/+$/, '');
if (isLegacy) {
const filesAfterBuild = await glob('**', entryPath);
@@ -629,7 +664,6 @@ export const build = async ({
const pages = await glob('**/*.js', pagesDir);
const staticPageFiles = await glob('**/*.html', pagesDir);
const prerenderManifest = await getPrerenderManifest(entryPath);
Object.keys(staticPageFiles).forEach((page: string) => {
const pathname = page.replace(/\.html$/, '');
@@ -640,9 +674,8 @@ export const build = async ({
// Lazily prerendered routes have a fallback `.html` file on newer
// Next.js versions so we need to also not treat it as a static page here.
if (
prerenderManifest.routes[routeName] ||
(prerenderManifest.lazyRoutes[routeName] &&
prerenderManifest.lazyRoutes[routeName].fallback)
prerenderManifest.staticRoutes[routeName] ||
prerenderManifest.fallbackRoutes[routeName]
) {
return;
}
@@ -884,33 +917,49 @@ export const build = async ({
}
let prerenderGroup = 1;
const onPrerenderRoute = (routeKey: string, isLazy: boolean) => {
const onPrerenderRoute = (
routeKey: string,
{ isBlocking, isFallback }: { isBlocking: boolean; isFallback: boolean }
) => {
if (isBlocking && isFallback) {
throw new Error(
'invariant: isBlocking and isFallback cannot both be true'
);
}
// Get the route file as it'd be mounted in the builder output
const routeFileNoExt = routeKey === '/' ? '/index' : routeKey;
const lazyHtmlFallback =
isLazy && prerenderManifest.lazyRoutes[routeKey].fallback;
const htmlFsRef =
isLazy && !lazyHtmlFallback
const htmlFsRef = isBlocking
? // Blocking pages do not have an HTML fallback
null
: new FileFsRef({
fsPath: path.join(
pagesDir,
isFallback
? // Fallback pages have a special file.
prerenderManifest.fallbackRoutes[routeKey].fallback
: // Otherwise, the route itself should exist as a static HTML
// file.
`${routeFileNoExt}.html`
),
});
const jsonFsRef =
// JSON data does not exist for fallback or blocking pages
isFallback || isBlocking
? null
: new FileFsRef({
fsPath: path.join(
pagesDir,
`${lazyHtmlFallback || routeFileNoExt + '.html'}`
),
fsPath: path.join(pagesDir, `${routeFileNoExt}.json`),
});
const jsonFsRef = isLazy
? null
: new FileFsRef({
fsPath: path.join(pagesDir, `${routeFileNoExt}.json`),
});
let initialRevalidate: false | number;
let srcRoute: string | null;
let dataRoute: string;
if (isLazy) {
const pr = prerenderManifest.lazyRoutes[routeKey];
if (isFallback || isBlocking) {
const pr = isFallback
? prerenderManifest.fallbackRoutes[routeKey]
: prerenderManifest.legacyBlockingRoutes[routeKey];
initialRevalidate = 1; // TODO: should Next.js provide this default?
// @ts-ignore
if (initialRevalidate === false) {
@@ -920,7 +969,7 @@ export const build = async ({
srcRoute = null;
dataRoute = pr.dataRoute;
} else {
const pr = prerenderManifest.routes[routeKey];
const pr = prerenderManifest.staticRoutes[routeKey];
({ initialRevalidate, srcRoute, dataRoute } = pr);
}
@@ -946,20 +995,14 @@ export const build = async ({
}
prerenders[outputPathPage] = new Prerender({
expiration:
initialRevalidate === false
? MAX_AGE_ONE_YEAR * 10
: initialRevalidate,
expiration: initialRevalidate,
lambda,
fallback: htmlFsRef,
group: prerenderGroup,
bypassToken: prerenderManifest.bypassToken,
});
prerenders[outputPathData] = new Prerender({
expiration:
initialRevalidate === false
? MAX_AGE_ONE_YEAR * 10
: initialRevalidate,
expiration: initialRevalidate,
lambda,
fallback: jsonFsRef,
group: prerenderGroup,
@@ -969,25 +1012,35 @@ export const build = async ({
++prerenderGroup;
};
Object.keys(prerenderManifest.routes).forEach(route =>
onPrerenderRoute(route, false)
Object.keys(prerenderManifest.staticRoutes).forEach(route =>
onPrerenderRoute(route, { isBlocking: false, isFallback: false })
);
Object.keys(prerenderManifest.lazyRoutes).forEach(route =>
onPrerenderRoute(route, true)
Object.keys(prerenderManifest.fallbackRoutes).forEach(route =>
onPrerenderRoute(route, { isBlocking: false, isFallback: true })
);
Object.keys(prerenderManifest.legacyBlockingRoutes).forEach(route =>
onPrerenderRoute(route, { isBlocking: true, isFallback: false })
);
// Dynamic pages for lazy routes should be handled by the lambda flow.
Object.keys(prerenderManifest.lazyRoutes).forEach(lazyRoute => {
const { dataRouteRegex, dataRoute } = prerenderManifest.lazyRoutes[
lazyRoute
];
dynamicDataRoutes.push({
// Next.js provided data route regex
src: dataRouteRegex.replace(/^\^/, `^${appMountPrefixNoTrailingSlash}`),
// Location of lambda in builder output
dest: path.posix.join(entryDirectory, dataRoute),
// We still need to use lazyRoutes if the dataRoutes field
// isn't available for backwards compatibility
if (!(routesManifest && routesManifest.dataRoutes)) {
// Dynamic pages for lazy routes should be handled by the lambda flow.
[
...Object.entries(prerenderManifest.fallbackRoutes),
...Object.entries(prerenderManifest.legacyBlockingRoutes),
].forEach(([, { dataRouteRegex, dataRoute }]) => {
dataRoutes.push({
// Next.js provided data route regex
src: dataRouteRegex.replace(
/^\^/,
`^${appMountPrefixNoTrailingSlash}`
),
// Location of lambda in builder output
dest: path.posix.join(entryDirectory, dataRoute),
});
});
});
}
}
const nextStaticFiles = await glob(
@@ -1032,7 +1085,8 @@ export const build = async ({
entryDirectory,
dynamicPages,
false,
routesManifest
routesManifest,
new Set(prerenderManifest.omittedRoutes)
).then(arr =>
arr.map(route => {
route.src = route.src.replace('^', `^${dynamicPrefix}`);
@@ -1040,6 +1094,23 @@ 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 => {
// Get the route file as it'd be mounted in the builder output
const routeFileNoExt = path.posix.join(
entryDirectory,
routeKey === '/' ? '/index' : routeKey
);
if (typeof lambdas[routeFileNoExt] === undefined) {
throw new Error(
`invariant: unknown lambda ${routeKey} (lookup: ${routeFileNoExt}) | please report this immediately`
);
}
delete lambdas[routeFileNoExt];
});
return {
output: {
...publicDirectoryFiles,
@@ -1086,6 +1157,7 @@ export const build = async ({
continue: true,
},
{ src: path.join('/', entryDirectory, '_next(?!/data(?:/|$))(?:/.*)?') },
// Next.js page lambdas, `static/` folder, reserved assets, and `public/`
// folder
{ handle: 'filesystem' },
@@ -1107,7 +1179,10 @@ export const build = async ({
...rewrites,
// Dynamic routes
...dynamicRoutes,
...dynamicDataRoutes,
// /_next/data routes for getServerProps/getStaticProps pages
...dataRoutes,
// Custom Next.js 404 page (TODO: do we want to remove this?)
...(isLegacy
? []

View File

@@ -316,6 +316,7 @@ export type RoutesManifest = {
regex: string;
}[];
version: number;
dataRoutes?: Array<{ page: string; dataRouteRegex: string }>;
};
export async function getRoutesManifest(
@@ -353,7 +354,8 @@ export async function getDynamicRoutes(
entryDirectory: string,
dynamicPages: string[],
isDev?: boolean,
routesManifest?: RoutesManifest
routesManifest?: RoutesManifest,
omittedRoutes?: Set<string>
): Promise<Source[]> {
if (!dynamicPages.length) {
return [];
@@ -363,14 +365,16 @@ export async function getDynamicRoutes(
switch (routesManifest.version) {
case 1:
case 2: {
return routesManifest.dynamicRoutes.map(
({ page, regex }: { page: string; regex: string }) => {
return routesManifest.dynamicRoutes
.filter(({ page }) =>
omittedRoutes ? !omittedRoutes.has(page) : true
)
.map(({ page, regex }: { page: string; regex: string }) => {
return {
src: regex,
dest: !isDev ? path.join('/', entryDirectory, page) : page,
};
}
);
});
}
default: {
// update MIN_ROUTES_MANIFEST_VERSION
@@ -616,7 +620,7 @@ export async function createLambdaFromPseudoLayers({
export type NextPrerenderedRoutes = {
bypassToken: string | null;
routes: {
staticRoutes: {
[route: string]: {
initialRevalidate: number | false;
dataRoute: string;
@@ -624,14 +628,24 @@ export type NextPrerenderedRoutes = {
};
};
lazyRoutes: {
legacyBlockingRoutes: {
[route: string]: {
fallback?: string;
routeRegex: string;
dataRoute: string;
dataRouteRegex: string;
};
};
fallbackRoutes: {
[route: string]: {
fallback: string;
routeRegex: string;
dataRoute: string;
dataRouteRegex: string;
};
};
omittedRoutes: string[];
};
export async function getExportIntent(
@@ -716,30 +730,58 @@ export async function getPrerenderManifest(
.catch(() => false);
if (!hasManifest) {
return { routes: {}, lazyRoutes: {}, bypassToken: null };
return {
staticRoutes: {},
legacyBlockingRoutes: {},
fallbackRoutes: {},
bypassToken: null,
omittedRoutes: [],
};
}
const manifest: {
version: 1;
routes: {
[key: string]: {
initialRevalidateSeconds: number | false;
dataRoute: string;
srcRoute: string | null;
};
};
dynamicRoutes: {
[key: string]: {
fallback?: string;
routeRegex: string;
dataRoute: string;
dataRouteRegex: string;
};
};
preview?: {
previewModeId: string;
};
} = JSON.parse(await fs.readFile(pathPrerenderManifest, 'utf8'));
const manifest:
| {
version: 1;
routes: {
[key: string]: {
initialRevalidateSeconds: number | false;
dataRoute: string;
srcRoute: string | null;
};
};
dynamicRoutes: {
[key: string]: {
fallback?: string;
routeRegex: string;
dataRoute: string;
dataRouteRegex: string;
};
};
preview?: {
previewModeId: string;
};
}
| {
version: 2;
routes: {
[route: string]: {
initialRevalidateSeconds: number | false;
srcRoute: string | null;
dataRoute: string;
};
};
dynamicRoutes: {
[route: string]: {
routeRegex: string;
fallback: string | false;
dataRoute: string;
dataRouteRegex: string;
};
};
preview: {
previewModeId: string;
};
} = JSON.parse(await fs.readFile(pathPrerenderManifest, 'utf8'));
switch (manifest.version) {
case 1: {
@@ -747,10 +789,12 @@ export async function getPrerenderManifest(
const lazyRoutes = Object.keys(manifest.dynamicRoutes);
const ret: NextPrerenderedRoutes = {
routes: {},
lazyRoutes: {},
staticRoutes: {},
legacyBlockingRoutes: {},
fallbackRoutes: {},
bypassToken:
(manifest.preview && manifest.preview.previewModeId) || null,
omittedRoutes: [],
};
routes.forEach(route => {
@@ -759,7 +803,7 @@ export async function getPrerenderManifest(
dataRoute,
srcRoute,
} = manifest.routes[route];
ret.routes[route] = {
ret.staticRoutes[route] = {
initialRevalidate:
initialRevalidateSeconds === false
? false
@@ -777,7 +821,68 @@ export async function getPrerenderManifest(
dataRouteRegex,
} = manifest.dynamicRoutes[lazyRoute];
ret.lazyRoutes[lazyRoute] = {
if (fallback) {
ret.fallbackRoutes[lazyRoute] = {
routeRegex,
fallback,
dataRoute,
dataRouteRegex,
};
} else {
ret.legacyBlockingRoutes[lazyRoute] = {
routeRegex,
dataRoute,
dataRouteRegex,
};
}
});
return ret;
}
case 2: {
const routes = Object.keys(manifest.routes);
const lazyRoutes = Object.keys(manifest.dynamicRoutes);
const ret: NextPrerenderedRoutes = {
staticRoutes: {},
legacyBlockingRoutes: {},
fallbackRoutes: {},
bypassToken: manifest.preview.previewModeId,
omittedRoutes: [],
};
routes.forEach(route => {
const {
initialRevalidateSeconds,
dataRoute,
srcRoute,
} = manifest.routes[route];
ret.staticRoutes[route] = {
initialRevalidate:
initialRevalidateSeconds === false
? false
: Math.max(1, initialRevalidateSeconds),
dataRoute,
srcRoute,
};
});
lazyRoutes.forEach(lazyRoute => {
const {
routeRegex,
fallback,
dataRoute,
dataRouteRegex,
} = manifest.dynamicRoutes[lazyRoute];
if (!fallback) {
// Fallback behavior is disabled, all routes would've been provided
// in the top-level `routes` key (`staticRoutes`).
ret.omittedRoutes.push(lazyRoute);
return;
}
ret.fallbackRoutes[lazyRoute] = {
routeRegex,
fallback,
dataRoute,
@@ -788,7 +893,13 @@ export async function getPrerenderManifest(
return ret;
}
default: {
return { routes: {}, lazyRoutes: {}, bypassToken: null };
return {
staticRoutes: {},
legacyBlockingRoutes: {},
fallbackRoutes: {},
bypassToken: null,
omittedRoutes: [],
};
}
}
}

View File

@@ -84,6 +84,21 @@
"x-now-cache": "/HIT|STALE/"
}
},
{
"path": "/_next/data/testing-build-id/blog/post-4.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{ "delay": 2000 },
{
"path": "/_next/data/testing-build-id/blog/post-4.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "/HIT|STALE/"
}
},
{
"path": "/blog/post-1/comment-1",
"status": 200,

View File

@@ -82,6 +82,21 @@
"x-now-cache": "/HIT|STALE/"
}
},
{
"path": "/_next/data/testing-build-id/blog/post-4.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{ "delay": 2000 },
{
"path": "/_next/data/testing-build-id/blog/post-4.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "/HIT|STALE/"
}
},
{
"path": "/blog/post-3",
"status": 200,

View File

@@ -1,6 +1,6 @@
{
"dependencies": {
"next": "9.2.2-canary.16",
"next": "9.2.3-canary.13",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}

View File

@@ -0,0 +1,5 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
};

View File

@@ -0,0 +1,164 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/next" }],
"probes": [
{
"path": "/lambda",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{
"path": "/forever",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{ "delay": 2000 },
{
"path": "/forever",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{
"path": "/another",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{ "delay": 2000 },
{
"path": "/another",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{
"path": "/blog/post-1",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{ "delay": 2000 },
{
"path": "/blog/post-1",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{
"path": "/blog/post-2",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{ "delay": 2000 },
{
"path": "/blog/post-2",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{
"path": "/blog/post-3",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{ "delay": 2000 },
{
"path": "/blog/post-3",
"status": 200,
"responseHeaders": {
"x-now-cache": "/MISS/"
}
},
{
"path": "/blog/post-1/comment-1",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{
"path": "/blog/post-2/comment-2",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{
"path": "/blog/post-3/comment-3",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{
"path": "/_next/data/testing-build-id/lambda.json",
"status": 404
},
{
"path": "/_next/data/testing-build-id/another.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "/MISS/"
}
},
{
"path": "/_next/data/testing-build-id/another2.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{ "delay": 2000 },
{
"path": "/_next/data/testing-build-id/another2.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{
"path": "/_next/data/testing-build-id/blog/post-1.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "/MISS/"
}
},
{
"path": "/_next/data/testing-build-id/blog/post-1.json",
"status": 200,
"mustContain": "post-1"
},
{
"path": "/_next/data/testing-build-id/blog/post-1337/comment-1337.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{
"path": "/_next/data/testing-build-id/blog/post-1337/comment-1337.json",
"status": 200,
"mustContain": "comment-1337"
},
{
"path": "/_next/data/testing-build-id/blog/post-1337/comment-1337.json",
"status": 200,
"mustContain": "post-1337"
}
]
}

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"next": "9.2.3-canary.13",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1,20 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getServerProps() {
return {
props: {
world: 'world',
time: new Date().getTime(),
},
};
}
export default ({ world, time }) => {
return (
<>
<p>hello: {world}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -0,0 +1,20 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getServerProps() {
return {
props: {
world: 'world',
time: new Date().getTime(),
},
};
}
export default ({ world, time }) => {
return (
<>
<p>hello: {world}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -0,0 +1,22 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getServerProps ({ params }) {
return {
props: {
post: params.post,
comment: params.comment,
time: new Date().getTime(),
},
};
}
export default ({ post, comment, time }) => {
return (
<>
<p>Post: {post}</p>
<p>Comment: {comment}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -0,0 +1,26 @@
import React from 'react'
// eslint-disable-next-line camelcase
export async function unstable_getServerProps ({ params }) {
if (params.post === 'post-10') {
await new Promise(resolve => {
setTimeout(() => resolve(), 1000)
})
}
return {
props: {
post: params.post,
time: (await import('perf_hooks')).performance.now()
},
}
}
export default ({ post, time }) => {
return (
<>
<p>Post: {post}</p>
<span>time: {time}</span>
</>
)
}

View File

@@ -0,0 +1,20 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getServerProps() {
return {
props: {
world: 'world',
time: new Date().getTime(),
},
};
}
export default ({ world, time }) => {
return (
<>
<p>hello: {world}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -0,0 +1 @@
export default () => 'Hi';

View File

@@ -0,0 +1,5 @@
const Page = ({ data }) => <p>{data} world</p>;
Page.getInitialProps = () => ({ data: 'hello' });
export default Page;

View File

@@ -0,0 +1,5 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
};

View File

@@ -0,0 +1,218 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/next" }],
"probes": [
{
"path": "/lambda",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{
"path": "/forever",
"status": 200,
"responseHeaders": {
"x-now-cache": "PRERENDER"
}
},
{ "delay": 2000 },
{
"path": "/forever",
"status": 200,
"responseHeaders": {
"x-now-cache": "HIT"
}
},
{
"path": "/another",
"status": 200,
"responseHeaders": {
"x-now-cache": "PRERENDER"
}
},
{ "delay": 2000 },
{
"path": "/another",
"status": 200,
"responseHeaders": {
"x-now-cache": "HIT"
}
},
{
"path": "/blog/post-1",
"status": 200,
"responseHeaders": {
"x-now-cache": "PRERENDER"
}
},
{ "delay": 2000 },
{
"path": "/blog/post-1",
"status": 200,
"responseHeaders": {
"x-now-cache": "HIT"
}
},
{
"path": "/blog/post-2",
"status": 200,
"responseHeaders": {
"x-now-cache": "PRERENDER"
}
},
{ "delay": 2000 },
{
"path": "/blog/post-2",
"status": 200,
"responseHeaders": {
"x-now-cache": "HIT"
}
},
{
"path": "/blog/post-3",
"status": 200,
"mustContain": "loading..."
},
{ "delay": 2000 },
{
"path": "/blog/post-3",
"status": 200,
"responseHeaders": {
"x-now-cache": "/HIT|STALE/"
}
},
{
"path": "/_next/data/testing-build-id/blog/post-4.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "MISS"
}
},
{ "delay": 2000 },
{
"path": "/_next/data/testing-build-id/blog/post-4.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "/HIT|STALE/"
}
},
{
"path": "/blog/post-3",
"status": 200,
"mustContain": "post-3"
},
{
"path": "/blog/post-1/comment-1",
"status": 200,
"responseHeaders": {
"x-now-cache": "PRERENDER"
}
},
{
"path": "/blog/post-2/comment-2",
"status": 200,
"responseHeaders": {
"x-now-cache": "PRERENDER"
}
},
{
"path": "/blog/post-3/comment-3",
"status": 200,
"mustContain": "loading..."
},
{
"path": "/_next/data/testing-build-id/lambda.json",
"status": 404
},
{
"path": "/_next/data/testing-build-id/forever.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "/PRERENDER|HIT/"
}
},
{ "delay": 2000 },
{
"path": "/blog/post-3/comment-3",
"status": 200,
"mustContain": "comment-3"
},
{
"path": "/_next/data/testing-build-id/forever.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "HIT"
}
},
{
"path": "/_next/data/testing-build-id/another.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "/HIT|STALE/"
}
},
{
"path": "/_next/data/testing-build-id/another2.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "PRERENDER"
}
},
{ "delay": 2000 },
{
"path": "/_next/data/testing-build-id/another2.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "HIT"
}
},
{
"path": "/_next/data/testing-build-id/blog/post-1.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "/HIT|STALE|PRERENDER/"
}
},
{
"path": "/_next/data/testing-build-id/blog/post-1337/comment-1337.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "PRERENDER"
}
},
{
"path": "/nofallback/one",
"status": 200,
"mustContain": "one"
},
{
"path": "/nofallback/two",
"status": 200,
"mustContain": "two"
},
{
"path": "/nofallback/nope",
"status": 404,
"mustContain": "This page could not be found"
},
{
"path": "/_next/data/testing-build-id/nofallback/one.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "/HIT|STALE|PRERENDER/"
}
},
{
"path": "/_next/data/testing-build-id/nofallback/two.json",
"status": 200,
"responseHeaders": {
"x-now-cache": "/HIT|STALE|PRERENDER/"
}
},
{
"path": "/_next/data/testing-build-id/nofallback/nope.json",
"status": 404
}
]
}

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"next": "9.2.3-canary.14",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1,21 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps() {
return {
props: {
world: 'world',
time: new Date().getTime(),
},
revalidate: 5,
};
}
export default ({ world, time }) => {
return (
<>
<p>hello: {world}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -0,0 +1,21 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps() {
return {
props: {
world: 'world',
time: new Date().getTime(),
},
revalidate: 5,
};
}
export default ({ world, time }) => {
return (
<>
<p>hello: {world}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -0,0 +1,37 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getStaticPaths() {
return {
paths: [
'/blog/post-1/comment-1',
{ params: { post: 'post-2', comment: 'comment-2' } },
'/blog/post-1337/comment-1337',
],
fallback: true,
};
}
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps({ params }) {
return {
props: {
post: params.post,
comment: params.comment,
time: new Date().getTime(),
},
revalidate: 2,
};
}
export default ({ post, comment, time }) => {
if (!post) return <p>loading...</p>;
return (
<>
<p>Post: {post}</p>
<p>Comment: {comment}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -0,0 +1,37 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getStaticPaths() {
return {
paths: ['/blog/post-1', { params: { post: 'post-2' } }],
fallback: true,
};
}
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps({ params }) {
if (params.post === 'post-10') {
await new Promise(resolve => {
setTimeout(() => resolve(), 1000);
});
}
return {
props: {
post: params.post,
time: (await import('perf_hooks')).performance.now(),
},
revalidate: 10,
};
}
export default ({ post, time }) => {
if (!post) return <p>loading...</p>;
return (
<>
<p>Post: {post}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -0,0 +1,21 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps() {
return {
props: {
world: 'world',
time: new Date().getTime(),
},
revalidate: false,
};
}
export default ({ world, time }) => {
return (
<>
<p>hello: {world}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -0,0 +1 @@
export default () => 'Hi';

View File

@@ -0,0 +1,5 @@
const Page = ({ data }) => <p>{data} world</p>;
Page.getInitialProps = () => ({ data: 'hello' });
export default Page;

View File

@@ -0,0 +1,31 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getStaticPaths() {
return {
paths: ['/nofallback/one', { params: { slug: 'two' } }],
fallback: false,
};
}
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps({ params }) {
return {
props: {
slug: params.slug,
time: (await import('perf_hooks')).performance.now(),
},
revalidate: 10,
};
}
export default ({ slug, time }) => {
return (
<>
<p>
Slug ({slug.length}): {slug}
</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -21,23 +21,23 @@ mv dist/types dist/index.d.ts
# bundle helpers.ts with ncc
rm dist/helpers.js
ncc build src/helpers.ts -o dist/helpers
ncc build src/helpers.ts -e @now/build-utils -o dist/helpers
mv dist/helpers/index.js dist/helpers.js
rm -rf dist/helpers
# build source-map-support/register for source maps
ncc build ../../node_modules/source-map-support/register -o dist/source-map-support
ncc build ../../node_modules/source-map-support/register -e @now/build-utils -o dist/source-map-support
mv dist/source-map-support/index.js dist/source-map-support.js
rm -rf dist/source-map-support
# build typescript
ncc build ../../node_modules/typescript/lib/typescript -o dist/typescript
ncc build ../../node_modules/typescript/lib/typescript -e @now/build-utils -o dist/typescript
mv dist/typescript/index.js dist/typescript.js
mkdir -p dist/typescript/lib
mv dist/typescript/typescript/lib/*.js dist/typescript/lib/
mv dist/typescript/typescript/lib/*.d.ts dist/typescript/lib/
rm -r dist/typescript/typescript
ncc build src/index.ts -o dist/main
ncc build src/index.ts -e @now/build-utils -o dist/main
mv dist/main/index.js dist/index.js
rm -rf dist/main

View File

@@ -1,6 +1,6 @@
{
"name": "@now/node",
"version": "1.4.1",
"version": "1.4.2-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/node-js",

View File

@@ -12,15 +12,5 @@
}
}
}
],
"probes": [
{
"path": "/lighthouse",
"mustContain": "lighthouse:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/screenshot",
"mustContain": "screenshot:RANDOMNESS_PLACEHOLDER"
}
]
}

View File

@@ -0,0 +1,37 @@
const assert = require('assert').strict;
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
async function tryTest({
testName,
deploymentUrl,
fetch,
randomness,
retries = 4,
}) {
try {
const res = await fetch(`https://${deploymentUrl}/${testName}`);
assert.equal(res.status, 200);
const text = await res.text();
assert.equal(text.trim(), `${testName}:${randomness}`);
console.log(`Finished testing "${testName}" probe.`);
} catch (e) {
if (retries === 0) {
console.error(e);
throw e;
}
console.log(`Failed "${testName}" probe. Retries remaining: ${retries}`);
await sleep(1000);
await tryTest({
testName,
deploymentUrl,
fetch,
randomness,
retries: retries - 1,
});
}
}
module.exports = async ({ deploymentUrl, fetch, randomness }) => {
await tryTest({ testName: 'lighthouse', deploymentUrl, fetch, randomness });
await tryTest({ testName: 'screenshot', deploymentUrl, fetch, randomness });
};

View File

@@ -1 +1 @@
ncc build src/index.ts -o dist
ncc build src/index.ts -e @now/build-utils -o dist

View File

@@ -45,6 +45,7 @@ if 'handler' in __now_variables or 'Handler' in __now_variables:
payload = json.loads(event['body'])
path = unquote(payload['path'])
path = path.replace(' ', '%20')
headers = payload['headers']
method = payload['method']
encoding = payload.get('encoding')

View File

@@ -1,6 +1,6 @@
{
"name": "@now/python",
"version": "1.1.4",
"version": "1.1.5-canary.1",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/python",

View File

@@ -1,9 +1,16 @@
{
"version": 2,
"builds": [{ "src": "*.py", "use": "@now/python" }],
"routes": [{ "src": "/another", "dest": "custom.py" }],
"routes": [{ "src": "/(.+)", "dest": "custom.py" }],
"probes": [
{ "path": "/?hello=/", "mustContain": "path=/?hello=/" },
{ "path": "/another?hello=/", "mustContain": "path=/another?hello=/" }
{ "path": "/another?hello=/", "mustContain": "path=/another?hello=/" },
{ "path": "/another?hello=/", "mustContain": "path=/another?hello=/" },
{ "path": "/?say=hello%20world", "mustContain": "/?say=hello%20world" },
{ "path": "/?say%20hello=nice", "mustContain": "/?say%20hello=nice" },
{
"path": "/hello%20world?say=nice",
"mustContain": "/hello%20world?say=nice"
}
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@now/routing-utils",
"version": "1.6.0",
"version": "1.7.0",
"description": "ZEIT Now routing utilities",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",

View File

@@ -0,0 +1,36 @@
import { AppendRoutesToPhaseProps } from './types';
import { isHandler } from './index';
export function appendRoutesToPhase({
routes: prevRoutes,
newRoutes,
phase,
}: AppendRoutesToPhaseProps) {
const routes = prevRoutes ? [...prevRoutes] : [];
if (newRoutes === null || newRoutes.length === 0) {
return routes;
}
let isInPhase = false;
let insertIndex = -1;
routes.forEach((r, i) => {
if (isHandler(r)) {
if (r.handle === phase) {
isInPhase = true;
} else if (isInPhase) {
insertIndex = i;
isInPhase = false;
}
}
});
if (isInPhase) {
routes.push(...newRoutes);
} else if (insertIndex > -1) {
routes.splice(insertIndex, 0, ...newRoutes);
} else {
routes.push({ handle: phase });
routes.push(...newRoutes);
}
return routes;
}

View File

@@ -19,6 +19,7 @@ import {
export { getCleanUrls } from './superstatic';
export { mergeRoutes } from './merge';
export { appendRoutesToPhase } from './append';
const VALID_HANDLE_VALUES = ['filesystem', 'hit', 'miss'] as const;
const validHandleValues = new Set<string>(VALID_HANDLE_VALUES);

View File

@@ -80,3 +80,18 @@ export interface NowHeaderKeyValue {
key: string;
value: string;
}
export interface AppendRoutesToPhaseProps {
/**
* All input routes including `handle` phases.
*/
routes: Route[] | null;
/**
* The routes to append to a specific phase.
*/
newRoutes: Route[] | null;
/**
* The phase to append the routes such as `filesystem`.
*/
phase: HandleValue;
}

View File

@@ -0,0 +1,123 @@
const { deepEqual } = require('assert');
const { appendRoutesToPhase } = require('../dist/append');
test('appendRoutesToPhase `routes=null` and `newRoutes=[]`', () => {
const routes = null;
const newRoutes = [];
const phase = 'filesystem';
const actual = appendRoutesToPhase({ routes, newRoutes, phase });
const expected = [];
deepEqual(actual, expected);
});
test('appendRoutesToPhase `routes=null` and one `newRoutes`', () => {
const routes = null;
const newRoutes = [{ src: '/foo', dest: '/bar' }];
const phase = 'filesystem';
const actual = appendRoutesToPhase({ routes, newRoutes, phase });
const expected = [{ handle: 'filesystem' }, ...newRoutes];
deepEqual(actual, expected);
});
test('appendRoutesToPhase `routes=[]` and `newRoutes=null`', () => {
const routes = [];
const newRoutes = null;
const phase = 'filesystem';
const actual = appendRoutesToPhase({ routes, newRoutes, phase });
const expected = [];
deepEqual(actual, expected);
});
test('appendRoutesToPhase `routes=[]` and `newRoutes=[]`', () => {
const routes = [];
const newRoutes = [];
const phase = 'filesystem';
const actual = appendRoutesToPhase({ routes, newRoutes, phase });
const expected = [];
deepEqual(actual, expected);
});
test('appendRoutesToPhase one routes, zero newRoutes', () => {
const routes = [{ src: '/foo', dest: '/bar' }];
const newRoutes = [];
const phase = 'filesystem';
const actual = appendRoutesToPhase({ routes, newRoutes, phase });
const expected = routes;
deepEqual(actual, expected);
});
test('appendRoutesToPhase zero routes, one newRoutes', () => {
const routes = [];
const newRoutes = [{ src: '/foo', dest: '/bar' }];
const phase = 'filesystem';
const actual = appendRoutesToPhase({ routes, newRoutes, phase });
const expected = [{ handle: 'filesystem' }, ...newRoutes];
deepEqual(actual, expected);
});
test('appendRoutesToPhase two routes in phase', () => {
const routes = [{ handle: 'filesystem' }, { src: '/first', dest: '/one' }];
const newRoutes = [{ src: '/new', dest: '/to' }];
const phase = 'filesystem';
const actual = appendRoutesToPhase({ routes, newRoutes, phase });
const expected = [
{ handle: 'filesystem' },
{ src: '/first', dest: '/one' },
{ src: '/new', dest: '/to' },
];
deepEqual(actual, expected);
});
test('appendRoutesToPhase two routes out of phase', () => {
const routes = [{ handle: 'resource' }, { src: '/first', dest: '/one' }];
const newRoutes = [{ src: '/new', dest: '/to' }];
const phase = 'filesystem';
const actual = appendRoutesToPhase({ routes, newRoutes, phase });
const expected = [
{ handle: 'resource' },
{ src: '/first', dest: '/one' },
{ handle: 'filesystem' },
{ src: '/new', dest: '/to' },
];
deepEqual(actual, expected);
});
test('appendRoutesToPhase one routes before, two routes in phase', () => {
const routes = [
{ src: '/first', dest: '/one' },
{ handle: 'filesystem' },
{ src: '/second', dest: '/two' },
];
const newRoutes = [{ src: '/new', dest: '/to' }];
const phase = 'filesystem';
const actual = appendRoutesToPhase({ routes, newRoutes, phase });
const expected = [
{ src: '/first', dest: '/one' },
{ handle: 'filesystem' },
{ src: '/second', dest: '/two' },
{ src: '/new', dest: '/to' },
];
deepEqual(actual, expected);
});
test('appendRoutesToPhase one routes before, two routes in phase, two routes in different phase', () => {
const routes = [
{ src: '/first', dest: '/one' },
{ handle: 'filesystem' },
{ src: '/second', dest: '/two' },
{ handle: 'miss' },
{ src: '/third', dest: '/three' },
];
const newRoutes = [{ src: '/new', dest: '/to' }];
const phase = 'filesystem';
const actual = appendRoutesToPhase({ routes, newRoutes, phase });
const expected = [
{ src: '/first', dest: '/one' },
{ handle: 'filesystem' },
{ src: '/second', dest: '/two' },
{ src: '/new', dest: '/to' },
{ handle: 'miss' },
{ src: '/third', dest: '/three' },
];
deepEqual(actual, expected);
});

View File

@@ -1 +1 @@
ncc build index.ts -o dist
ncc build index.ts -e @now/build-utils -o dist

View File

@@ -1,7 +1,7 @@
{
"name": "@now/ruby",
"author": "Nathan Cahill <nathan@nathancahill.com>",
"version": "1.0.2",
"version": "1.0.3-canary.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/ruby",

View File

@@ -1,7 +1,7 @@
require 'cowsay'
Handler = Proc.new do |req, res|
res.status = 200
res['Content-Type'] = 'text/plain'
res.body = Cowsay.say('gem:RANDOMNESS_PLACEHOLDER', 'cow')
res.status = 200
res['Content-Type'] = 'text/plain'
res.body = Cowsay.say('gem:RANDOMNESS_PLACEHOLDER', 'cow')
end

View File

@@ -1,4 +1,4 @@
#!/bin/bash
set -euo pipefail
ncc build src/index.ts -o dist
ncc build src/index.ts -e @now/build-utils -o dist

View File

@@ -1,6 +1,6 @@
{
"name": "@now/static-build",
"version": "0.14.13-canary.0",
"version": "0.14.13-canary.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/static-builds",

View File

@@ -91,7 +91,6 @@ async function testDeployment(
bodies['now.json'] = Buffer.from(JSON.stringify(nowJson));
delete bodies['probe.js'];
const { deploymentId, deploymentUrl } = await nowDeploy(bodies, randomness);
console.log('deploymentUrl', `https://${deploymentUrl}`);
for (const probe of nowJson.probes || []) {
console.log('testing', JSON.stringify(probe));