Compare commits

..

38 Commits

Author SHA1 Message Date
Nathan Rajlich
a6ec53d9d3 Publish Canary
- vercel@21.0.2-canary.6
 - @vercel/node-bridge@1.3.2-canary.0
 - @vercel/node@1.8.6-canary.3
 - @vercel/static-build@0.18.1-canary.1
 - @vercel/redwood@0.2.1-canary.0
2021-01-04 16:27:14 -08:00
Nathan Rajlich
3ad5903f70 [node-bridge] Move launcher.ts to @vercel/node-bridge (#5634)
The `launcher.ts` file is more closely aligned to the node bridge package, as it's used by other runtimes as well.
2021-01-04 16:25:27 -08:00
Nathan Rajlich
3cf155e999 [tests] Retry fetch upon "ECONNREFUSED" (#5635)
CI failed due to this error code, which can be retried.
2021-01-04 16:06:28 -08:00
JJ Kasper
d9a298d97c Publish Canary
- @vercel/next@2.7.8-canary.0
2021-01-04 15:15:21 -06:00
JJ Kasper
487d3c8554 [next] Ensure 404 route is correct for GSP/GIP 404 (#5632)
This is a follow-up to https://github.com/vercel/vercel/pull/5618 ensuring the 404 route is pointing to the static 404 output correctly when `_app.gip` and getStaticProps in `/404.js` is used

### Related Issues

Fixes: https://github.com/vercel/next.js/issues/19849

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [ ] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2021-01-04 20:53:29 +00:00
JJ Kasper
9d0bfd3656 Publish Stable
- @vercel/next@2.7.7
2021-01-02 12:24:18 -06:00
JJ Kasper
ec75333569 [next] Fix next test files lint (#5619) 2020-12-31 17:10:52 -06:00
JJ Kasper
51aab912a2 Publish Canary
- @vercel/next@2.7.7-canary.2
2020-12-31 16:01:02 -06:00
JJ Kasper
670b2653c0 [next] Ensure 404 is static with _app.GIP and 404 GSP (#5618) 2020-12-31 15:47:18 -06:00
JJ Kasper
f71686fdad Publish Canary
- vercel@21.0.2-canary.5
 - @vercel/next@2.7.7-canary.1
 - @vercel/node@1.8.6-canary.2
2020-12-29 18:26:39 -06:00
Joe Haddad
ec9c8ce150 [next] share cache between nft runs (#5614)
This should result in faster Lambda tracing, as it avoids duplicate work.
2020-12-29 19:04:47 -05:00
Steven
a2048fc6d3 [node][next] Bump nft to 0.9.5 (#5581)
This PR bumps nft from version 0.9.4 to [0.9.5](https://github.com/vercel/nft/releases/tag/0.9.5).
2020-12-28 12:23:03 -05:00
Steven
09ff9cda9f [docs] Fix error message when credentials are missing (#5590) 2020-12-21 12:12:32 -08:00
Nathan Rajlich
3a4d6f7848 Publish Canary
- vercel@21.0.2-canary.4
 - @vercel/node@1.8.6-canary.1
2020-12-21 10:11:14 -08:00
Andy
9a0d676c0d Remove "Add a new framework" CONTRIBUTING.md (#5583) 2020-12-18 12:34:50 -08:00
Nathan Rajlich
25cd7b9e6e [cli] Fix vc logs -o raw and add test (#5571)
There are events (i.e. with `type: "deployment-event"`) that do not
contain a `text` property, so check that it's a string before invoking
`.replace()` on it.

Fixes this stack trace:

```
Error! An unexpected error occurred in logs: TypeError: Cannot read property 'replace' of undefined
    at Object.printLogRaw [as raw] (/home/me/nextjs-site/node_modules/vercel/dist/index.js:203803:10)
    at printEvent (/home/me/nextjs-site/node_modules/vercel/dist/index.js:203688:35)
    at Array.forEach (<anonymous>)
    at main (/home/me/nextjs-site/node_modules/vercel/dist/index.js:203690:31)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at main (/home/me/nextjs-site/node_modules/vercel/dist/index.js:207173:16)
```
2020-12-16 16:16:36 -08:00
Steven
f926d5516c [examples] Update gatsby to use @vercel/node (#5567)
[`@now/node`](https://www.npmjs.com/package/@now/node) is deprecated in favor of [`@vercel/node`](https://www.npmjs.com/package/@vercel/node)
2020-12-14 18:29:10 -05:00
Nathan Rajlich
4603383850 [node] Add Vercel prefixed TypeScript types (#5568)
The `Now` prefixed types remain as aliases for backwards-compat purposes.

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
2020-12-14 18:26:22 -05:00
Nathan Rajlich
c0c57889c8 Publish Canary
- @vercel/build-utils@2.6.1-canary.2
 - vercel@21.0.2-canary.3
 - @vercel/client@9.0.5-canary.2
 - @vercel/next@2.7.7-canary.0
 - @vercel/static-build@0.18.1-canary.0
2020-12-14 09:59:43 -08:00
Nathan Rajlich
85908a0524 [build-utils][static-build][next] Force YARN_NODE_LINKER="node-modules" env var when executing yarn (#5552)
### Related Issues

* https://vercel.com/knowledge/does-vercel-support-yarn-2
* https://github.com/vercel/vercel/discussions/4223
* https://github.com/vercel/vercel/discussions/4910
* https://github.com/vercel/vercel/issues/5136
* https://github.com/vercel/vercel/issues/5280

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2020-12-14 04:27:59 +00:00
Andy Bitz
503b9a2429 Publish Canary
- @vercel/build-utils@2.6.1-canary.1
 - vercel@21.0.2-canary.2
 - @vercel/client@9.0.5-canary.1
2020-12-11 19:02:19 +01:00
Andy
eac8f32ae7 [cli][build-utils] Change type to handle builds without a src property (#5553)
* [cli][build-utils] Change type to handle builds without a `src` property

* Add test

* Update unit tests

* Update test

* Undo test changes
2020-12-11 19:00:49 +01:00
dependabot[bot]
3f76fefde6 Bump ini from 1.3.5 to 1.3.7 (#5550)
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7.
<details>
<summary>Commits</summary>
<ul>
<li><a href="c74c8af35f"><code>c74c8af</code></a> 1.3.7</li>
<li><a href="024b8b55ac"><code>024b8b5</code></a> update deps, add linting</li>
<li><a href="032fbaf5f0"><code>032fbaf</code></a> Use Object.create(null) to avoid default object property hazards</li>
<li><a href="2da90391ef"><code>2da9039</code></a> 1.3.6</li>
<li><a href="cfea636f53"><code>cfea636</code></a> better git push script, before publish instead of after</li>
<li><a href="56d2805e07"><code>56d2805</code></a> do not allow invalid hazardous string as section name</li>
<li>See full diff in <a href="https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7">compare view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a href="https://www.npmjs.com/~isaacs">isaacs</a>, a new releaser for ini since your current version.</p>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ini&package-manager=npm_and_yarn&previous-version=1.3.5&new-version=1.3.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language

You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/vercel/vercel/network/alerts).

</details>
2020-12-10 22:09:11 +00:00
Steven
79ddc5746b Publish Canary
- @vercel/routing-utils@1.9.2-canary.4
2020-12-10 15:14:12 -05:00
Javi Velasco
9a14615b43 [routing-utils] More accurate schema (#5477)
This PR completes the routes schema to be more accurate for the provided type.
This will allow us to generate the type dynamically from the JSON Schema
2020-12-10 20:10:57 +00:00
JJ Kasper
6d6ccbdc25 [tests] Add retrying for GitHub actions connection error (#5533)
Follow-up to https://github.com/vercel/vercel/pull/5526 this also adds retrying for fetch connection issues in GitHub Actions to prevent false test failures.

x-ref: https://github.com/vercel/vercel/runs/1518604380
2020-12-09 21:11:01 +00:00
JJ Kasper
704424ec58 Publish Stable
- @vercel/next@2.7.6
2020-12-08 10:49:48 -06:00
JJ Kasper
c471127c69 Publish Canary
- @vercel/next@2.7.6-canary.1
2020-12-08 10:33:13 -06:00
JJ Kasper
c02dc9ac49 [next] Add public dir check and update tests (#5526)
* Add public dir check and update tests

* De-dupe test checks a bit
2020-12-08 10:25:55 -05:00
JJ Kasper
dd10e8cc77 Publish Canary
- @vercel/next@2.7.6-canary.0
 - @vercel/routing-utils@1.9.2-canary.3
2020-12-01 16:11:08 -06:00
Connor Davis
cf69c2398c [next] Add Important Headers to Avoid Users Overriding Cache Headers (#5452)
We want to ensure users cannot override Next.js' cache-control headers

### Related Issues

https://app.clubhouse.io/vercel/story/14921/add-important-headers-to-routes
https://app.clubhouse.io/vercel/story/15528/add-important-to-cache-control-headers-in-next-js

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [x] Issue from task tracker has a link to this PR


Fixes: https://github.com/vercel/vercel/pull/5452
2020-12-01 22:08:50 +00:00
JJ Kasper
e48707571f Publish Stable
- @vercel/next@2.7.5
2020-11-30 11:06:59 -06:00
JJ Kasper
6051fe6f0c Publish Canary
- @vercel/next@2.7.5-canary.1
2020-11-30 11:00:43 -06:00
JJ Kasper
2b3ba8a14f [next] Make sure to use outputDirectory when loading prerenderManifest (#5487)
* Make sure to use outputDirectory when loading prerenderManifest

* Add test case
2020-11-30 10:57:56 -06:00
Steven
25bea3f83e Publish Canary
- vercel@21.0.2-canary.1
 - @vercel/next@2.7.5-canary.0
 - @vercel/node@1.8.6-canary.0
2020-11-24 15:24:52 -05:00
Steven
b3e1828ebe [node][next] Bump nft to 0.9.4 (#5455)
This PR bumps nft to [0.9.4](https://github.com/vercel/nft/releases/tag/0.9.4)
2020-11-24 14:32:46 -05:00
Andy Bitz
e0e2a8e87e Publish Canary
- @vercel/routing-utils@1.9.2-canary.2
2020-11-24 19:52:19 +01:00
Andy
37ec89796d [routing-utils] Handle null phase at the beginning (#5470)
* [routing-utils] Handle `null` phase at the beginning

* Add fixed list

* Add tests

* Change hit and miss order

* Put null at the start

* Undo order changes

Co-authored-by: Steven <steven@ceriously.com>
2020-11-24 19:51:06 +01:00
85 changed files with 6864 additions and 351 deletions

View File

@@ -94,12 +94,3 @@ Sometimes you want to test changes to a Builder against an existing project, may
4. Run `vercel *.tgz` to upload the tarball file and get a URL
5. Edit any existing `vercel.json` project and replace `use` with the URL
6. Run `vercel` or `vercel dev` to deploy with the experimental Builder
## Add a New Framework
You can add support for a new Framework by creating a Pull Request for this repository and following the steps below:
1. Add the Framework to the `@vercel/frameworks` package: The file is located in `./packages/frameworks/frameworks.json`. You can copy the structure of an existing one and adjust the required fields. Note that the `settings` property either contains a `value` or a `placeholder`. The `value` property is used when something is not configurable, the `placeholder` is used when something is configurable and can be changed with configuration. An example would be the Output Directory for Hugo, it's `public` by default but can be changed through its config file, so we use `placeholder` with an explanation of what can be used.
2. Add an example to the `./examples` directory: The name of the directory should equal the slug of the framework used in `@vercel/frameworks`. The `.github/EXAMPLE_README_TEMPLATE.md` file can be used to create a `README.md` file for the example.
3. Update the `@vercel/static-build` package: The file `./packages/now-static-build/src/frameworks.ts` has to be extended. You can add default routes that will always be applied to projects that use this Framework or specify some paths that will be cached to speed up the build process.
4. After your Pull Request has been merged and released, other users can select the example on the Vercel dashboard and deploy it.

View File

@@ -6,5 +6,5 @@ You're running Vercel CLI in a non-terminal context and there are no credentials
#### Possible Ways to Fix It
- Specify a value for the `--token` flag (this needs to be the token of the user account as which you'd like to act). You can either get the token from the `./vercel/auth.json` file located in your user directory or [from the dashboard](https://vercel.com/account/tokens).
- Ensure that both `~/vercel/auth.json` and `~/vercel/config.json` exist
- Specify a value for the `--token` flag (this needs to be the token of the user account as which you'd like to act). You can create a new token on your [Settings page](https://vercel.com/account/tokens).
- Run `vercel login` to sign in and generate a new token

View File

@@ -1,4 +1,4 @@
import { NowRequest, NowResponse } from '@now/node';
import { NowRequest, NowResponse } from '@vercel/node';
export default (_req: NowRequest, res: NowResponse) => {
const date = new Date().toString();

View File

@@ -12,7 +12,7 @@
"react-helmet": "^5.2.0"
},
"devDependencies": {
"@now/node": "^1.3.0"
"@vercel/node": "1.8.5"
},
"scripts": {
"dev": "gatsby develop",

View File

@@ -1107,15 +1107,6 @@
"@nodelib/fs.scandir" "2.1.3"
fastq "^1.6.0"
"@now/node@^1.3.0":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@now/node/-/node-1.7.1.tgz#764a0c6bcb24967f8014c4f73ad238c292996fe3"
integrity sha512-+srVKopsVTPDR3u9eOjJryZroLTrPp8XEOuIDGBdfFcJuS7qpAomctSbfyA7WNyjC0ExtUxELqBg5sAedG5+2g==
dependencies:
"@types/node" "*"
ts-node "8.9.1"
typescript "3.9.3"
"@pieh/friendly-errors-webpack-plugin@1.7.0-chalk-2":
version "1.7.0-chalk-2"
resolved "https://registry.yarnpkg.com/@pieh/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.7.0-chalk-2.tgz#2e9da9d3ade9d18d013333eb408c457d04eabac0"
@@ -1409,6 +1400,15 @@
dependencies:
wonka "^4.0.14"
"@vercel/node@1.8.5":
version "1.8.5"
resolved "https://registry.yarnpkg.com/@vercel/node/-/node-1.8.5.tgz#2c8b9532f1bb25734a9964c52973386ed78022d4"
integrity sha512-1iw7FSR8Oau6vZB1MWfBnA5q2a/IqRHiSZSbt8lz0dyTF599q8pc5GcSv/TvmrYaEGzh3+N0S4cbmuMCqVlwJg==
dependencies:
"@types/node" "*"
ts-node "8.9.1"
typescript "3.9.3"
"@webassemblyjs/ast@1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "2.6.1-canary.0",
"version": "2.6.1-canary.2",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",

View File

@@ -43,9 +43,8 @@ export function sortFiles(fileA: string, fileB: string) {
export function detectApiExtensions(builders: Builder[]): Set<string> {
return new Set<string>(
builders
.filter(
b =>
b.config && b.config.zeroConfig && b.src && b.src.startsWith('api/')
.filter((b): b is Builder & { src: string } =>
Boolean(b.config && b.config.zeroConfig && b.src?.startsWith('api/'))
)
.map(b => extname(b.src))
.filter(Boolean)
@@ -56,22 +55,28 @@ export function detectApiDirectory(builders: Builder[]): string | null {
// TODO: We eventually want to save the api directory to
// builder.config.apiDirectory so it is only detected once
const found = builders.some(
b => b.config && b.config.zeroConfig && b.src.startsWith('api/')
b => b.config && b.config.zeroConfig && b.src?.startsWith('api/')
);
return found ? 'api' : null;
}
// TODO: Replace this function with `config.outputDirectory`
function getPublicBuilder(builders: Builder[]): Builder | null {
const builder = builders.find(
builder =>
function getPublicBuilder(
builders: Builder[]
): (Builder & { src: string }) | null {
for (const builder of builders) {
if (
typeof builder.src === 'string' &&
isOfficialRuntime('static', builder.use) &&
/^.*\/\*\*\/\*$/.test(builder.src) &&
builder.config &&
builder.config.zeroConfig === true
);
) {
return builder as Builder & { src: string };
}
}
return builder || null;
return null;
}
export function detectOutputDirectory(builders: Builder[]): string | null {
// TODO: We eventually want to save the output directory to
@@ -361,7 +366,7 @@ function maybeGetApiBuilder(
return null;
}
const match = apiMatches.find(({ src }) => {
const match = apiMatches.find(({ src = '**' }) => {
return src === fileName || minimatch(fileName, src);
});

View File

@@ -292,6 +292,11 @@ export async function runNpmInstall(
opts.prettyCommand = 'yarn install';
command = 'yarn';
commandArgs = ['install', ...args];
// Yarn v2 PnP mode may be activated, so force "node-modules" linker style
if (!env.YARN_NODE_LINKER) {
env.YARN_NODE_LINKER = 'node-modules';
}
}
if (process.env.NPM_ONLY_PRODUCTION) {
@@ -388,10 +393,17 @@ export async function runPackageJsonScript(
prettyCommand,
});
} else {
// Yarn v2 PnP mode may be activated, so force "node-modules" linker style
const env: typeof process.env = { ...spawnOpts?.env };
if (!env.YARN_NODE_LINKER) {
env.YARN_NODE_LINKER = 'node-modules';
}
const prettyCommand = `yarn run ${scriptName}`;
console.log(`Running "${prettyCommand}"`);
await spawnAsync('yarn', ['run', scriptName], {
...spawnOpts,
env,
cwd: destPath,
prettyCommand,
});

View File

@@ -336,7 +336,7 @@ export interface NodeVersion {
export interface Builder {
use: string;
src: string;
src?: string;
config?: Config;
}

View File

@@ -0,0 +1,5 @@
const { camelCase } = require('camel-case');
module.exports = (req, res) => {
res.end(camelCase('camel-case module is working'));
};

View File

@@ -1,5 +0,0 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@vercel/static-build" }],
"probes": [{ "path": "/", "mustContain": "Svelte app" }]
}

View File

@@ -16,6 +16,7 @@
"svelte": "^3.0.0"
},
"dependencies": {
"camel-case": "^4.1.2",
"sirv-cli": "^0.4.4"
}
}

View File

@@ -0,0 +1,11 @@
{
"version": 2,
"builds": [
{ "src": "package.json", "use": "@vercel/static-build" },
{ "src": "api/index.js", "use": "@vercel/node" }
],
"probes": [
{ "path": "/", "mustContain": "Svelte app" },
{ "path": "/api", "mustContain": "camelCaseModuleIsWorking" }
]
}

View File

@@ -270,6 +270,16 @@ __metadata:
languageName: node
linkType: hard
"camel-case@npm:^4.1.2":
version: 4.1.2
resolution: "camel-case@npm:4.1.2"
dependencies:
pascal-case: ^3.1.2
tslib: ^2.0.3
checksum: 3/0b8dcfb424c9497e45984b88ef005c66bdf8e877e36365aedfc3cf73182684fde5a14cf2c526579c0351a5f27dc39a00f1edecc25d43606075fea948c504e37f
languageName: node
linkType: hard
"caseless@npm:~0.12.0":
version: 0.12.0
resolution: "caseless@npm:0.12.0"
@@ -850,6 +860,15 @@ fsevents@~2.1.2:
languageName: node
linkType: hard
"lower-case@npm:^2.0.2":
version: 2.0.2
resolution: "lower-case@npm:2.0.2"
dependencies:
tslib: ^2.0.3
checksum: 3/aabaca9cef65f7564a1005b625664527e4d169e363101e65773f8f6ff2fdcf09884a3bc02990cd7a62cf05f3538114af25ee7bef553f1ca3208c8a77ac75cbfa
languageName: node
linkType: hard
"magic-string@npm:^0.25.2":
version: 0.25.7
resolution: "magic-string@npm:0.25.7"
@@ -944,6 +963,16 @@ fsevents@~2.1.2:
languageName: node
linkType: hard
"no-case@npm:^3.0.4":
version: 3.0.4
resolution: "no-case@npm:3.0.4"
dependencies:
lower-case: ^2.0.2
tslib: ^2.0.3
checksum: 3/84db4909caec37504c6655f995a004067f8733be8cd8d849f1578661b60a1685e086325fa4e1a5e8ce94e7416c1d0f037e2a00f635a14457183de80ab4fc7612
languageName: node
linkType: hard
"node-gyp@npm:latest":
version: 6.1.0
resolution: "node-gyp@npm:6.1.0"
@@ -1057,6 +1086,16 @@ fsevents@~2.1.2:
languageName: node
linkType: hard
"pascal-case@npm:^3.1.2":
version: 3.1.2
resolution: "pascal-case@npm:3.1.2"
dependencies:
no-case: ^3.0.4
tslib: ^2.0.3
checksum: 3/31708cecab221482edc81e2bd9b9d8282d72d4f1443b31f39725aa23768c5e42d93c4c014f1bc90f7f074e2a70d5091e4892ea370e550affc9ccf1d33c900bcd
languageName: node
linkType: hard
"path-is-absolute@npm:^1.0.0":
version: 1.0.1
resolution: "path-is-absolute@npm:1.0.1"
@@ -1447,6 +1486,7 @@ fsevents@~2.1.2:
dependencies:
"@rollup/plugin-commonjs": ^12.0.0
"@rollup/plugin-node-resolve": ^8.0.0
camel-case: ^4.1.2
rollup: ^2.3.4
rollup-plugin-livereload: ^1.0.0
rollup-plugin-svelte: ^5.0.3
@@ -1517,6 +1557,13 @@ fsevents@~2.1.2:
languageName: node
linkType: hard
"tslib@npm:^2.0.3":
version: 2.0.3
resolution: "tslib@npm:2.0.3"
checksum: 3/447bfca5deaa157806c3f77eaba74d05dd0b38b014e47ce79d98b5c77ce7d91b00a687ba13ca1b5a74d35ca1098ac7a072c0a97fad06f0266612f2a03a6c8e8f
languageName: node
linkType: hard
"tunnel-agent@npm:^0.6.0":
version: 0.6.0
resolution: "tunnel-agent@npm:0.6.0"

View File

@@ -182,7 +182,7 @@ describe('Test `detectBuilders`', () => {
const { builders } = await detectBuilders(files);
expect(builders!.length).toBe(7);
expect(builders!.some(b => b.src.endsWith('_test.go'))).toBe(false);
expect(builders!.some(b => b.src!.endsWith('_test.go'))).toBe(false);
});
it('just public', async () => {
@@ -1341,7 +1341,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
featHandleMiss,
});
expect(builders!.length).toBe(7);
expect(builders!.some(b => b.src.endsWith('_test.go'))).toBe(false);
expect(builders!.some(b => b.src!.endsWith('_test.go'))).toBe(false);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "21.0.2-canary.0",
"version": "21.0.2-canary.6",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -61,9 +61,9 @@
"node": ">= 10"
},
"dependencies": {
"@vercel/build-utils": "2.6.1-canary.0",
"@vercel/build-utils": "2.6.1-canary.2",
"@vercel/go": "1.1.6",
"@vercel/node": "1.8.5",
"@vercel/node": "1.8.6-canary.3",
"@vercel/python": "1.2.3",
"@vercel/ruby": "1.2.4",
"update-notifier": "4.1.0"

View File

@@ -324,7 +324,7 @@ function printLogRaw(log) {
if (log.object) {
console.log(log.object);
} else {
} else if (typeof log.text === 'string') {
console.log(
log.text
.replace(/\n$/, '')

View File

@@ -402,7 +402,7 @@ export async function getBuildMatches(
const builds = nowConfig.builds || [{ src: '**', use: '@vercel/static' }];
for (const buildConfig of builds) {
let { src, use } = buildConfig;
let { src = '**', use } = buildConfig;
if (!use) {
continue;

View File

@@ -50,6 +50,7 @@ export interface EnvConfigs {
export interface BuildMatch extends BuildConfig {
entrypoint: string;
src: string;
builderWithPkg: BuilderWithPackage;
buildOutput: BuilderOutputs;
buildResults: Map<string | null, BuildResult>;

View File

@@ -0,0 +1,4 @@
package.json
yarn.lock
.now
.vercel

View File

@@ -0,0 +1 @@
hello:index.txt

View File

@@ -0,0 +1,7 @@
{
"builds": [
{
"use": "@vercel/static"
}
]
}

View File

@@ -1756,3 +1756,11 @@ test(
});
})
);
test(
'[vercel dev] Do not fail if `src` is missing',
testFixtureStdio('missing-src-property', async testPath => {
await testPath(200, '/', /hello:index.txt/m);
await testPath(404, '/i-do-not-exist');
})
);

View File

@@ -1566,7 +1566,7 @@ test('ensure we render a warning for deployments with no files', async t => {
t.is(res.status, 404);
});
test('output logs of a 2.0 deployment', async t => {
test('output logs with "short" output', async t => {
const { stderr, stdout, exitCode } = await execa(
binaryPath,
['logs', context.deployment, ...defaultArgs],
@@ -1583,13 +1583,21 @@ test('output logs of a 2.0 deployment', async t => {
stderr.includes(`Fetched deployment "${context.deployment}"`),
formatOutput({ stderr, stdout })
);
// "short" format includes timestamps
t.truthy(
stdout.match(
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/
)
);
t.is(exitCode, 0);
});
test('output logs of a 2.0 deployment without annotate', async t => {
test('output logs with "raw" output', async t => {
const { stderr, stdout, exitCode } = await execa(
binaryPath,
['logs', context.deployment, ...defaultArgs],
['logs', context.deployment, ...defaultArgs, '--output', 'raw'],
{
reject: false,
}
@@ -1599,12 +1607,19 @@ test('output logs of a 2.0 deployment without annotate', async t => {
console.log(stdout);
console.log(exitCode);
t.true(!stderr.includes('[now-builder-debug]'));
t.true(!stderr.includes('START RequestId'));
t.true(!stderr.includes('END RequestId'));
t.true(!stderr.includes('REPORT RequestId'));
t.true(!stderr.includes('Init Duration'));
t.true(!stderr.includes('XRAY TraceId'));
t.true(
stderr.includes(`Fetched deployment "${context.deployment}"`),
formatOutput({ stderr, stdout })
);
// "raw" format does not include timestamps
t.is(
null,
stdout.match(
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/
)
);
t.is(exitCode, 0);
});

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "9.0.5-canary.0",
"version": "9.0.5-canary.2",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -37,7 +37,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "2.6.1-canary.0",
"@vercel/build-utils": "2.6.1-canary.2",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "2.7.4",
"version": "2.7.8-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -26,7 +26,7 @@
"@types/resolve-from": "5.0.1",
"@types/semver": "6.0.0",
"@types/yazl": "2.4.1",
"@vercel/nft": "0.9.2",
"@vercel/nft": "0.9.5",
"async-sema": "3.0.1",
"buffer-crc32": "0.2.13",
"escape-string-regexp": "2.0.0",

View File

@@ -381,6 +381,14 @@ export async function build({
console.log(`Running "install" command: \`${installCommand}\`...`);
await execCommand(installCommand, {
...spawnOpts,
// Yarn v2 PnP mode may be activated, so force
// "node-modules" linker style
env: {
YARN_NODE_LINKER: 'node-modules',
...spawnOpts.env,
},
cwd: entryPath,
});
} else {
@@ -410,7 +418,7 @@ export async function build({
}
const memoryToConsume = Math.floor(os.totalmem() / 1024 ** 2) - 128;
const env: { [key: string]: string | undefined } = { ...spawnOpts.env };
const env: typeof process.env = { ...spawnOpts.env };
env.NODE_OPTIONS = `--max_old_space_size=${memoryToConsume}`;
if (buildCommand) {
@@ -418,6 +426,11 @@ export async function build({
const nodeBinPath = await getNodeBinPath({ cwd: entryPath });
env.PATH = `${nodeBinPath}${path.delimiter}${env.PATH}`;
// Yarn v2 PnP mode may be activated, so force "node-modules" linker style
if (!env.YARN_NODE_LINKER) {
env.YARN_NODE_LINKER = 'node-modules';
}
debug(
`Added "${nodeBinPath}" to PATH env because a build command was used.`
);
@@ -445,7 +458,10 @@ export async function build({
nextVersion
);
const imagesManifest = await getImagesManifest(entryPath, outputDirectory);
const prerenderManifest = await getPrerenderManifest(entryPath);
const prerenderManifest = await getPrerenderManifest(
entryPath,
outputDirectory
);
const headers: Route[] = [];
const rewrites: Route[] = [];
let redirects: Route[] = [];
@@ -759,6 +775,7 @@ export async function build({
'cache-control': `public,max-age=${MAX_AGE_ONE_YEAR},immutable`,
},
continue: true,
important: true,
},
// error handling
@@ -1020,7 +1037,7 @@ export async function build({
if (
initialRevalidate === false &&
!canUsePreviewMode &&
(!canUsePreviewMode || (hasPages404 && routeKey === '/404')) &&
!prerenderManifest.fallbackRoutes[route] &&
!prerenderManifest.blockingFallbackRoutes[route]
) {
@@ -1072,19 +1089,25 @@ export async function build({
console.time(tracingLabel);
}
const nftCache = Object.create(null);
const {
fileList: apiFileList,
reasons: apiReasons,
} = await nodeFileTrace(apiPages, {
base: baseDir,
processCwd: entryPath,
cache: nftCache,
});
debug(`node-file-trace result for api routes: ${apiFileList}`);
const { fileList, reasons: nonApiReasons } = await nodeFileTrace(
nonApiPages,
{
base: baseDir,
processCwd: entryPath,
cache: nftCache,
}
);
@@ -1808,7 +1831,11 @@ export async function build({
});
}
if (!canUsePreviewMode) {
// If revalidate isn't enabled we force the /404 route to be static
// to match next start behavior otherwise getStaticProps would be
// recalled for each 404 URL path since Prerender is cached based
// on the URL path
if (!canUsePreviewMode || (hasPages404 && routeKey === '/404')) {
htmlFsRef.contentType = htmlContentType;
prerenders[outputPathPage] = htmlFsRef;
prerenders[outputPathData] = jsonFsRef;
@@ -1929,7 +1956,25 @@ export async function build({
path.join(entryPath, outputDirectory, 'static')
);
const staticFolderFiles = await glob('**', path.join(entryPath, 'static'));
const publicFolderFiles = await glob('**', path.join(entryPath, 'public'));
let publicFolderFiles: Files = {};
let publicFolderPath: string | undefined;
if (await pathExists(path.join(entryPath, 'public'))) {
publicFolderPath = path.join(entryPath, 'public');
} else if (
// check at the same level as the output directory also
await pathExists(path.join(entryPath, outputDirectory, '../public'))
) {
publicFolderPath = path.join(entryPath, outputDirectory, '../public');
}
if (publicFolderPath) {
debug(`Using public folder at ${publicFolderPath}`);
publicFolderFiles = await glob('**/*', publicFolderPath);
} else {
debug('No public folder found');
}
const staticFiles = Object.keys(nextStaticFiles).reduce(
(mappedFiles, file) => ({
@@ -1950,10 +1995,7 @@ export async function build({
const publicDirectoryFiles = Object.keys(publicFolderFiles).reduce(
(mappedFiles, file) => ({
...mappedFiles,
[path.join(
entryDirectory,
file.replace(/^public[/\\]+/, '')
)]: publicFolderFiles[file],
[path.join(entryDirectory, file)]: publicFolderFiles[file],
}),
{}
);
@@ -2329,6 +2371,7 @@ export async function build({
'cache-control': `public,max-age=${MAX_AGE_ONE_YEAR},immutable`,
},
continue: true,
important: true,
},
// error handling
@@ -2371,9 +2414,13 @@ export async function build({
),
status: 404,
headers: {
'x-nextjs-page': page404Path,
},
...(static404Page
? {}
: {
headers: {
'x-nextjs-page': page404Path,
},
}),
}
: {
src: path.join('/', entryDirectory, '.*'),

View File

@@ -829,11 +829,12 @@ export async function getExportStatus(
}
export async function getPrerenderManifest(
entryPath: string
entryPath: string,
outputDirectory: string
): Promise<NextPrerenderedRoutes> {
const pathPrerenderManifest = path.join(
entryPath,
'.next',
outputDirectory,
'prerender-manifest.json'
);

View File

@@ -1,6 +1,29 @@
/* eslint-env jest */
const fetch = require('node-fetch');
const cheerio = require('cheerio');
const { check, waitFor } = require('../../utils');
const fetch = require('../../../../../test/lib/deployment/fetch-retry');
async function checkForChange(url, initialValue, hardError) {
return check(
async () => {
const res = await fetch(url);
if (res.status !== 200) {
throw new Error(`Invalid status code ${res.status}`);
}
const $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
if (isNaN(props.random)) {
throw new Error(`Invalid random value ${props.random}`);
}
const newValue = props.random;
return initialValue !== newValue ? 'success' : 'fail';
},
'success',
hardError
);
}
module.exports = function (ctx) {
it('should revalidate content properly from /', async () => {
@@ -13,15 +36,15 @@ module.exports = function (ctx) {
expect($('#router-locale').text()).toBe('en-US');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('en-US');
await checkForChange(`${ctx.deploymentUrl}/`, initialRandom);
});
it('should revalidate content properly from /fr', async () => {
@@ -34,15 +57,15 @@ module.exports = function (ctx) {
expect($('#router-locale').text()).toBe('fr');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/fr`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('fr');
await checkForChange(`${ctx.deploymentUrl}/fr`, initialRandom);
});
it('should revalidate content properly from /nl-NL', async () => {
@@ -55,15 +78,15 @@ module.exports = function (ctx) {
expect($('#router-locale').text()).toBe('nl-NL');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/nl-NL`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('nl-NL');
await checkForChange(`${ctx.deploymentUrl}/nl-NL`, initialRandom);
});
it('should revalidate content properly from /second', async () => {
@@ -77,15 +100,15 @@ module.exports = function (ctx) {
expect($('#router-locale').text()).toBe('en-US');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/second`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('en-US');
await checkForChange(`${ctx.deploymentUrl}/second`, initialRandom);
});
it('should revalidate content properly from /fr/second', async () => {
@@ -99,15 +122,15 @@ module.exports = function (ctx) {
expect($('#router-locale').text()).toBe('fr');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/fr/second`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('fr');
await checkForChange(`${ctx.deploymentUrl}/fr/second`, initialRandom);
});
it('should revalidate content properly from /nl-NL/second', async () => {
@@ -121,14 +144,14 @@ module.exports = function (ctx) {
expect($('#router-locale').text()).toBe('nl-NL');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/nl-NL/second`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('nl-NL');
await checkForChange(`${ctx.deploymentUrl}/nl-NL/second`, initialRandom);
});
};

View File

@@ -1,6 +1,29 @@
/* eslint-env jest */
const fetch = require('node-fetch');
const cheerio = require('cheerio');
const { check, waitFor } = require('../../utils');
const fetch = require('../../../../../test/lib/deployment/fetch-retry');
async function checkForChange(url, initialValue, hardError) {
return check(
async () => {
const res = await fetch(url);
if (res.status !== 200) {
throw new Error(`Invalid status code ${res.status}`);
}
const $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
if (isNaN(props.random)) {
throw new Error(`Invalid random value ${props.random}`);
}
const newValue = props.random;
return initialValue !== newValue ? 'success' : 'fail';
},
'success',
hardError
);
}
module.exports = function (ctx) {
it('should revalidate content properly from /', async () => {
@@ -10,7 +33,7 @@ module.exports = function (ctx) {
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res = await fetch(`${ctx.deploymentUrl}/`);
expect(res.status).toBe(200);
@@ -22,16 +45,17 @@ module.exports = function (ctx) {
expect(JSON.parse($('#router-query').text())).toEqual({});
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('en-US');
expect(JSON.parse($('#router-query').text())).toEqual({});
await checkForChange(`${ctx.deploymentUrl}/`, initialRandom);
});
it('should revalidate content properly from /fr', async () => {
@@ -41,7 +65,7 @@ module.exports = function (ctx) {
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res = await fetch(`${ctx.deploymentUrl}/fr`);
expect(res.status).toBe(200);
@@ -53,16 +77,16 @@ module.exports = function (ctx) {
expect(JSON.parse($('#router-query').text())).toEqual({});
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/fr`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('fr');
expect(JSON.parse($('#router-query').text())).toEqual({});
await checkForChange(`${ctx.deploymentUrl}/fr`, initialRandom);
});
it('should revalidate content properly from /nl-NL', async () => {
@@ -72,7 +96,7 @@ module.exports = function (ctx) {
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res = await fetch(`${ctx.deploymentUrl}/nl-NL`);
expect(res.status).toBe(200);
@@ -84,16 +108,16 @@ module.exports = function (ctx) {
expect(JSON.parse($('#router-query').text())).toEqual({});
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/nl-NL`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('nl-NL');
expect(JSON.parse($('#router-query').text())).toEqual({});
await checkForChange(`${ctx.deploymentUrl}/nl-NL`, initialRandom);
});
it('should revalidate content properly from /gsp/fallback/first', async () => {
@@ -104,7 +128,7 @@ module.exports = function (ctx) {
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res = await fetch(`${ctx.deploymentUrl}/gsp/fallback/first`);
expect(res.status).toBe(200);
@@ -118,17 +142,21 @@ module.exports = function (ctx) {
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/gsp/fallback/first`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('en-US');
expect(props2.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
await checkForChange(
`${ctx.deploymentUrl}/gsp/fallback/first`,
initialRandom
);
});
it('should revalidate content properly from /fr/gsp/fallback/first', async () => {
@@ -139,7 +167,7 @@ module.exports = function (ctx) {
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res = await fetch(`${ctx.deploymentUrl}/fr/gsp/fallback/first`);
expect(res.status).toBe(200);
@@ -153,17 +181,21 @@ module.exports = function (ctx) {
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/fr/gsp/fallback/first`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('fr');
expect(props2.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
await checkForChange(
`${ctx.deploymentUrl}/fr/gsp/fallback/first`,
initialRandom
);
});
it('should revalidate content properly from /nl-NL/gsp/fallback/first', async () => {
@@ -174,7 +206,7 @@ module.exports = function (ctx) {
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res = await fetch(`${ctx.deploymentUrl}/nl-NL/gsp/fallback/first`);
expect(res.status).toBe(200);
@@ -188,17 +220,21 @@ module.exports = function (ctx) {
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/nl-NL/gsp/fallback/first`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('nl-NL');
expect(props2.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
await checkForChange(
`${ctx.deploymentUrl}/nl-NL/gsp/fallback/first`,
initialRandom
);
});
//
@@ -212,7 +248,7 @@ module.exports = function (ctx) {
const initRes = await fetch(`${ctx.deploymentUrl}/gsp/fallback/new-page`);
expect(initRes.status).toBe(200);
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res = await fetch(`${ctx.deploymentUrl}/gsp/fallback/new-page`);
expect(res.status).toBe(200);
@@ -226,17 +262,21 @@ module.exports = function (ctx) {
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'new-page' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/gsp/fallback/new-page`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('en-US');
expect(props2.params).toEqual({ slug: 'new-page' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'new-page' });
await checkForChange(
`${ctx.deploymentUrl}/gsp/fallback/new-page`,
initialRandom
);
});
it('should revalidate content properly from /fr/gsp/fallback/new-page', async () => {
@@ -246,7 +286,7 @@ module.exports = function (ctx) {
);
expect(dataRes.status).toBe(200);
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res = await fetch(`${ctx.deploymentUrl}/fr/gsp/fallback/new-page`);
expect(res.status).toBe(200);
@@ -260,15 +300,18 @@ module.exports = function (ctx) {
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'new-page' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/fr/gsp/fallback/new-page`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('fr');
await checkForChange(
`${ctx.deploymentUrl}/fr/gsp/fallback/new-page`,
initialRandom
);
});
it('should revalidate content properly from /nl-NL/gsp/fallback/new-page', async () => {
@@ -278,7 +321,7 @@ module.exports = function (ctx) {
);
expect(dataRes.status).toBe(200);
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res = await fetch(`${ctx.deploymentUrl}/nl-NL/gsp/fallback/new-page`);
expect(res.status).toBe(200);
@@ -292,7 +335,7 @@ module.exports = function (ctx) {
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'new-page' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(
`${ctx.deploymentUrl}/nl-NL/gsp/fallback/new-page`
@@ -301,10 +344,14 @@ module.exports = function (ctx) {
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('nl-NL');
expect(props2.params).toEqual({ slug: 'new-page' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'new-page' });
await checkForChange(
`${ctx.deploymentUrl}/nl-NL/gsp/fallback/new-page`,
initialRandom
);
});
it('should revalidate content properly from /gsp/no-fallback/first', async () => {
@@ -314,7 +361,7 @@ module.exports = function (ctx) {
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res = await fetch(`${ctx.deploymentUrl}/gsp/no-fallback/first`);
expect(res.status).toBe(200);
@@ -327,17 +374,21 @@ module.exports = function (ctx) {
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/gsp/no-fallback/first`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('en-US');
expect(props2.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
await checkForChange(
`${ctx.deploymentUrl}/gsp/no-fallback/first`,
initialRandom
);
});
it('should revalidate content properly from /fr/gsp/no-fallback/first', async () => {
@@ -347,7 +398,7 @@ module.exports = function (ctx) {
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res = await fetch(`${ctx.deploymentUrl}/fr/gsp/no-fallback/first`);
expect(res.status).toBe(200);
@@ -360,17 +411,21 @@ module.exports = function (ctx) {
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(`${ctx.deploymentUrl}/fr/gsp/no-fallback/first`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('fr');
expect(props2.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
await checkForChange(
`${ctx.deploymentUrl}/fr/gsp/no-fallback/first`,
initialRandom
);
});
it('should revalidate content properly from /nl-NL/gsp/no-fallback/second', async () => {
@@ -380,7 +435,7 @@ module.exports = function (ctx) {
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res = await fetch(
`${ctx.deploymentUrl}/nl-NL/gsp/no-fallback/second`
@@ -395,7 +450,7 @@ module.exports = function (ctx) {
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'second' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 4000));
await waitFor(2000);
const res2 = await fetch(
`${ctx.deploymentUrl}/nl-NL/gsp/no-fallback/second`
@@ -404,9 +459,13 @@ module.exports = function (ctx) {
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('nl-NL');
expect(props2.params).toEqual({ slug: 'second' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'second' });
await checkForChange(
`${ctx.deploymentUrl}/nl-NL/gsp/no-fallback/second`,
initialRandom
);
});
};

View File

@@ -0,0 +1,39 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next",
"config": {
"outputDirectory": "web/.next"
}
}
],
"probes": [
{
"path": "/",
"status": 200,
"mustContain": "Index page"
},
{
"path": "/dynamic/first",
"status": 200,
"mustContain": "Dynamic Page"
},
{
"path": "/dynamic-ssr/second",
"status": 200,
"mustContain": "Dynamic SSR Page"
},
{
"path": "/hello.txt",
"status": 200,
"mustContain": "hello world!"
},
{
"path": "/public/data.txt",
"status": 200,
"mustContain": "data!!"
}
]
}

View File

@@ -0,0 +1,10 @@
{
"scripts": {
"build": "next build web"
},
"dependencies": {
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1,12 @@
export function getServerSideProps() {
return {
props: {
hello: 'world',
random: Math.random(),
},
};
}
export default function Dynamic() {
return <p>Dynamic SSR Page</p>;
}

View File

@@ -0,0 +1,3 @@
export default function Dynamic() {
return <p>Dynamic Page</p>;
}

View File

@@ -0,0 +1,3 @@
export default function Index() {
return <p>Index page</p>;
}

View File

@@ -0,0 +1 @@
hello world!

View File

@@ -0,0 +1 @@
data!!

View File

@@ -1,4 +1,17 @@
module.exports = {
async headers() {
return [
{
source: '/_next/static/testing-build-id/_buildManifest.js',
headers: [
{
key: 'cache-control',
value: 'no-cache',
},
],
},
];
},
generateBuildId() {
return 'testing-build-id';
},

View File

@@ -1,6 +1,6 @@
/* eslint-env jest */
const fetch = require('node-fetch');
const cheerio = require('cheerio');
const fetch = require('../../../../../test/lib/deployment/fetch-retry');
module.exports = function (ctx) {
it('should revalidate content properly from pathname', async () => {

View File

@@ -1,4 +1,6 @@
module.exports = {
distDir: 'the-output-directory',
target: 'serverless',
generateBuildId() {
return 'testing-build-id';
},
};

View File

@@ -14,6 +14,26 @@
"path": "/",
"status": 200,
"mustContain": "hello world"
},
{
"path": "/ssg/first",
"status": 200,
"mustContain": "first"
},
{
"path": "/ssg/second",
"status": 200,
"mustContain": "Loading..."
},
{
"path": "/_next/data/testing-build-id/ssg/second.json",
"status": 200,
"mustContain": "\"slug\":\"second\""
},
{
"path": "/_next/data/testing-build-id/ssg/third.json",
"status": 200,
"mustContain": "\"slug\":\"third\""
}
]
}

View File

@@ -0,0 +1,32 @@
import { useRouter } from 'next/router';
export const getStaticProps = ({ params }) => {
return {
props: {
params,
hello: 'world',
},
};
};
export const getStaticPaths = () => {
return {
paths: [{ params: { slug: 'first' } }],
fallback: true,
};
};
export default function Page(props) {
const router = useRouter();
if (router.isFallback) {
return 'Loading...';
}
return (
<>
<p>slug: {props.params?.slug}</p>
<p id="props">{JSON.stringify(props)}</p>
</>
);
}

View File

@@ -1,7 +1,7 @@
/* eslint-env jest */
const cheerio = require('cheerio');
const fetch = require('node-fetch');
const setCookieParser = require('set-cookie-parser');
const fetch = require('../../../../../test/lib/deployment/fetch-retry');
module.exports = function (ctx) {
let previewCookie;

View File

@@ -1,6 +1,6 @@
/* eslint-env jest */
const fetch = require('node-fetch');
const cheerio = require('cheerio');
const fetch = require('../../../../../test/lib/deployment/fetch-retry');
module.exports = function (ctx) {
it('should revalidate content properly from dynamic pathname', async () => {

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
yarnPath: ".yarn/releases/yarn-berry.cjs"

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,4 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@vercel/next" }]
}

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"next": "canary",
"react": "latest",
"react-dom": "latest"
}
}

View File

@@ -0,0 +1,13 @@
export default function MyApp() {
return '404 page';
}
export const getStaticProps = () => {
console.log('/404 getStaticProps');
return {
props: {
random: Math.random(),
is404: true,
},
};
};

View File

@@ -0,0 +1,15 @@
import React from 'react';
function MyApp({ Component, pageProps }) {
return React.createElement(Component, pageProps);
}
MyApp.getInitialProps = () => {
console.log('App.getInitialProps');
return {
random: Math.random(),
hello: 'world',
};
};
export default MyApp;

View File

@@ -0,0 +1,6 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default (req, res) => {
res.statusCode = 200;
res.json({ name: 'John Doe' });
};

View File

@@ -0,0 +1,3 @@
export default function Home() {
return 'index page';
}

View File

@@ -34,6 +34,42 @@ it(
FOUR_MINUTES
);
it(
'Should build the gip-gsp-404 example',
async () => {
const { buildResult } = await runBuildLambda(
path.join(__dirname, 'gip-gsp-404')
);
const { output, routes } = buildResult;
const handleErrorIdx = (routes || []).findIndex(r => r.handle === 'error')
expect(routes[handleErrorIdx + 1].dest).toBe('/404');
expect(routes[handleErrorIdx + 1].headers).toBe(undefined);
expect(output.goodbye).not.toBeDefined();
expect(output.__NEXT_PAGE_LAMBDA_0).toBeDefined();
expect(output['404']).toBeDefined();
expect(output['404'].type).toBe('FileFsRef');
expect(output['_next/data/testing-build-id/404.json']).toBeDefined();
expect(output['_next/data/testing-build-id/404.json'].type).toBe(
'FileFsRef'
);
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath =>
filePath.match(/_error/)
);
const hasUnderScoreAppStaticFile = filePaths.some(filePath =>
filePath.match(/static.*\/pages\/_app-.*\.js$/)
);
const hasUnderScoreErrorStaticFile = filePaths.some(filePath =>
filePath.match(/static.*\/pages\/_error-.*\.js$/)
);
expect(hasUnderScoreAppStaticFile).toBeTruthy();
expect(hasUnderScoreErrorStaticFile).toBeTruthy();
expect(serverlessError).toBeTruthy();
},
FOUR_MINUTES
);
it(
'Should not deploy preview lambdas for static site',
async () => {

39
packages/now-next/test/utils.js vendored Normal file
View File

@@ -0,0 +1,39 @@
async function waitFor(milliseconds) {
return new Promise(resolve => {
setTimeout(resolve, milliseconds);
});
}
async function check(contentFn, regex, hardError = true) {
let content;
let lastErr;
for (let tries = 0; tries < 30; tries++) {
try {
content = await contentFn();
if (typeof regex === 'string') {
if (regex === content) {
return true;
}
} else if (regex.test(content)) {
// found the content
return true;
}
await waitFor(1000);
} catch (err) {
await waitFor(1000);
lastErr = err;
}
}
console.error('TIMED OUT CHECK: ', { regex, content, lastErr });
if (hardError) {
throw new Error('TIMED OUT: ' + regex + '\n\n' + content);
}
return false;
}
module.exports = {
check,
waitFor,
};

View File

@@ -1 +1,2 @@
/bridge.*
/launcher.*

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node-bridge",
"version": "1.3.1",
"version": "1.3.2-canary.0",
"license": "MIT",
"main": "./index.js",
"repository": {

View File

@@ -41,7 +41,7 @@ export function getNowLauncher({
helpersPath,
shouldAddHelpers = false,
}: LauncherConfiguration) {
return function(): Bridge {
return function (): Bridge {
let bridge = new Bridge();
let isServerListening = false;
@@ -133,7 +133,7 @@ export function getAwsLauncher({
}
// @ts-ignore
return function(e, context, callback) {
return function (e, context, callback) {
const { path, method: httpMethod, body, headers } = JSON.parse(e.body);
const { query } = parse(path, true);
const queryStringParameters: { [i: string]: string } = {};

View File

@@ -1,4 +1,5 @@
/dist
/src/bridge.ts
/src/launcher.ts
/test/fixtures/**/types.d.ts
/test/fixtures/11-symlinks/symlink

View File

@@ -1,31 +1,34 @@
#!/bin/bash
set -euo pipefail
bridge_defs="$(dirname $(pwd))/now-node-bridge/src/bridge.ts"
# Copy shared dependencies
bridge_dir="$(dirname $(pwd))/now-node-bridge"
cp -v "$bridge_dir/src/bridge.ts" "$bridge_dir/src/launcher.ts" src
cp -v "$bridge_defs" src
# Start fresh
rm -rf dist
# build ts files
# Build TypeScript files
tsc
# todo: improve
# copy type file for ts test
# TODO: improve
# Copy type file for ts test
cp dist/types.d.ts test/fixtures/15-helpers/ts/types.d.ts
# setup symlink for symlink test
ln -sf symlinked-asset test/fixtures/11-symlinks/symlink
# use types.d.ts as the main types export
# Use types.d.ts as the main types export
mv dist/types.d.ts dist/types
rm dist/*.d.ts
mv dist/types dist/index.d.ts
# bundle helpers.ts with ncc
# Bundle helpers.ts with ncc
rm dist/helpers.js
ncc build src/helpers.ts -e @vercel/build-utils -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
# Build source-map-support/register for source maps
ncc build ../../node_modules/source-map-support/register -e @vercel/build-utils -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

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "1.8.5",
"version": "1.8.6-canary.3",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -33,7 +33,7 @@
"@types/etag": "1.8.0",
"@types/test-listen": "1.1.0",
"@vercel/ncc": "0.24.0",
"@vercel/nft": "0.9.2",
"@vercel/nft": "0.9.5",
"content-type": "1.0.4",
"cookie": "0.4.0",
"etag": "1.8.1",

View File

@@ -1,20 +1,31 @@
import { ServerResponse, IncomingMessage } from 'http';
export type NowRequestCookies = { [key: string]: string };
export type NowRequestQuery = { [key: string]: string | string[] };
export type VercelRequestCookies = { [key: string]: string };
export type VercelRequestQuery = { [key: string]: string | string[] };
export type VercelRequestBody = any;
export type VercelRequest = IncomingMessage & {
query: VercelRequestQuery;
cookies: VercelRequestCookies;
body: VercelRequestBody;
};
export type VercelResponse = ServerResponse & {
send: (body: any) => VercelResponse;
json: (jsonBody: any) => VercelResponse;
status: (statusCode: number) => VercelResponse;
redirect: (statusOrUrl: string | number, url?: string) => VercelResponse;
};
export type VercelApiHandler = (
req: VercelRequest,
res: VercelResponse
) => void;
// Backwards-compat
export type NowRequestCookies = VercelRequestCookies;
export type NowRequestQuery = VercelRequestQuery;
export type NowRequestBody = any;
export type NowRequest = IncomingMessage & {
query: NowRequestQuery;
cookies: NowRequestCookies;
body: NowRequestBody;
};
export type NowResponse = ServerResponse & {
send: (body: any) => NowResponse;
json: (jsonBody: any) => NowResponse;
status: (statusCode: number) => NowResponse;
redirect: (statusOrUrl: string | number, url?: string) => NowResponse;
};
export type NowApiHandler = (req: NowRequest, res: NowResponse) => void;
export type NowRequest = VercelRequest;
export type NowResponse = VercelResponse;
export type NowApiHandler = VercelApiHandler;

View File

@@ -3,6 +3,7 @@
"builds": [
{ "src": "index.js", "use": "@vercel/node" },
{ "src": "ts/index.ts", "use": "@vercel/node" },
{ "src": "ts-legacy/index.ts", "use": "@vercel/node" },
{ "src": "micro-compat/index.js", "use": "@vercel/node" },
{
"src": "no-helpers/index.js",
@@ -40,6 +41,10 @@
"path": "/ts",
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/ts-legacy",
"mustContain": "hello legacy:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/micro-compat",
"method": "POST",

View File

@@ -0,0 +1,8 @@
import { NowApiHandler } from './types';
const listener: NowApiHandler = (req, res) => {
res.status(200);
res.send('hello legacy:RANDOMNESS_PLACEHOLDER');
};
export default listener;

View File

@@ -0,0 +1,6 @@
import { NowRequest, NowResponse } from './types';
export default function listener(req: NowRequest, res: NowResponse) {
res.status(200);
res.send('hello legacy:RANDOMNESS_PLACEHOLDER');
}

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"sourceMap": true,
"lib": ["esnext"],
"target": "esnext",
"module": "commonjs"
},
"include": ["index.ts", "handler.ts"]
}

View File

@@ -1,6 +1,6 @@
import { NowApiHandler } from './types';
import { VercelApiHandler } from './types';
const listener: NowApiHandler = (req, res) => {
const listener: VercelApiHandler = (req, res) => {
res.status(200);
res.send('hello:RANDOMNESS_PLACEHOLDER');
};

View File

@@ -1,6 +1,6 @@
import { NowRequest, NowResponse } from './types';
import { VercelRequest, VercelResponse } from './types';
export default function listener(req: NowRequest, res: NowResponse) {
export default function listener(req: VercelRequest, res: VercelResponse) {
res.status(200);
res.send('hello:RANDOMNESS_PLACEHOLDER');
}

View File

@@ -7,5 +7,5 @@
"target": "esnext",
"module": "commonjs"
},
"include": ["index.ts"]
"include": ["index.ts", "handler.ts"]
}

View File

@@ -1,7 +1,7 @@
import { NowRequest, NowResponse } from '@now/node';
import { hello } from './dep';
export default function(req: NowRequest, res: NowResponse) {
export default function (req: NowRequest, res: NowResponse) {
if (req) {
res.end(hello.toString());
} else {

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/routing-utils",
"version": "1.9.2-canary.1",
"version": "1.9.2-canary.4",
"description": "Vercel routing utilities",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",

View File

@@ -89,6 +89,7 @@ export function mergeRoutes({ userRoutes, builds }: MergeRoutesProps): Route[] {
const outputRoutes: Route[] = [];
const uniqueHandleValues = new Set([
null,
...userHandleMap.keys(),
...builderHandleMap.keys(),
]);

View File

@@ -5,42 +5,21 @@ export const routesSchema = {
type: 'array',
maxItems: 1024,
items: {
type: 'object',
additionalProperties: false,
properties: {
src: {
type: 'string',
maxLength: 4096,
},
dest: {
type: 'string',
maxLength: 4096,
},
methods: {
type: 'array',
maxItems: 10,
items: {
type: 'string',
maxLength: 32,
},
},
headers: {
anyOf: [
{
type: 'object',
required: ['src'],
additionalProperties: false,
minProperties: 1,
maxProperties: 100,
patternProperties: {
'^.{1,256}$': {
properties: {
src: {
type: 'string',
maxLength: 4096,
},
},
},
locale: {
type: 'object',
additionalProperties: false,
properties: {
redirect: {
dest: {
type: 'string',
maxLength: 4096,
},
headers: {
type: 'object',
additionalProperties: false,
minProperties: 1,
@@ -52,43 +31,78 @@ export const routesSchema = {
},
},
},
value: {
type: 'string',
maxLength: 4096,
methods: {
type: 'array',
maxItems: 10,
items: {
type: 'string',
maxLength: 32,
},
},
path: {
type: 'string',
maxLength: 4096,
important: {
type: 'boolean',
},
cookie: {
type: 'string',
maxLength: 4096,
continue: {
type: 'boolean',
},
default: {
type: 'string',
maxLength: 4096,
check: {
type: 'boolean',
},
status: {
type: 'integer',
minimum: 100,
maximum: 999,
},
locale: {
type: 'object',
additionalProperties: false,
minProperties: 1,
properties: {
redirect: {
type: 'object',
additionalProperties: false,
minProperties: 1,
maxProperties: 100,
patternProperties: {
'^.{1,256}$': {
type: 'string',
maxLength: 4096,
},
},
},
value: {
type: 'string',
maxLength: 4096,
},
path: {
type: 'string',
maxLength: 4096,
},
cookie: {
type: 'string',
maxLength: 4096,
},
default: {
type: 'string',
maxLength: 4096,
},
},
},
},
},
handle: {
type: 'string',
maxLength: 32,
{
type: 'object',
required: ['handle'],
additionalProperties: false,
properties: {
handle: {
type: 'string',
maxLength: 32,
enum: ['error', 'filesystem', 'hit', 'miss', 'resource', 'rewrite'],
},
},
},
continue: {
type: 'boolean',
},
check: {
type: 'boolean',
},
important: {
type: 'boolean',
},
status: {
type: 'integer',
minimum: 100,
maximum: 999,
},
},
],
},
};

View File

@@ -16,10 +16,11 @@ export type Source = {
methods?: string[];
continue?: boolean;
check?: boolean;
important?: boolean;
status?: number;
locale?: {
redirect: Record<string, string>;
cookie: string;
redirect?: Record<string, string>;
cookie?: string;
};
};

View File

@@ -236,13 +236,25 @@ describe('normalizeRoutes', () => {
],
[
{
dataPath: '[0].src',
keyword: 'type',
dataPath: '[0].src',
schemaPath: '#/items/anyOf/0/properties/src/type',
params: { type: 'string' },
message: 'should be string',
params: {
type: 'string',
},
schemaPath: '#/items/properties/src/type',
},
{
keyword: 'additionalProperties',
dataPath: '[0]',
schemaPath: '#/items/anyOf/1/additionalProperties',
params: { additionalProperty: 'src' },
message: 'should NOT have additional properties',
},
{
keyword: 'anyOf',
dataPath: '[0]',
schemaPath: '#/items/anyOf',
params: {},
message: 'should match some schema in anyOf',
},
]
);
@@ -258,13 +270,25 @@ describe('normalizeRoutes', () => {
],
[
{
dataPath: '[0].dest',
keyword: 'type',
message: 'should be string',
params: {
type: 'string',
},
schemaPath: '#/items/properties/dest/type',
keyword: 'required',
dataPath: '[0]',
schemaPath: '#/items/anyOf/0/required',
params: { missingProperty: 'src' },
message: "should have required property 'src'",
},
{
keyword: 'additionalProperties',
dataPath: '[0]',
schemaPath: '#/items/anyOf/1/additionalProperties',
params: { additionalProperty: 'dest' },
message: 'should NOT have additional properties',
},
{
keyword: 'anyOf',
dataPath: '[0]',
schemaPath: '#/items/anyOf',
params: {},
message: 'should match some schema in anyOf',
},
]
);
@@ -280,13 +304,25 @@ describe('normalizeRoutes', () => {
],
[
{
dataPath: '[0].methods',
keyword: 'type',
message: 'should be array',
params: {
type: 'array',
},
schemaPath: '#/items/properties/methods/type',
keyword: 'required',
dataPath: '[0]',
schemaPath: '#/items/anyOf/0/required',
params: { missingProperty: 'src' },
message: "should have required property 'src'",
},
{
keyword: 'additionalProperties',
dataPath: '[0]',
schemaPath: '#/items/anyOf/1/additionalProperties',
params: { additionalProperty: 'methods' },
message: 'should NOT have additional properties',
},
{
keyword: 'anyOf',
dataPath: '[0]',
schemaPath: '#/items/anyOf',
params: {},
message: 'should match some schema in anyOf',
},
]
);
@@ -302,13 +338,25 @@ describe('normalizeRoutes', () => {
],
[
{
dataPath: '[0].methods[0]',
keyword: 'type',
message: 'should be string',
params: {
type: 'string',
},
schemaPath: '#/items/properties/methods/items/type',
keyword: 'required',
dataPath: '[0]',
schemaPath: '#/items/anyOf/0/required',
params: { missingProperty: 'src' },
message: "should have required property 'src'",
},
{
keyword: 'additionalProperties',
dataPath: '[0]',
schemaPath: '#/items/anyOf/1/additionalProperties',
params: { additionalProperty: 'methods' },
message: 'should NOT have additional properties',
},
{
keyword: 'anyOf',
dataPath: '[0]',
schemaPath: '#/items/anyOf',
params: {},
message: 'should match some schema in anyOf',
},
]
);
@@ -324,13 +372,25 @@ describe('normalizeRoutes', () => {
],
[
{
dataPath: '[0].headers',
keyword: 'type',
message: 'should be object',
params: {
type: 'object',
},
schemaPath: '#/items/properties/headers/type',
keyword: 'required',
dataPath: '[0]',
schemaPath: '#/items/anyOf/0/required',
params: { missingProperty: 'src' },
message: "should have required property 'src'",
},
{
keyword: 'additionalProperties',
dataPath: '[0]',
schemaPath: '#/items/anyOf/1/additionalProperties',
params: { additionalProperty: 'headers' },
message: 'should NOT have additional properties',
},
{
keyword: 'anyOf',
dataPath: '[0]',
schemaPath: '#/items/anyOf',
params: {},
message: 'should match some schema in anyOf',
},
]
);
@@ -348,14 +408,25 @@ describe('normalizeRoutes', () => {
],
[
{
dataPath: "[0].headers['test']",
keyword: 'type',
message: 'should be string',
params: {
type: 'string',
},
schemaPath:
'#/items/properties/headers/patternProperties/%5E.%7B1%2C256%7D%24/type',
keyword: 'required',
dataPath: '[0]',
schemaPath: '#/items/anyOf/0/required',
params: { missingProperty: 'src' },
message: "should have required property 'src'",
},
{
keyword: 'additionalProperties',
dataPath: '[0]',
schemaPath: '#/items/anyOf/1/additionalProperties',
params: { additionalProperty: 'headers' },
message: 'should NOT have additional properties',
},
{
keyword: 'anyOf',
dataPath: '[0]',
schemaPath: '#/items/anyOf',
params: {},
message: 'should match some schema in anyOf',
},
]
);
@@ -371,13 +442,25 @@ describe('normalizeRoutes', () => {
],
[
{
dataPath: '[0].handle',
keyword: 'additionalProperties',
dataPath: '[0]',
schemaPath: '#/items/anyOf/0/additionalProperties',
params: { additionalProperty: 'handle' },
message: 'should NOT have additional properties',
},
{
keyword: 'type',
dataPath: '[0].handle',
schemaPath: '#/items/anyOf/1/properties/handle/type',
params: { type: 'string' },
message: 'should be string',
params: {
type: 'string',
},
schemaPath: '#/items/properties/handle/type',
},
{
keyword: 'anyOf',
dataPath: '[0]',
schemaPath: '#/items/anyOf',
params: {},
message: 'should match some schema in anyOf',
},
]
);
@@ -393,13 +476,25 @@ describe('normalizeRoutes', () => {
],
[
{
dataPath: '[0].continue',
keyword: 'type',
message: 'should be boolean',
params: {
type: 'boolean',
},
schemaPath: '#/items/properties/continue/type',
keyword: 'required',
dataPath: '[0]',
schemaPath: '#/items/anyOf/0/required',
params: { missingProperty: 'src' },
message: "should have required property 'src'",
},
{
keyword: 'additionalProperties',
dataPath: '[0]',
schemaPath: '#/items/anyOf/1/additionalProperties',
params: { additionalProperty: 'continue' },
message: 'should NOT have additional properties',
},
{
keyword: 'anyOf',
dataPath: '[0]',
schemaPath: '#/items/anyOf',
params: {},
message: 'should match some schema in anyOf',
},
]
);
@@ -415,13 +510,25 @@ describe('normalizeRoutes', () => {
],
[
{
dataPath: '[0].check',
keyword: 'type',
message: 'should be boolean',
params: {
type: 'boolean',
},
schemaPath: '#/items/properties/check/type',
keyword: 'required',
dataPath: '[0]',
schemaPath: '#/items/anyOf/0/required',
params: { missingProperty: 'src' },
message: "should have required property 'src'",
},
{
keyword: 'additionalProperties',
dataPath: '[0]',
schemaPath: '#/items/anyOf/1/additionalProperties',
params: { additionalProperty: 'check' },
message: 'should NOT have additional properties',
},
{
keyword: 'anyOf',
dataPath: '[0]',
schemaPath: '#/items/anyOf',
params: {},
message: 'should match some schema in anyOf',
},
]
);
@@ -437,13 +544,25 @@ describe('normalizeRoutes', () => {
],
[
{
dataPath: '[0].status',
keyword: 'type',
message: 'should be integer',
params: {
type: 'integer',
},
schemaPath: '#/items/properties/status/type',
keyword: 'required',
dataPath: '[0]',
schemaPath: '#/items/anyOf/0/required',
params: { missingProperty: 'src' },
message: "should have required property 'src'",
},
{
keyword: 'additionalProperties',
dataPath: '[0]',
schemaPath: '#/items/anyOf/1/additionalProperties',
params: { additionalProperty: 'status' },
message: 'should NOT have additional properties',
},
{
keyword: 'anyOf',
dataPath: '[0]',
schemaPath: '#/items/anyOf',
params: {},
message: 'should match some schema in anyOf',
},
]
);
@@ -459,13 +578,25 @@ describe('normalizeRoutes', () => {
],
[
{
dataPath: '[0]',
keyword: 'additionalProperties',
dataPath: '[0]',
schemaPath: '#/items/anyOf/0/additionalProperties',
params: { additionalProperty: 'doesNotExist' },
message: 'should NOT have additional properties',
params: {
additionalProperty: 'doesNotExist',
},
schemaPath: '#/items/additionalProperties',
},
{
keyword: 'additionalProperties',
dataPath: '[0]',
schemaPath: '#/items/anyOf/1/additionalProperties',
params: { additionalProperty: 'doesNotExist' },
message: 'should NOT have additional properties',
},
{
keyword: 'anyOf',
dataPath: '[0]',
schemaPath: '#/items/anyOf',
params: {},
message: 'should match some schema in anyOf',
},
]
);

View File

@@ -1,4 +1,4 @@
const { deepEqual } = require('assert');
const { deepStrictEqual } = require('assert');
const { mergeRoutes } = require('../dist/merge');
test('mergeRoutes simple', () => {
@@ -10,7 +10,10 @@ test('mergeRoutes simple', () => {
{
use: '@now/node',
entrypoint: 'api/home.js',
routes: [{ src: '/node1', dest: '/n1' }, { src: '/node2', dest: '/n2' }],
routes: [
{ src: '/node1', dest: '/n1' },
{ src: '/node2', dest: '/n2' },
],
},
{
use: '@now/python',
@@ -30,7 +33,7 @@ test('mergeRoutes simple', () => {
{ dest: '/py1', src: '/python1' },
{ dest: '/py2', src: '/python2' },
];
deepEqual(actual, expected);
deepStrictEqual(actual, expected);
});
test('mergeRoutes handle filesystem user routes', () => {
@@ -43,7 +46,10 @@ test('mergeRoutes handle filesystem user routes', () => {
{
use: '@now/node',
entrypoint: 'api/home.js',
routes: [{ src: '/node1', dest: '/n1' }, { src: '/node2', dest: '/n2' }],
routes: [
{ src: '/node1', dest: '/n1' },
{ src: '/node2', dest: '/n2' },
],
},
{
use: '@now/python',
@@ -64,7 +70,7 @@ test('mergeRoutes handle filesystem user routes', () => {
{ handle: 'filesystem' },
{ dest: '/u2', src: '/user2' },
];
deepEqual(actual, expected);
deepStrictEqual(actual, expected);
});
test('mergeRoutes handle filesystem build routes', () => {
@@ -102,7 +108,7 @@ test('mergeRoutes handle filesystem build routes', () => {
{ dest: '/n2', src: '/node2' },
{ dest: '/py2', src: '/python2' },
];
deepEqual(actual, expected);
deepStrictEqual(actual, expected);
});
test('mergeRoutes handle filesystem both user and builds', () => {
@@ -141,7 +147,7 @@ test('mergeRoutes handle filesystem both user and builds', () => {
{ dest: '/n2', src: '/node2' },
{ dest: '/py2', src: '/python2' },
];
deepEqual(actual, expected);
deepStrictEqual(actual, expected);
});
test('mergeRoutes continue true', () => {
@@ -182,7 +188,7 @@ test('mergeRoutes continue true', () => {
{ dest: '/py1', src: '/python1' },
{ dest: '/py3', src: '/python3' },
];
deepEqual(actual, expected);
deepStrictEqual(actual, expected);
});
test('mergeRoutes check true', () => {
@@ -223,7 +229,7 @@ test('mergeRoutes check true', () => {
{ dest: '/py1', src: '/python1' },
{ dest: '/py3', src: '/python3' },
];
deepEqual(actual, expected);
deepStrictEqual(actual, expected);
});
test('mergeRoutes check true, continue true, handle filesystem middle', () => {
@@ -268,7 +274,7 @@ test('mergeRoutes check true, continue true, handle filesystem middle', () => {
{ dest: '/n3', src: '/node3' },
{ dest: '/py3', src: '/python3' },
];
deepEqual(actual, expected);
deepStrictEqual(actual, expected);
});
test('mergeRoutes check true, continue true, handle filesystem top', () => {
@@ -306,7 +312,7 @@ test('mergeRoutes check true, continue true, handle filesystem top', () => {
{ dest: '/n1', src: '/node1' },
{ dest: '/py1', src: '/python1' },
];
deepEqual(actual, expected);
deepStrictEqual(actual, expected);
});
test('mergeRoutes multiple handle values', () => {
@@ -359,5 +365,57 @@ test('mergeRoutes multiple handle values', () => {
{ dest: '/u3', src: '/user3' },
{ check: true, dest: '/py2', src: '/python2' },
];
deepEqual(actual, expected);
deepStrictEqual(actual, expected);
});
test('mergeRoutes ensure `handle: error` comes last', () => {
const userRoutes = [];
const builds = [
{
use: '@vercel/static-build',
entrypoint: 'packge.json',
routes: [
{
src: '^/home$',
status: 301,
headers: {
Location: '/',
},
},
],
},
{
use: '@vercel/zero-config-routes',
entrypoint: '/',
routes: [
{
handle: 'error',
},
{
status: 404,
src: '^/(?!.*api).*$',
dest: '404.html',
},
],
},
];
const actual = mergeRoutes({ userRoutes, builds });
const expected = [
{
status: 301,
src: '^/home$',
headers: {
Location: '/',
},
},
{
handle: 'error',
},
{
status: 404,
src: '^/(?!.*api).*$',
dest: '404.html',
},
];
deepStrictEqual(actual, expected);
});

View File

@@ -2,15 +2,13 @@
set -euo pipefail
# Copy shared dependencies
bridge_defs="$(dirname $(pwd))/now-node-bridge/src/bridge.ts"
launcher_defs="$(dirname $(pwd))/now-node/src/launcher.ts"
cp -v "$bridge_defs" src
cp -v "$launcher_defs" src
bridge_dir="$(dirname $(pwd))/now-node-bridge"
cp -v "$bridge_dir/src/bridge.ts" "$bridge_dir/src/launcher.ts" src
# Start fresh
rm -rf dist
# Build TypeScript files
tsc
# Build with `ncc`

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/static-build",
"version": "0.18.0",
"version": "0.18.1-canary.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/build-step",

View File

@@ -366,6 +366,14 @@ export async function build({
console.log(`Running "install" command: \`${installCommand}\`...`);
await execCommand(installCommand, {
...spawnOpts,
// Yarn v2 PnP mode may be activated, so force
// "node-modules" linker style
env: {
YARN_NODE_LINKER: 'node-modules',
...spawnOpts.env,
},
cwd: entrypointDir,
});
} else {
@@ -468,6 +476,14 @@ export async function build({
typeof buildCommand === 'string'
? await execCommand(buildCommand, {
...spawnOpts,
// Yarn v2 PnP mode may be activated, so force
// "node-modules" linker style
env: {
YARN_NODE_LINKER: 'node-modules',
...spawnOpts.env,
},
cwd: entrypointDir,
})
: await runPackageJsonScript(

View File

@@ -2,17 +2,11 @@
set -euo pipefail
# Copy shared dependencies
bridge_defs="$(dirname $(pwd))/now-node-bridge/src/bridge.ts"
launcher_defs="$(dirname $(pwd))/now-node/src/launcher.ts"
cp -v "$bridge_defs" src
cp -v "$launcher_defs" src
bridge_dir="$(dirname $(pwd))/now-node-bridge"
cp -v "$bridge_dir/src/bridge.ts" "$bridge_dir/src/launcher.ts" src
# Start fresh
rm -rf dist
## Build ts files
# Build TypeScript files
tsc
# Build with `ncc`
#ncc build src/index.ts -e @vercel/build-utils -e @now/build-utils -o dist

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/redwood",
"version": "0.2.0",
"version": "0.2.1-canary.0",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs",

View File

@@ -1,9 +1,9 @@
const fetch = require('node-fetch');
const retryBailByDefault = require('./retry-bail-by-default.js');
async function fetchRetry (...args) {
async function fetchRetry(...args) {
return await retryBailByDefault(
async (canRetry) => {
async canRetry => {
try {
return await fetch(...args);
} catch (error) {
@@ -14,6 +14,12 @@ async function fetchRetry (...args) {
// request to https://api-gru1.vercel.com/v3/now/deployments/dpl_FBWWhpQomjgwjJLu396snLrGZYCm failed, reason:
// connect ETIMEDOUT 18.228.143.224:443
throw canRetry(error);
} else if (error.code === 'ECONNREFUSED') {
// request to https://test2020-dhdy1xrfa.vercel.app/blog/post-3 failed, reason:
// connect ECONNREFUSED 76.76.21.21:443
throw canRetry(error);
} else if (error.code === 'ECONNRESET') {
throw canRetry(error);
}
throw error;
}

View File

@@ -1,3 +1,4 @@
const fs = require('fs-extra');
const { glob, getWriteableDirectory } = require('@vercel/build-utils');
function runAnalyze(wrapper, context) {
@@ -11,13 +12,21 @@ function runAnalyze(wrapper, context) {
async function runBuildLambda(inputPath) {
const inputFiles = await glob('**', inputPath);
const nowJsonRef = inputFiles['vercel.json'] || inputFiles['now.json'];
expect(nowJsonRef).toBeDefined();
if (typeof expect !== 'undefined') {
expect(nowJsonRef).toBeDefined();
}
const nowJson = require(nowJsonRef.fsPath);
expect(nowJson.builds.length).toBe(1);
const build = nowJson.builds[0];
expect(build.src.includes('*')).toBeFalsy();
if (typeof expect !== 'undefined') {
expect(build.src.includes('*')).toBeFalsy();
}
const entrypoint = build.src.replace(/^\//, ''); // strip leftmost slash
expect(inputFiles[entrypoint]).toBeDefined();
if (typeof expect !== 'undefined') {
expect(inputFiles[entrypoint]).toBeDefined();
}
inputFiles[entrypoint].digest =
'this-is-a-fake-digest-for-non-default-analyze';
const wrapper = require(build.use);
@@ -28,7 +37,7 @@ async function runBuildLambda(inputPath) {
config: build.config,
});
const workPath = await getWriteableDirectory();
const workPath = await fs.realpath(await getWriteableDirectory());
const buildResult = await wrapper.build({
files: inputFiles,
entrypoint,

View File

@@ -2226,10 +2226,10 @@
resolved "https://registry.yarnpkg.com/@vercel/ncc/-/ncc-0.24.0.tgz#a2e8783a185caa99b5d8961a57dfc9665de16296"
integrity sha512-crqItMcIwCkvdXY/V3/TzrHJQx6nbIaRqE1cOopJhgGX6izvNov40SmD//nS5flfEvdK54YGjwVVq+zG6crjOg==
"@vercel/nft@0.9.2":
version "0.9.2"
resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.9.2.tgz#677ecefb0bd618143281c62c719baca57a36ac4d"
integrity sha512-Dr2yJlCnfkQEt4QHKcPJKTxCyoBX0YCzHDzozd8upBFm8kKbh2yMSu5wp+1btevQXOMkOUtxntovwwPHDIU51w==
"@vercel/nft@0.9.5":
version "0.9.5"
resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.9.5.tgz#bf795944a4764ca49ca1a642f17ab32f9ac701d2"
integrity sha512-EhSFOYwqvH3KZyK1pKyFj/DRoCZ2KFu8sRaVaJ+KGlU4kroAWm8okeA2EtIY11+/fMX3YQkNno7kf5H4FZrDvg==
dependencies:
acorn "^7.1.1"
acorn-class-fields "^0.3.2"
@@ -6266,9 +6266,9 @@ inherits@2.0.3:
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
version "1.3.7"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==
init-package-json@^1.10.3:
version "1.10.3"