Compare commits

...

21 Commits

Author SHA1 Message Date
Steven
71c297de32 Publish
- @now/go@0.5.10
 - @now/next@0.6.0
 - @now/node@0.12.6
 - @now/python@0.2.15
 - @now/static-build@0.9.7
2019-08-13 09:18:59 -07:00
Steven
9569c1babc [now-static-build] Fix zero config vue headers (#918)
* [now-static-build] Add test probes for vue headers

* Add mustContain probe

* Fix vue caching headers
2019-08-13 09:17:42 -07:00
Steven
16a573ec6e [now-node][now-next] Bump node-file-trace to 0.2.9 (#920) 2019-08-13 09:17:37 -07:00
Steven
79a6c8e1d2 [docs] Add versioning documentation (#919)
* [docs] Add versioning documentation

* Separate readme into multiple files
2019-08-13 09:17:31 -07:00
Contextualist
e7f62d194b [now-python] WSGI encoding dance for QUERY_STRING (#915) 2019-08-13 09:17:24 -07:00
Steven
3c4306662e [now-node][now-next] Bump node-file-trace to 0.2.8 (#917) 2019-08-13 09:17:17 -07:00
Sophearak Tha
2ed4e7e9a2 Update link to docs (#912) 2019-08-13 09:17:11 -07:00
Joe Haddad
d7a65cc835 Revert "Disable compression for Zips" (#907)
This reverts commit 57d26761fdc3ef322a77e31c0c7c6f090694323c.
2019-08-13 09:16:50 -07:00
Joe Haddad
0eb5dac904 Next.js experimental for canary only (#910) 2019-08-13 09:16:45 -07:00
Joe Haddad
9ad63f9ed2 Disable compression for Zips (#901) 2019-08-13 09:16:36 -07:00
Joe Haddad
4ad4765034 [now-next] Add Serverless Trace to Next.js Builder (#884)
* Add Serverless Trace to Next.js Builder

* Correctly specify the experimental target

* Extract version

* Capture real Next.js version

* Disable asset folder on versions with trace mode

* Adjust console output

* Start on node-file-trace integration

* Remove ESNext syntax

* Add firebase fixture

* Add message

* Add 2x logs

* Use resolve from

* Use correct path

* Check against realNextVersion

* Populate traced files

* Put logging behind some debug flags

* Add another TODO

* Update message

* Set default config if missing

* Inject the correct page require path into build

* Add a Next.js 8 sanity test

* Do not trace individual lambdas

* Limit zipping to 5 to not saturate CPU/Memory

* Revert "Limit zipping to 5 to not saturate CPU/Memory"

This reverts commit b826be6c927f083555cceb61c3d20938427408c1.

* Try to compress traced files separate

* Ensure it works with 2x lambdas

* Revert "Try to compress traced files separate"

This reverts commit 46fd20938cd8c7a1eecc878ae8ecbbefa27a72da.
2019-08-13 09:15:22 -07:00
Andy Bitz
162f06e4a6 Publish
- @now/build-utils@0.9.12
2019-08-09 21:48:13 +02:00
Andy
4b5b573a1e [now-build-utils] Fix 404 trailing slash for index files and add tests (#904)
* [now-build-utils] Fix 404 trailing slash for index files and add tests

* Don't fail on 404

* Consider status probe

* Fix slash only route

* Adjust tests

* Add another test
2019-08-09 21:48:01 +02:00
Steven
86a2640fff Publish
- @now/go@0.5.9
 - @now/next@0.5.10
 - @now/node@0.12.5
 - @now/routing-utils@1.2.2
2019-08-08 17:48:44 -07:00
Steven
351a85f875 [now-go] Fix go concurrent/parallel builds (#900)
* [now-go] Fix concurrent go install

* Add test

* Fix typo

* Add config.parallel
2019-08-08 17:47:53 -07:00
Connor Davis
00fd570ce5 [now-routing-utils] Allow the combined use of continue and dest (#897)
* allow continue dest

* remove test
2019-08-08 17:47:47 -07:00
Steven
4ff1f05cf3 [now-node] Fix windows typescript entrypoint bug (#899) 2019-08-08 17:47:39 -07:00
Steven
fb9035bc00 [now-node][now-next] Bump node-file-trace to 0.2.7 (#892) 2019-08-08 17:47:28 -07:00
Steven
9258a74986 [tests] Add automerge (#893) 2019-08-08 17:46:02 -07:00
Leo Lamprecht
f7c21bbde6 Publish
- @now/static-build@0.9.6
2019-08-07 23:10:28 +00:00
Leo Lamprecht
4c6c17af8a [now-static-build] Capture file system routes for CRA apps (#891)
* Capture file system routes for CRA apps

* Added integration test

* Fixed defaults
2019-08-07 23:09:55 +00:00
58 changed files with 671 additions and 159 deletions

View File

@@ -7,6 +7,7 @@
/packages/now-build-utils/src/fs/*.js
/packages/now-node/dist/*
/packages/now-next/dist/*
/packages/now-next/test/fixtures/**
/packages/now-node-bridge/*
/packages/now-python/dist/*
/packages/now-go/*

16
.kodiak.toml Normal file
View File

@@ -0,0 +1,16 @@
version = 1
[merge]
automerge_label = "automerge"
blacklist_title_regex = "^WIP.*"
blacklist_labels = ["work in progress"]
method = "squash"
delete_branch_on_merge = true
block_on_reviews_requested = false
notify_on_conflict = true
optimistic_updates = true
[merge.message]
title = "pull_request_title"
include_pr_number = true
body_type = "markdown"

40
PUBLISHING.md Normal file
View File

@@ -0,0 +1,40 @@
# Publishing to npm
Always publish to the Canary Channel as soon as a PR is merged into the `canary` branch.
```
yarn publish-canary
```
Publish the Stable Channel weekly.
- Cherry pick each commit from `canary` to `master` branch
- Verify that you are _in-sync_ with canary (with the exception of the `version` line in `package.json`)
- Deploy the modified Builders
```
# View differences excluding "Publish" commits
git checkout canary && git pull
git log --pretty=format:"$ad- %s [%an]" | grep -v Publish > ~/Desktop/canary.txt
git checkout master && git pull
git log --pretty=format:"$ad- %s [%an]" | grep -v Publish > ~/Desktop/master.txt
diff ~/Desktop/canary.txt ~/Desktop/master.txt
# Cherry pick all PRs from canary into master ...
git cherry-pick <PR501_COMMIT_SHA>
git cherry-pick <PR502_COMMIT_SHA>
git cherry-pick <PR503_COMMIT_SHA>
git cherry-pick <PR504_COMMIT_SHA>
# Verify the only difference is "version" in package.json
git diff origin/canary
# Ship it
yarn publish-stable
```
After running this publish step, GitHub Actions will take care of publishing the modified Builder packages to npm.
If for some reason GitHub Actions fails to publish the npm package, you may do so
manually by running `npm publish` from the package directory. Make sure to
use `npm publish --tag canary` if you are publishing a canary release!

View File

@@ -2,64 +2,18 @@
This is a monorepo containing the [Official Builders](https://zeit.co/docs/v2/advanced/builders) provided by the ZEIT team.
## Channels
## Versioning and advanced usage
There are two Channels:
See [VERSIONING.md](VERSIONING.md).
| Channel | Git Branch | npm dist-tag | use example |
| ------- | ------------------------------------------------------------- | ------------ | ------------------ |
| Canary | [canary](https://github.com/zeit/now-builders/commits/canary) | `@canary` | `@now/node@canary` |
| Stable | [master](https://github.com/zeit/now-builders/commits/master) | `@latest` | `@now/node@latest` |
## Publishing to npm
All PRs should be submitted to the `canary` branch.
See [PUBLISHING.md](PUBLISHING.md).
Once a PR is merged into the `canary` branch, it should be published to npm immediately using the Canary Channel.
## Contributing
### Publishing to npm
For the Canary Channel, publish the modified Builders to npm with the following:
```
yarn publish-canary
```
For the Stable Channel, you must do the following:
- Cherry pick each commit from canary to master
- Verify that you are _in-sync_ with canary (with the exception of the `version` line in `package.json`)
- Deploy the modified Builders
```
# View differences excluding "Publish" commits
git checkout canary && git pull
git log --pretty=format:"$ad- %s [%an]" | grep -v Publish > ~/Desktop/canary.txt
git checkout master && git pull
git log --pretty=format:"$ad- %s [%an]" | grep -v Publish > ~/Desktop/master.txt
diff ~/Desktop/canary.txt ~/Desktop/master.txt
# Cherry pick all PRs from canary into master ...
git cherry-pick <PR501_COMMIT_SHA>
git cherry-pick <PR502_COMMIT_SHA>
git cherry-pick <PR503_COMMIT_SHA>
git cherry-pick <PR504_COMMIT_SHA>
# Verify the only difference is "version" in package.json
git diff origin/canary
# Ship it
yarn publish-stable
```
After running this publish step, GitHub Actions will take care of publishing the modified Builder packages to npm.
If for some reason GitHub Actions fails to publish the npm package, you may do so
manually by running `npm publish` from the package directory. Make sure to
use `npm publish --tag canary` if you are publishing a canary release!
### Contributing
See the [Contribution guidelines for this project](CONTRIBUTING.md), it also contains guidance on interpreting tests failures.
See [CONTRIBUTING.md](CONTRIBUTING.md).
### Creating Your Own Builder
To create your own Builder, see [the Builder's Developer Reference](DEVELOPING_A_BUILDER.md).
See [DEVELOPING_A_BUILDER.md](DEVELOPING_A_BUILDER.md).

25
VERSIONING.md Normal file
View File

@@ -0,0 +1,25 @@
# Versioning
Builders are released to two different channels.
## Channels
| Channel | Git Branch | npm dist-tag | use example |
| ------- | ------------------------------------------------------------- | ------------ | ------------------ |
| Canary | [canary](https://github.com/zeit/now-builders/commits/canary) | `@canary` | `@now/node@canary` |
| Stable | [master](https://github.com/zeit/now-builders/commits/master) | `@latest` | `@now/node@latest` |
All PRs are submitted to the `canary` branch. Once a PR is merged into the `canary` branch, it should be published to npm immediately using the Canary Channel.
## Version Selection
Since Builders are published to [npmjs.com](https://npmjs.com), this makes versioning works the same for Builders as it does for any npm package. The `use` statement in [now.json](https://zeit.co/docs/v2/advanced/configuration#builds) has a similar syntax to `npm install`.
The following are valid examples [@now/node](https://www.npmjs.com/package/@now/node?activeTab=versions):
- `@now/node`
- `@now/node@0.7.3`
- `@now/node@canary`
- `@now/node@0.7.2-canary.2`
We always recommend using the latest version by leaving off the dist-tag suffix, `@now/node` for example.

View File

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

View File

@@ -58,19 +58,30 @@ function createRouteFromPath(filePath: string): Route {
} else if (isLast) {
const { name: fileName, ext } = parsePath(segment);
const isIndex = fileName === 'index';
const prefix = isIndex ? '\\/' : '';
const names = [
prefix,
prefix + escapeName(fileName),
prefix + escapeName(fileName) + escapeName(ext),
].filter(Boolean);
// Either filename with extension, filename without extension
// or nothing when the filename is `index`
return `(${escapeName(fileName)}|${escapeName(fileName)}${escapeName(
ext
)})${isIndex ? '?' : ''}`;
return `(${names.join('|')})${isIndex ? '?' : ''}`;
}
return segment;
}
);
const src = `^/${srcParts.join('/')}$`;
const { name: fileName } = parsePath(filePath);
const isIndex = fileName === 'index';
const src = isIndex
? `^/${srcParts.slice(0, -1).join('/')}${srcParts.slice(-1)[0]}$`
: `^/${srcParts.join('/')}$`;
const dest = `/${filePath}${query.length ? '?' : ''}${query.join('&')}`;
return { src, dest };

View File

@@ -0,0 +1 @@
now.json

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end(req.query.endpoint);
};

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end(`${req.query.endpoint}/${req.query.id}`);
};

View File

@@ -0,0 +1,5 @@
{
"scripts": {
"build": "mkdir -p public && echo 'hello from index.txt' > public/index.txt"
}
}

View File

@@ -0,0 +1 @@
now.json

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end('hello from api/date.js');
};

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end('hello from api/date/index.js');
};

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end('hello from api/index.js');
};

View File

@@ -0,0 +1,5 @@
{
"scripts": {
"build": "mkdir -p public && echo 'hello from index.txt' > public/index.txt"
}
}

View File

@@ -4,21 +4,21 @@ const fs = require('fs-extra');
// eslint-disable-next-line import/no-extraneous-dependencies
const execa = require('execa');
const assert = require('assert');
const { createZip } = require('../dist/lambda');
const {
glob, download, detectBuilders, detectRoutes,
} = require('../');
const { createZip } = require('../dist/lambda');
const {
getSupportedNodeVersion,
defaultSelection,
} = require('../dist/fs/node-version');
const {
packAndDeploy,
testDeployment,
} = require('../../../test/lib/deployment/test-deployment.js');
} = require('../../../test/lib/deployment/test-deployment');
jest.setTimeout(4 * 60 * 1000);
const builderUrl = '@canary';
let buildUtilsUrl;
@@ -152,6 +152,11 @@ const fixturesPath = path.resolve(__dirname, 'fixtures');
// eslint-disable-next-line no-restricted-syntax
for (const fixture of fs.readdirSync(fixturesPath)) {
if (fixture.includes('zero-config')) {
// Those have separate tests
continue; // eslint-disable-line no-continue
}
// eslint-disable-next-line no-loop-func
it(`should build ${fixture}`, async () => {
await expect(
@@ -548,7 +553,9 @@ it('Test `detectRoutes`', async () => {
const { defaultRoutes } = await detectRoutes(files, builders);
expect(defaultRoutes.length).toBe(3);
expect(defaultRoutes[0].src).toBe('^/api/date/(index|index\\.js)?$');
expect(defaultRoutes[0].src).toBe(
'^/api/date(\\/|\\/index|\\/index\\.js)?$',
);
expect(defaultRoutes[0].dest).toBe('/api/date/index.js');
expect(defaultRoutes[1].src).toBe('^/api/(date|date\\.js)$');
expect(defaultRoutes[1].dest).toBe('/api/date.js');
@@ -561,7 +568,9 @@ it('Test `detectRoutes`', async () => {
const { defaultRoutes } = await detectRoutes(files, builders);
expect(defaultRoutes.length).toBe(3);
expect(defaultRoutes[0].src).toBe('^/api/([^\\/]+)/(index|index\\.js)?$');
expect(defaultRoutes[0].src).toBe(
'^/api/([^\\/]+)(\\/|\\/index|\\/index\\.js)?$',
);
expect(defaultRoutes[0].dest).toBe('/api/[date]/index.js?date=$1');
expect(defaultRoutes[1].src).toBe('^/api/(date|date\\.js)$');
expect(defaultRoutes[1].dest).toBe('/api/date.js');
@@ -587,3 +596,146 @@ it('Test `detectRoutes`', async () => {
expect(defaultRoutes.length).toBe(5);
}
});
it('Test `detectBuilders` and `detectRoutes`', async () => {
const fixture = path.join(__dirname, 'fixtures', '01-zero-config-api');
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
const fileList = await glob('**', fixture);
const files = Object.keys(fileList);
const probes = [
{
path: '/api/my-endpoint',
mustContain: 'my-endpoint',
status: 200,
},
{
path: '/api/other-endpoint',
mustContain: 'other-endpoint',
status: 200,
},
{
path: '/api/team/zeit',
mustContain: 'team/zeit',
status: 200,
},
{
path: '/api/user/myself',
mustContain: 'user/myself',
status: 200,
},
{
path: '/api/not-okay/',
status: 404,
},
{
path: '/api',
status: 404,
},
{
path: '/api/',
status: 404,
},
{
path: '/',
mustContain: 'hello from index.txt',
},
];
const { builders } = await detectBuilders(files, pkg);
const { defaultRoutes } = await detectRoutes(files, builders);
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
await fs.writeFile(
path.join(fixture, 'now.json'),
JSON.stringify(nowConfig, null, 2),
);
const deployment = await testDeployment(
{ builderUrl, buildUtilsUrl },
fixture,
);
expect(deployment).toBeDefined();
});
it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
const fileList = await glob('**', fixture);
const files = Object.keys(fileList);
const probes = [
{
path: '/api/not-okay',
status: 404,
},
{
path: '/api',
mustContain: 'hello from api/index.js',
status: 200,
},
{
path: '/api/',
mustContain: 'hello from api/index.js',
status: 200,
},
{
path: '/api/index',
mustContain: 'hello from api/index.js',
status: 200,
},
{
path: '/api/index.js',
mustContain: 'hello from api/index.js',
status: 200,
},
{
path: '/api/date.js',
mustContain: 'hello from api/date.js',
status: 200,
},
{
// Someone might expect this to be `date.js`,
// but I doubt that there is any case were both
// `date/index.js` and `date.js` exists,
// so it is not special cased
path: '/api/date',
mustContain: 'hello from api/date/index.js',
status: 200,
},
{
path: '/api/date/',
mustContain: 'hello from api/date/index.js',
status: 200,
},
{
path: '/api/date/index',
mustContain: 'hello from api/date/index.js',
status: 200,
},
{
path: '/api/date/index.js',
mustContain: 'hello from api/date/index.js',
status: 200,
},
{
path: '/',
mustContain: 'hello from index.txt',
},
];
const { builders } = await detectBuilders(files, pkg);
const { defaultRoutes } = await detectRoutes(files, builders);
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
await fs.writeFile(
path.join(fixture, 'now.json'),
JSON.stringify(nowConfig, null, 2),
);
const deployment = await testDeployment(
{ builderUrl, buildUtilsUrl },
fixture,
);
expect(deployment).toBeDefined();
});

View File

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

View File

@@ -106,7 +106,7 @@ Learn more: https://github.com/golang/go/wiki/Modules
if (!analyzed) {
const err = new Error(
`Could not find an exported function in "${entrypoint}"
Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#entrypoint
Learn more: https://zeit.co/docs/v2/advanced/builders/#go
`
);
console.log(err.message);

View File

@@ -1,6 +1,6 @@
{
"name": "@now/go",
"version": "0.5.8",
"version": "0.5.10",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/go-now-go",
@@ -13,7 +13,7 @@
"build": "./build.sh",
"test": "./build.sh && jest",
"prepublish": "./build.sh",
"now-postinstall": "node dist/install/index.js"
"now-postinstall": "node dist/install.js"
},
"files": [
"dist"

View File

@@ -0,0 +1,9 @@
package handler
import (
"fmt"
"net/http"
)
// Handler is cool
func Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "ONE:RANDOMNESS_PLACEHOLDER")
}

View File

@@ -0,0 +1,9 @@
package handler
import (
"fmt"
"net/http"
)
// Handler is cool
func Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "TWO:RANDOMNESS_PLACEHOLDER")
}

View File

@@ -0,0 +1,3 @@
module go-example
go 1.12

View File

@@ -0,0 +1,25 @@
{
"version": 2,
"builds": [
{
"src": "/api/go-one/one.go",
"use": "@now/go",
"config": { "parallel": true }
},
{
"src": "/api/go-two/two.go",
"use": "@now/go",
"config": { "parallel": true }
}
],
"probes": [
{
"path": "/api/go-one/one.go",
"mustContain": "ONE:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/api/go-two/two.go",
"mustContain": "TWO:RANDOMNESS_PLACEHOLDER"
}
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@now/next",
"version": "0.5.9",
"version": "0.6.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/next-js-now-next",
@@ -21,6 +21,7 @@
"@types/next-server": "8.0.0",
"@types/resolve-from": "5.0.1",
"@types/semver": "6.0.0",
"@zeit/node-file-trace": "0.2.9",
"fs-extra": "7.0.0",
"get-port": "5.0.0",
"resolve-from": "5.0.0",

View File

@@ -1,13 +1,16 @@
import path from 'path';
import fs from 'fs-extra';
import path from 'path';
import semver from 'semver';
function getCustomData(importName: string) {
import { ExperimentalTraceVersion } from './utils';
function getCustomData(importName: string, target: string) {
return `
module.exports = function(...args) {
let original = require('./${importName}');
const finalConfig = {};
const target = { target: 'serverless' };
const target = { target: '${target}' };
if (typeof original === 'function' && original.constructor.name === 'AsyncFunction') {
// AsyncFunctions will become promises
@@ -37,14 +40,29 @@ function getDefaultData() {
return `module.exports = { target: 'serverless' };`;
}
export default async function createServerlessConfig(workPath: string) {
export default async function createServerlessConfig(
workPath: string,
nextVersion: string | undefined
) {
let target = 'serverless';
if (nextVersion) {
try {
if (
nextVersion.includes('canary') &&
semver.satisfies(nextVersion, `>=${ExperimentalTraceVersion}`)
) {
target = 'experimental-serverless-trace';
}
} catch (_ignored) {}
}
const configPath = path.join(workPath, 'next.config.js');
const backupConfigName = `next.config.original.${Date.now()}.js`;
const backupConfigPath = path.join(workPath, backupConfigName);
if (fs.existsSync(configPath)) {
await fs.rename(configPath, backupConfigPath);
await fs.writeFile(configPath, getCustomData(backupConfigName));
await fs.writeFile(configPath, getCustomData(backupConfigName, target));
} else {
await fs.writeFile(configPath, getDefaultData());
}

View File

@@ -7,10 +7,12 @@ import {
} from 'fs-extra';
import os from 'os';
import path from 'path';
import resolveFrom from 'resolve-from';
import semver from 'semver';
import {
BuildOptions,
Config,
createLambda,
download,
FileBlob,
@@ -21,16 +23,18 @@ import {
glob,
Lambda,
PrepareCacheOptions,
Route,
runNpmInstall,
runPackageJsonScript,
Route,
} from '@now/build-utils';
import nodeFileTrace from '@zeit/node-file-trace';
import createServerlessConfig from './create-serverless-config';
import nextLegacyVersions from './legacy-versions';
import {
EnvConfig,
excludeFiles,
ExperimentalTraceVersion,
filesFromDirectory,
getDynamicRoutes,
getNextConfig,
@@ -156,7 +160,7 @@ export const build = async ({
files,
workPath,
entrypoint,
config,
config = {} as Config,
meta = {} as BuildParamsMeta,
}: BuildParamsType): Promise<{
routes: Route[];
@@ -176,10 +180,6 @@ export const build = async ({
const pkg = await readPackageJson(entryPath);
const nextVersion = getNextVersion(pkg);
if (!meta.isDev) {
await createServerlessConfig(workPath);
}
const nodeVersion = await getNodeVersion(entryPath, undefined, config);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
@@ -284,6 +284,20 @@ export const build = async ({
console.log('installing dependencies...');
await runNpmInstall(entryPath, ['--prefer-offline'], spawnOpts);
let realNextVersion: string | undefined;
try {
realNextVersion = require(resolveFrom(entryPath, 'next/package.json'))
.version;
console.log(`detected Next.js version: ${realNextVersion}`);
} catch (_ignored) {
console.warn(`could not identify real Next.js version, that's OK!`);
}
if (!isLegacy) {
await createServerlessConfig(workPath, realNextVersion);
}
console.log('running user script...');
const memoryToConsume = Math.floor(os.totalmem() / 1024 ** 2) - 128;
const env = { ...spawnOpts.env } as any;
@@ -394,14 +408,6 @@ export const build = async ({
);
} else {
console.log('preparing lambda files...');
const launcherFiles = {
'now__bridge.js': new FileFsRef({
fsPath: path.join(__dirname, 'now__bridge.js'),
}),
'now__launcher.js': new FileFsRef({
fsPath: path.join(__dirname, 'launcher.js'),
}),
};
const pagesDir = path.join(entryPath, '.next', 'serverless', 'pages');
const pages = await glob('**/*.js', pagesDir);
@@ -440,18 +446,77 @@ export const build = async ({
);
}
// An optional assets folder that is placed alongside every page entrypoint
const assets = await glob(
'assets/**',
path.join(entryPath, '.next', 'serverless')
);
const assetKeys = Object.keys(assets);
if (assetKeys.length > 0) {
console.log('detected assets to be bundled with lambda:');
assetKeys.forEach(assetFile => console.log(`\t${assetFile}`));
// Assume tracing to be safe, bail if we know we don't need it.
let requiresTracing = true;
try {
if (
realNextVersion &&
semver.satisfies(realNextVersion, `<${ExperimentalTraceVersion}`)
) {
if (config.debug) {
console.log(
'Next.js version is too old for us to trace the required dependencies.\n' +
'Assuming Next.js has handled it!'
);
}
requiresTracing = false;
}
} catch (err) {
if (config.debug) {
console.log(
'Failed to check Next.js version for tracing compatibility: ' + err
);
}
}
let assets:
| {
[filePath: string]: FileFsRef;
}
| undefined;
const tracedFiles: {
[filePath: string]: FileFsRef;
} = {};
if (requiresTracing) {
const tracingLabel = 'Tracing Next.js lambdas for external files ...';
console.time(tracingLabel);
const { fileList } = await nodeFileTrace(
Object.keys(pages).map(page => pages[page].fsPath),
{ base: workPath }
);
if (config.debug) {
console.log(`node-file-trace result for pages: ${fileList}`);
}
fileList.forEach(file => {
tracedFiles[file] = new FileFsRef({
fsPath: path.join(workPath, file),
});
});
console.timeEnd(tracingLabel);
} else {
// An optional assets folder that is placed alongside every page
// entrypoint.
// This is a legacy feature that was needed before we began tracing
// lambdas.
assets = await glob(
'assets/**',
path.join(entryPath, '.next', 'serverless')
);
const assetKeys = Object.keys(assets!);
if (assetKeys.length > 0) {
console.log('detected (legacy) assets to be bundled with lambda:');
assetKeys.forEach(assetFile => console.log(`\t${assetFile}`));
console.log(
'\nPlease upgrade to Next.js 9.1 to leverage modern asset handling.'
);
}
}
const launcherPath = path.join(__dirname, 'templated-launcher.js');
const launcherData = await readFile(launcherPath, 'utf8');
await Promise.all(
pageKeys.map(async page => {
// These default pages don't have to be handled as they'd always 404
@@ -465,17 +530,34 @@ export const build = async ({
dynamicPages.push(normalizePage(pathname));
}
console.log(`Creating lambda for page: "${page}"...`);
const label = `Creating lambda for page: "${page}"...`;
console.time(label);
const pageFileName = path.relative(workPath, pages[page].fsPath);
const launcher = launcherData.replace(
/__LAUNCHER_PAGE_PATH__/g,
JSON.stringify(
requiresTracing ? path.join('./', pageFileName) : './page'
)
);
const launcherFiles = {
'now__bridge.js': new FileFsRef({
fsPath: path.join(__dirname, 'now__bridge.js'),
}),
'now__launcher.js': new FileBlob({ data: launcher }),
};
lambdas[path.join(entryDirectory, pathname)] = await createLambda({
files: {
...launcherFiles,
...assets,
'page.js': pages[page],
...tracedFiles,
[requiresTracing ? pageFileName : 'page.js']: pages[page],
},
handler: 'now__launcher.launcher',
runtime: nodeVersion.runtime,
});
console.log(`Created lambda for page: "${page}"`);
console.timeEnd(label);
})
);
}

View File

@@ -5,7 +5,8 @@ if (!process.env.NODE_ENV) {
import { Server } from 'http';
import { Bridge } from './now__bridge';
const page = require('./page');
// @ts-ignore
const page = require(__LAUNCHER_PAGE_PATH__);
// page.render is for React rendering
// page.default is for /api rendering

View File

@@ -356,6 +356,8 @@ function syncEnvVars(base: EnvConfig, removeEnv: EnvConfig, addEnv: EnvConfig) {
Object.assign(base, addEnv);
}
export const ExperimentalTraceVersion = `9.0.4-canary.1`;
export {
excludeFiles,
validateEntrypoint,

View File

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

View File

@@ -0,0 +1,8 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/next" }],
"probes": [
{ "path": "/nested/fb", "mustContain": "Hello Firebase: <!-- -->0" },
{ "path": "/nested/moar/fb", "mustContain": "Hello Firebase: <!-- -->0" }
]
}

View File

@@ -0,0 +1,8 @@
{
"dependencies": {
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"firebase": "6.3.4"
}
}

View File

@@ -0,0 +1,19 @@
import firebase from 'firebase/app';
import 'firebase/firestore';
if (!firebase.apps.length) {
firebase.initializeApp({ projectId: 'noop' });
}
const store = firebase.firestore();
const Comp = ({ results }) => {
return <div>Hello Firebase: {results}</div>;
};
Comp.getInitialProps = async () => {
const query = await store.collection('users').get();
return { results: query.size };
};
export default Comp;

View File

@@ -0,0 +1,19 @@
import firebase from 'firebase/app';
import 'firebase/firestore';
if (!firebase.apps.length) {
firebase.initializeApp({ projectId: 'noop' });
}
const store = firebase.firestore();
const Comp = ({ results }) => {
return <div>Hello Firebase: {results}</div>;
};
Comp.getInitialProps = async () => {
const query = await store.collection('users').get();
return { results: query.size };
};
export default Comp;

View File

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

View File

@@ -0,0 +1,8 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/next" }],
"probes": [
{ "path": "/hello1", "mustContain": "Hello World 1" },
{ "path": "/nested/hello2", "mustContain": "Hello World 2" }
]
}

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"next": "8.x.x",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1,7 @@
function A({ data }) {
return <div>{data}</div>;
}
A.getInitialProps = () => ({ data: 'Hello World 1' });
export default A;

View File

@@ -0,0 +1,7 @@
function B({ data }) {
return <div>{data}</div>;
}
B.getInitialProps = () => ({ data: 'Hello World 2' });
export default B;

View File

@@ -1,6 +1,6 @@
{
"name": "@now/node",
"version": "0.12.4",
"version": "0.12.6",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/node-js-now-node",
@@ -28,7 +28,7 @@
"@types/etag": "1.8.0",
"@types/test-listen": "1.1.0",
"@zeit/ncc": "0.20.4",
"@zeit/node-file-trace": "0.2.6",
"@zeit/node-file-trace": "0.2.9",
"content-type": "1.0.4",
"cookie": "0.4.0",
"etag": "1.8.1",

View File

@@ -239,7 +239,12 @@ async function compile(
}
}
// Rename .ts -> .js (except for entry)
if (path !== entrypoint && tsCompiled.has(path)) {
// There is a bug on Windows where entrypoint uses forward slashes
// and workPath uses backslashes so we use resolve before comparing.
if (
resolve(workPath, path) !== resolve(workPath, entrypoint) &&
tsCompiled.has(path)
) {
preparedFiles[
path.slice(0, -3 - Number(path.endsWith('x'))) + '.js'
] = entry;

View File

@@ -101,7 +101,7 @@ elif 'app' in __now_variables:
}
for key, value in environ.items():
if isinstance(value, string_types) and key != 'QUERY_STRING':
if isinstance(value, string_types):
environ[key] = wsgi_encoding_dance(value)
for key, value in headers.items():

View File

@@ -1,6 +1,6 @@
{
"name": "@now/python",
"version": "0.2.14",
"version": "0.2.15",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/python-now-python",

View File

@@ -0,0 +1,8 @@
from flask import Flask, Response, request, __version__
app = Flask(__name__)
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
qs = request.args.to_dict()
return Response("path: %s query: %s" %(path, qs), mimetype='text/html')

View File

@@ -0,0 +1,8 @@
from flask import Flask, Response, request, __version__
app = Flask(__name__)
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
qs = request.args.to_dict()
return Response("path: %s query: %s" %(path, qs), mimetype='text/html')

View File

@@ -0,0 +1,15 @@
{
"version": 2,
"builds": [{ "src": "*.py", "use": "@now/python" }],
"routes": [{ "src": "/%E6%82%A8%E5%A5%BD", "dest": "custom.py" }],
"probes": [
{
"path": "/?%E6%82%A8%E5%A5%BD=/",
"mustContain": "path: query: {'您好': '/'}"
},
{
"path": "/%E6%82%A8%E5%A5%BD?hello=/",
"mustContain": "path: 您好 query: {'hello': '/'}"
}
]
}

View File

@@ -0,0 +1 @@
Flask==1.0.2

View File

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

View File

@@ -70,14 +70,6 @@ export function normalizeRoutes(
handling.push(route.handle);
}
} else if (route.src) {
// typeof normal route
if (route.continue && route.dest) {
errors.push({
message: `Cannot use both continue and dest`,
src: route.src,
});
}
// Route src should always start with a '^'
if (!route.src.startsWith('^')) {
route.src = `^${route.src}`;

View File

@@ -115,12 +115,6 @@ describe('normalizeRoutes', () => {
handle: 'filesystem',
});
routes.push({ src: '^/about$', dest: '/about', continue: true });
errors.push({
message: 'Cannot use both continue and dest',
src: '^/about$',
});
routes.push({ src: '^/(broken]$' });
errors.push({
message: 'Invalid regular expression: "^/(broken]$"',

View File

@@ -1,6 +1,6 @@
{
"name": "@now/static-build",
"version": "0.9.5",
"version": "0.9.7",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/static-build-now-static-build",

View File

@@ -65,23 +65,23 @@ export default [
dependency: '@vue/cli-service',
getOutputDirName: async () => 'dist',
defaultRoutes: [
{
handle: 'filesystem',
},
{
src: '^/js/(.*)',
headers: { 'cache-control': 'max-age=31536000, immutable' },
dest: '/js/$1',
continue: true,
},
{
src: '^/css/(.*)',
headers: { 'cache-control': 'max-age=31536000, immutable' },
dest: '/css/$1',
continue: true,
},
{
src: '^/img/(.*)',
headers: { 'cache-control': 'max-age=31536000, immutable' },
dest: '/img/$1',
continue: true,
},
{
handle: 'filesystem',
},
{
src: '/(.*)',
@@ -158,33 +158,20 @@ export default [
{
src: '/static/(.*)',
headers: { 'cache-control': 's-maxage=31536000, immutable' },
dest: '/static/$1',
},
{
src: '/favicon.ico',
dest: '/favicon.ico',
},
{
src: '/asset-manifest.json',
dest: '/asset-manifest.json',
},
{
src: '/manifest.json',
dest: '/manifest.json',
},
{
src: '/precache-manifest.(.*)',
dest: '/precache-manifest.$1',
continue: true,
},
{
src: '/service-worker.js',
headers: { 'cache-control': 's-maxage=0' },
dest: '/service-worker.js',
continue: true,
},
{
src: '/sockjs-node/(.*)',
dest: '/sockjs-node/$1',
},
{
handle: 'filesystem',
},
{
src: '/(.*)',
headers: { 'cache-control': 's-maxage=0' },

View File

@@ -1,9 +1,14 @@
{
"version": 2,
"builds": [
{ "src": "package.json", "use": "@now/static-build", "config": { "zeroConfig": true } }
{
"src": "package.json",
"use": "@now/static-build",
"config": { "zeroConfig": true }
}
],
"probes": [
{ "path": "/", "mustContain": "React App" }
{ "path": "/", "mustContain": "React App" },
{ "path": "/testing-output-dir.txt", "mustContain": "This is some content" }
]
}

View File

@@ -0,0 +1 @@
This is some content

View File

@@ -1,10 +1,24 @@
{
"version": 2,
"builds": [
{ "src": "package.json", "use": "@now/static-build", "config": { "zeroConfig": true } }
{
"src": "package.json",
"use": "@now/static-build",
"config": { "zeroConfig": true }
}
],
"probes": [
{ "path": "/", "mustContain": "13-vue" },
{
"path": "/js/app.js",
"headers": { "cache-control": "max-age=31536000, immutable" },
"mustContain": "function"
},
{
"path": "/css/app.css",
"headers": { "cache-control": "max-age=31536000, immutable" },
"mustContain": "font-family"
},
{ "path": "/923h3223329ddas", "mustContain": "13-vue" }
]
}

View File

@@ -0,0 +1,5 @@
module.exports = {
// This is necessary for our test probes
// so that the generated file names are deterministic.
filenameHashing: false,
};

View File

@@ -90,6 +90,16 @@ async function testDeployment (
const { text, resp } = await fetchDeploymentUrl(probeUrl, fetchOpts);
console.log('finished testing', JSON.stringify(probe));
if (probe.status) {
if (probe.status !== resp.status) {
throw new Error(
`Fetched page ${probeUrl} does not return the status ${
probe.status
} Instead it has ${resp.status}`
);
}
}
if (probe.mustContain) {
if (!text.includes(probe.mustContain)) {
await fs.writeFile(path.join(__dirname, 'failed-page.txt'), text);
@@ -117,7 +127,7 @@ async function testDeployment (
);
}
});
} else {
} else if (!probe.status) {
assert(false, 'probe must have a test condition');
}
}
@@ -140,14 +150,10 @@ async function nowDeployIndexTgz (file) {
}
async function fetchDeploymentUrl (url, opts) {
for (let i = 0; i < 500; i += 1) {
for (let i = 0; i < 50; i += 1) {
const resp = await fetch(url, opts);
const text = await resp.text();
if (
text
&& !text.includes('Join Free')
&& !text.includes('The page could not be found')
) {
if (text && !text.includes('Join Free')) {
return { resp, text };
}

View File

@@ -1480,10 +1480,10 @@
resolved "https://registry.yarnpkg.com/@zeit/ncc/-/ncc-0.20.4.tgz#00f0a25a88cac3712af4ba66561d9e281c6f05c9"
integrity sha512-fmq+F/QxPec+k/zvT7HiVpk7oiGFseS6brfT/AYqmCUp6QFRK7vZf2Ref46MImsg/g2W3g5X6SRvGRmOAvEfdA==
"@zeit/node-file-trace@0.2.6":
version "0.2.6"
resolved "https://registry.yarnpkg.com/@zeit/node-file-trace/-/node-file-trace-0.2.6.tgz#0884bf7a7176f6aa865a40c8c8b95513d59ee463"
integrity sha512-b6OPed+EtVPMNdSdumBPIBFq0h6CgvVj7sa4Lmbq/KJu9KnOJ19Vu4YrJvSWRjgzP4SJqlQ4ya/HUXRP/LWVog==
"@zeit/node-file-trace@0.2.9":
version "0.2.9"
resolved "https://registry.yarnpkg.com/@zeit/node-file-trace/-/node-file-trace-0.2.9.tgz#176aa55ae4800bfc847076b69b33df3bebc60201"
integrity sha512-OZU4HqNwlCEDIW67WTQnRjQ0ML7o9O8boPf1f9ffZbliKWRJrBPU+ydqvUtJeHICmY5Cjy9MQxwzo+q81G3uAA==
dependencies:
acorn "^6.1.1"
acorn-stage3 "^2.0.0"