Compare commits

..

43 Commits

Author SHA1 Message Date
Steven
bdd25ac727 Publish Canary
- @now/build-utils@1.1.2-canary.0
 - @now/cgi@1.0.1-canary.1
 - now@16.6.4-canary.0
 - @now/go@1.0.1-canary.1
 - @now/next@2.1.2-canary.0
 - @now/node@1.2.2-canary.0
 - @now/python@1.0.1-canary.1
 - @now/ruby@1.0.1-canary.1
 - @now/static-build@0.13.2-canary.0
2019-12-11 17:30:11 -05:00
Steven
3a27328828 [now-build-utils] Discontinue Node 8 (#3406)
This PR adds a `discontinueDate` to Node 8 and prints a warning if the current deployment is using it.

```
    ┌──────────────────────────────────────────────────────────────────────────────────────────────┐
    │                                                                                              │
    │   WARNING                                                                                    │
    │                                                                                              │
    │   Node.js 8.10.x will be discontinued on 2020-01-06.                                         │
    │   Deployments created on or after 2020-01-06 will fail to build.                             │
    │   Please use one of the following supported `engines` in `package.json`: ["12.x","10.x"]     │
    │   This change is a result of a decision made by an upstream infrastructure provider (AWS).   │
    │   Read more: https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html        │
    │                                                                                              │
    └──────────────────────────────────────────────────────────────────────────────────────────────┘
```

Starting January 2020, Node 8 deployments will fail to build and print an error.

```
Found `engines` in `package.json` with an unsupported Node.js version range: 8.10.x
Please use one of the following supported ranges: ["12.x","10.x"]
```

[PRODUCT-796]

[PRODUCT-796]: https://zeit.atlassian.net/browse/PRODUCT-796
2019-12-11 22:27:16 +00:00
Nathan Rajlich
c076a5620f [all] Move "Downloading deployment source files" message to download() (#3413)
Before, the debug log message "Downloading user files..." was copy+pasted to all the builders.

This change centralizes that log message to be inside the `download()` function for consistency and DRY purposes.

Additionally, the wording has changed as per [INFRA-289], and a resulting timestamp message is also printed.

[INFRA-289]: https://zeit.atlassian.net/browse/INFRA-289
2019-12-11 20:59:14 +00:00
Nathan Rajlich
2bd8ef9eed [now-next] Do not pass runtime env vars via argv in now dev (#3340)
Before this change, in `@now/next` when running via `now dev`,
the runtime env vars get passed to the child `dev-server.js`
process via argv.

This is problematic because it causes the env vars to be visible in
the process listing, and also causes the command itself to be very
large.

In some cases, with a lot of env vars, we've seen the command be too
large and it will fail to spawn (https://serverfault.com/a/163390/294389).

This changes the start-up process such that the env vars are passed
in via Node.js `fork()` IPC, rather than via `argv`.
2019-12-11 19:18:08 +00:00
JJ Kasper
500014f2fc [now-next] Handle symlinks in createPseudoLayer (#3404)
This makes sure to handle symlinks created in mono repos while creating the zip for a lambda based page in Next.js

Fixes #3400
2019-12-11 15:43:14 +00:00
JJ Kasper
17687e9bcd Fix unused pre-commit package overriding husky (#3405)
Saw my `pre-commit` hook wasn't being triggered after running `yarn` and noticed it was due to an un-used `pre-commit` dependency in `now-cli` overriding the changes to my `.git/hooks/pre-commit` file done by `husky`

**Note**: you will probably need to run `yarn install --force` after this is applied before the correct `pre-commit` changes are applied by `husky`
2019-12-11 14:35:30 +00:00
Andy
90354e9fe7 [now-cgi][now-next][now-ruby] Unify logging about downloading user files (#3397)
* [now-cgi][now-next][now-ruby] Unify logging about downloading user files

* Update next tests

* Bump Next.js Version
2019-12-10 21:15:32 +01:00
Leo Lamprecht
6236631beb [now-dev] Improved feedback link (#3399)
We've shut down our Typeform for feedback, so we can simply the feedback link. The old one will continue working, but we should start using the new one.

Pending on https://github.com/zeit/front/pull/5874.

Fixes #3377
2019-12-10 18:18:10 +00:00
Steven
75aefdddd6 Publish Stable
- @now/build-utils@1.1.1
 - now@16.6.3
 - now-client@6.0.0
 - @now/next@2.1.1
 - @now/node@1.2.1
 - @now/routing-utils@1.4.0
 - @now/static-build@0.13.1
2019-12-09 08:03:47 -05:00
Steven
566d82e873 Publish Canary
- now@16.6.3-canary.0
 - @now/next@2.1.1-canary.1
 - @now/node@1.2.1-canary.0
 - @now/routing-utils@1.3.4-canary.6
 - @now/static-build@0.13.1-canary.1
2019-12-06 19:49:27 -05:00
Steven
44ae0b654e [now-routing-utils] Use 308 status code (#3392)
We decided that all of the new properties should default to 301 status code for any redirects.
2019-12-07 00:38:26 +00:00
Steven
d8cfaae596 [now-node][now-next][now-static-build] Remove lockfiles from cache (#3391)
The lock files should not be cached because the user may wish to make a new deployment without a `yarn.lock` or `package-lock.json`.

This recently started causing problems because of the order of downloading cache changed from before user files to after user files.

So we need to be extra careful to only cache outputs and not source files.
2019-12-06 23:35:17 +00:00
Steven
a40e0f21ee Publish Canary
- @now/build-utils@1.1.1-canary.2
2019-12-06 15:57:33 -05:00
Steven
ac1f506c98 [now-build-utils] Add --no-audit flag to npm install (#3390)
This PR will reduce deployment time when a `package-lock.json` file is found by avoiding the audit step which usually [sends audit reports](https://docs.npmjs.com/cli/audit#description) to the registry.

The [--no-audit](https://docs.npmjs.com/cli/install) flag was introduced in [npm@6](
https://medium.com/npm-inc/announcing-npm-6-5d0b1799a905) which shipped with Node 10. However, using the flag with npm@5 does not do anything which is great because npm@5 doesn't audit. So this PR is backwards compatible.

### Performance

I tried `npm install` and `npm install --no-audit` with a large project, [StateOfJS](a9fa6d47f9/homepages/stateofjs), which has 2206 packages (audited 21778 packages).

I made sure to `rm -rf node_modules` each time and ran both commands 5 times to make sure it was always faster with `--no-audit`.

- Before: 61 seconds
- After: 49 seconds
2019-12-06 20:48:30 +00:00
Steven
68d5bdcf3d [script] Fix stable publish script (#3389)
Since we switched to a single branch (instead of master/canary), lerna gets confused about which packages to publish because stable and canary releases are in the same branch.

This PR fixes the confusion by looking at the git history and using [--force-publish](https://github.com/lerna/lerna/tree/master/commands/version#--force-publish) on the changed packages.

In order to avoid confusion for the person publishing, I removed the `yarn publish-stable` script in favor of `yarn changelog` which will print the change log and emit a script that can be used to publish stable.

<details><summary>View Example Output</summary>
<p>

```
$ yarn changelog
yarn run v1.19.1
$ node changelog.js
Changes since the last Stable release (21fe0a2):

- [now-cli] Change `--debug` to avoid debugging builders (#3386) [Steven]
- [now-next] Update routes for new check: true behavior (#3383) [JJ Kasper]
- [now-build-utils] Update Detectors API (#3384) [Nathan Rajlich]
- [now-client] Bump version (#3385) [Andy]
- [now-client] (Major) Split `now-client` options (#3382) [Andy]
- [now-cli][now-client] Fix user agent (#3381) [Steven]
- [now-client] Fix `main` in package.json (#3344) [Max]
- [now-build-utils] Change `script` to `scripts` in error message (#3376) [Andy]
- [now-cli] Add support for `check: true` routes in `now dev` (#3364) [Steven]
- [now-cli] Fix preinstall script on windows when `LOCALAPPDATA` is missing (#3365) [Steven]
- [now dev] skip installing already installed versioned runtimes (#3354) [Tommaso De Rossi]
- [now-routing-utils] Update `path-to-regexp` to v6.1.0 (#3361) [Steven]
- [now-routing-utils] Add mergeRoutes function (#3358) [Steven]
- [docs] Remove deprecated LambdaRuntimes (#3346) [Steven]
- [now-routing-utils] Add support for `check: true` (#3343) [Steven]
- [now-static-build] Cache `.cache` folder for gatsby deployments (#3260) (#3342) [Luc]

To publish a stable release, execute the following:

git pull && lerna version --message 'Publish Stable' --exact --force-publish=@now/build-utils,now,now-client,@now/next,@now/routing-utils,@now/static-build
```

</p>
</details>
2019-12-06 19:31:38 +00:00
Andy Bitz
beb51f8c67 Publish Stable
- now@16.6.2
2019-12-06 13:48:44 +01:00
Andy
b881cb7111 [now-cli] Remove github property from payload before sending it (#3388)
* [now-cli] Remove `github` property from payload before sending it

* Add test and remove unused one

* Remove .only

* Remove unused fixture

* Use correct github properties
2019-12-06 13:47:16 +01:00
Andy Bitz
d83bc59257 Publish Stable
- now@16.6.1
2019-12-06 00:18:57 +01:00
Steven
5be9f297de [now-cli] Change --debug to avoid debugging builders (#3386)
* [now-cli] Change `--debug` to avoid debugging builders

* Fix tests

* Replace test with on/off
2019-12-06 00:11:22 +01:00
JJ Kasper
51d440431e Publish Canary
- @now/build-utils@1.1.1-canary.1
 - @now/next@2.1.1-canary.0
2019-12-05 15:18:35 -06:00
JJ Kasper
7cf061122c [now-next] Update routes for new check: true behavior (#3383)
As discussed this moves the `handle: filesystem` usage to the right location now that we have `check: true` for the `rewrites`
2019-12-05 20:48:17 +00:00
Nathan Rajlich
1254368505 [now-build-utils] Update Detectors API (#3384)
* Changes the `buildCommand` and `devCommand` from `string[]` to `string`
 * Renames `buildEnv` to `buildVariables` and `devEnv` to `devVariables`
2019-12-05 20:01:28 +00:00
Andy Bitz
9d4b830c5f Publish Canary
- now@16.6.1-canary.1
 - now-client@6.0.0-canary.1
2019-12-05 14:14:50 +01:00
Andy
37401b4363 [now-client] Bump version (#3385) 2019-12-05 14:14:00 +01:00
Andy
10fe08e14f [now-client] (Major) Split now-client options (#3382)
* Change types

* Split options for now-client

* Fix query and teamId

* Adjust tests

* Fix linting

* Ignore scope

* Adjust now-client tests

* Adjust more tests

* Apply prettier
2019-12-05 14:08:39 +01:00
Steven
0ecdb35d50 [now-cli][now-client] Fix user agent (#3381)
Since switching to a single branch, each package in the monorepo can be independently versioned so that some packages are using a canary version and others using a stable version.

This PR fixes an issue where a canary version of `now-cli` is bundling a stable version of `now-client` and thus does does not deploy zero config using canary builders.

The solution is to pass the User Agent from `now-cli` to `now-client` in a new option.

A nice side-effect of this PR is that we will switch the User Agent back to what it used to be pre-now-client days. It will look something like `now 16.6.1-canary.0 node-v10.17.0 darwin (x64)`.
2019-12-04 23:10:31 +00:00
Steven
caee8fe9ef Publish Canary
- now-client@5.2.5-canary.0
2019-12-04 16:46:43 -05:00
Max
7d92c27b2d [now-client] Fix main in package.json (#3344)
This sets `main` in `now-client` to a proper path.

Follow up to #3315.

Fixes #3373.
2019-12-04 13:25:27 -08:00
Steven
701eabbaba Publish Canary
- now@16.6.1-canary.0
2019-12-04 09:28:21 -05:00
Andy Bitz
e74a1b2d1a Publish Canary
- @now/build-utils@1.1.1-canary.0
2019-12-02 23:41:28 +01:00
Andy
e087b02333 [now-build-utils] Change script to scripts in error message (#3376)
Change `script` to `scripts` in error message.

[PRODUCT-740]

[PRODUCT-740]: https://zeit.atlassian.net/browse/PRODUCT-740
2019-12-02 22:33:27 +00:00
Steven
eea7f902b5 [now-cli] Add support for check: true routes in now dev (#3364)
This PR adds `now dev` support for routes that define `check: true`.

The algorithm is as follows:

- If a matching `dest` file is found, then serve it
- If a matching `src` file is found, then serve it
- Otherwise, behave the same as `continue: true` and continue processing routes
2019-11-28 10:58:13 +00:00
Steven
db7583201b [now-cli] Fix preinstall script on windows when LOCALAPPDATA is missing (#3365)
Usually `LOCALAPPDATA` is set to `C:\Users\{username}\AppData\Local` but occasionally, it is unassigned and causes installation failures. Looks like this could be due to the [registry](https://liquidwarelabs.zendesk.com/hc/en-us/articles/210634163-How-To-Make-APPDATA-and-LOCALAPPDATA-Environment-Variables-Follow-The-Registry-Keys).

If `LOCALAPPDATA` is missing, we can assume that now.exe was not installed before and can skip the deletion step that happens in the preinstall script.
2019-11-28 00:35:53 +00:00
Tommaso De Rossi
023001a8b1 [now dev] skip installing already installed versioned runtimes (#3354)
Fixes #3353
The current solution might break if a user interrupts `now dev` while yarn wrote the package in the cache package.json but has not yet added to node_modules.
This happens in like 20 ms but is possible, so we could execute `yarn` every time to be sure.
Tell me if the above is a problem or not
2019-11-27 11:33:18 +00:00
Steven
4ff8ab2435 Publish Canary
- @now/routing-utils@1.3.4-canary.5
2019-11-26 19:00:34 -05:00
Steven
d2cccbfce6 [now-routing-utils] Update path-to-regexp to v6.1.0 (#3361)
This bumps `path-to-regexp` to the latest version 6.1.0 which fixes optional capture groups like the test I added for `/next.js`.
2019-11-26 23:41:35 +00:00
Steven
970e6c400c Publish Canary
- @now/routing-utils@1.3.4-canary.4
2019-11-26 13:10:09 -05:00
Steven
b4cb7345a1 [now-routing-utils] Add mergeRoutes function (#3358)
This moves the merging logic to `@now/routing-utils` and adds support for `check: true`.

- Builder before filesystem, continue: true
- User before filesystem
- Builder before filesystem, check: true
- Builder before filesystem, continue: false
- Handle filesystem
- Builder after filesystem, continue: true
- User after filesystem
- Builder after filesystem, check: true
- Builder after filesystem, continue: false
2019-11-26 17:51:19 +00:00
Steven
7e75d8c1a3 [docs] Remove deprecated LambdaRuntimes (#3346)
- Removes Node 8.10 and old .NET which are EOL
- Adds a couple missing such as Ruby
2019-11-22 21:12:23 +00:00
Steven
a4ea551160 Publish Canary
- @now/routing-utils@1.3.4-canary.3
2019-11-22 14:41:33 -05:00
Steven
f56ad447a0 [now-routing-utils] Add support for check: true (#3343)
This PR adds support for `check: true` for a route object. It is basically a way to add a rewrite and still check the filesystem.
2019-11-22 19:03:45 +00:00
luc
7656422057 Publish Canary
- @now/static-build@0.13.1-canary.0
2019-11-22 17:39:35 +08:00
Luc
afa2231add [now-static-build] Cache .cache folder for gatsby deployments (#3260) (#3342)
Apply 77348ea71e again.

> Adds `.cache` folder to the Now cache for Gatsby deployments.

> Also adds a generic optional `cachePattern` property to the frameworks array so we can optimize cache paths for other frameworks in the future.
2019-11-22 09:16:51 +00:00
124 changed files with 12248 additions and 856 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
node_modules
package-lock.json
dist
.vscode
npm-debug.log
yarn-error.log
.nyc_output

View File

@@ -285,14 +285,13 @@ This is an abstract enumeration type that is implemented by one of the following
- `nodejs12.x`
- `nodejs10.x`
- `nodejs8.10`
- `go1.x`
- `java-1.8.0-openjdk`
- `java11`
- `python3.8`
- `python3.6`
- `python2.7`
- `dotnetcore2.1`
- `dotnetcore2.0`
- `dotnetcore1.0`
- `ruby2.5`
- `provided`
## JavaScript API

View File

@@ -12,12 +12,31 @@ if (!commit) {
throw new Error('Unable to find last publish commit');
}
const log = execSync(`git log --pretty=format:"- %s [%an]" ${commit}...HEAD`)
.toString()
.trim()
.split('\n')
.filter(line => !line.startsWith('- Publish Canary '))
.join('\n');
const log =
execSync(`git log --pretty=format:"- %s [%an]" ${commit}...HEAD`)
.toString()
.trim()
.split('\n')
.filter(line => !line.startsWith('- Publish Canary '))
.join('\n') || 'NO CHANGES DETECTED';
console.log(`Changes since the last Stable release (${commit.slice(0, 7)}):`);
console.log(`\n${log}\n`);
const pkgs =
Array.from(
new Set(
execSync(`git diff --name-only ${commit}...HEAD`)
.toString()
.trim()
.split('\n')
.filter(line => line.startsWith('packages/'))
.map(line => line.split('/')[1])
.map(pkgName => require(`./packages/${pkgName}/package.json`).name)
)
).join(',') || 'now';
console.log('To publish a stable release, execute the following:');
console.log(
`\ngit pull && lerna version --message 'Publish Stable' --exact --force-publish=${pkgs}\n`
);

View File

@@ -30,7 +30,7 @@
"scripts": {
"lerna": "lerna",
"bootstrap": "lerna bootstrap",
"publish-stable": "git pull && lerna version --message 'Publish Stable' --exact",
"publish-stable": "echo 'Run `yarn changelog` for instructions'",
"publish-canary": "git pull && lerna version prerelease --preid canary --message 'Publish Canary' --exact",
"publish-from-github": "./.circleci/publish.sh",
"changelog": "node changelog.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@now/build-utils",
"version": "1.1.0",
"version": "1.1.2-canary.0",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -32,6 +32,7 @@
"aggregate-error": "3.0.1",
"async-retry": "1.2.3",
"async-sema": "2.1.4",
"boxen": "4.2.0",
"cross-spawn": "6.0.5",
"end-of-stream": "1.4.1",
"execa": "^1.0.0",

View File

@@ -18,7 +18,7 @@ const config: Config = { zeroConfig: true };
const MISSING_BUILD_SCRIPT_ERROR: ErrorResponse = {
code: 'missing_build_script',
message:
'Your `package.json` file is missing a `build` property inside the `script` property.' +
'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
'\nMore details: https://zeit.co/docs/v2/platform/frequently-asked-questions#missing-build-script',
};

View File

@@ -6,9 +6,9 @@ export default async function detectAngular({
const hasAngular = await hasDependency('@angular/cli');
if (!hasAngular) return false;
return {
buildCommand: ['ng', 'build'],
buildCommand: 'ng build',
buildDirectory: 'dist',
devCommand: ['ng', 'serve', '--port', '$PORT'],
devCommand: 'ng serve --port $PORT',
minNodeRange: '10.x',
routes: [
{

View File

@@ -10,8 +10,8 @@ export default async function detectBrunch({
if (!hasConfig) return false;
return {
buildCommand: ['brunch', 'build', '--production'],
buildCommand: 'brunch build --production',
buildDirectory: 'public',
devCommand: ['brunch', 'watch', '--server', '--port', '$PORT'],
devCommand: 'brunch watch --server --port $PORT',
};
}

View File

@@ -8,10 +8,10 @@ export default async function detectCreateReactAppEjected({
return false;
}
return {
buildCommand: ['node', 'scripts/build.js'],
buildCommand: 'node scripts/build.js',
buildDirectory: 'build',
devCommand: ['node', 'scripts/start.js'],
devEnv: { BROWSER: 'none' },
devCommand: 'node scripts/start.js',
devVariables: { BROWSER: 'none' },
routes: [
{
src: '/static/(.*)',

View File

@@ -8,10 +8,10 @@ export default async function detectCreateReactApp({
return false;
}
return {
buildCommand: ['react-scripts', 'build'],
buildCommand: 'react-scripts build',
buildDirectory: 'build',
devCommand: ['react-scripts', 'start'],
devEnv: { BROWSER: 'none' },
devCommand: 'react-scripts start',
devVariables: { BROWSER: 'none' },
routes: [
{
src: '/static/(.*)',

View File

@@ -6,8 +6,8 @@ export default async function detectDocusaurus({
const hasDocusaurus = await hasDependency('docusaurus');
if (!hasDocusaurus) return false;
return {
buildCommand: ['docusaurus-build'],
buildCommand: 'docusaurus-build',
buildDirectory: 'build',
devCommand: ['docusaurus-start', '--port', '$PORT'],
devCommand: 'docusaurus-start --port $PORT',
};
}

View File

@@ -6,15 +6,8 @@ export default async function detectEleventy({
const hasEleventy = await hasDependency('@11ty/eleventy');
if (!hasEleventy) return false;
return {
buildCommand: ['npx', '@11ty/eleventy'],
buildCommand: 'npx @11ty/eleventy',
buildDirectory: '_site',
devCommand: [
'npx',
'@11ty/eleventy',
'--serve',
'--watch',
'--port',
'$PORT',
],
devCommand: 'npx @11ty/eleventy --serve --watch --port $PORT',
};
}

View File

@@ -6,9 +6,9 @@ export default async function detectEmber({
const hasEmber = await hasDependency('ember-cli');
if (!hasEmber) return false;
return {
buildCommand: ['ember', 'build'],
buildCommand: 'ember build',
buildDirectory: 'dist',
devCommand: ['ember', 'serve', '--port', '$PORT'],
devCommand: 'ember serve --port $PORT',
routes: [
{
handle: 'filesystem',

View File

@@ -8,9 +8,9 @@ export default async function detectGatsby({
return false;
}
return {
buildCommand: ['gatsby', 'build'],
buildCommand: 'gatsby build',
buildDirectory: 'public',
devCommand: ['gatsby', 'develop', '-p', '$PORT'],
devCommand: 'gatsby develop -p $PORT',
cachePattern: '.cache/**',
};
}

View File

@@ -8,8 +8,8 @@ export default async function detectGridsome({
return false;
}
return {
buildCommand: ['gridsome', 'build'],
buildCommand: 'gridsome build',
buildDirectory: 'dist',
devCommand: ['gridsome', 'develop', '-p', '$PORT'],
devCommand: 'gridsome develop -p $PORT',
};
}

View File

@@ -6,8 +6,8 @@ export default async function detectHexo({
const hasHexo = await hasDependency('hexo');
if (!hasHexo) return false;
return {
buildCommand: ['hexo', 'generate'],
buildCommand: 'hexo generate',
buildDirectory: 'public',
devCommand: ['hexo', 'server', '--port', '$PORT'],
devCommand: 'hexo server --port $PORT',
};
}

View File

@@ -19,8 +19,8 @@ export default async function detectHugo({
return false;
}
return {
buildCommand: ['hugo'],
buildCommand: 'hugo',
buildDirectory: config.publishDir || 'public',
devCommand: ['hugo', 'server', '-D', '-w', '-p', '$PORT'],
devCommand: 'hugo server -D -w -p $PORT',
};
}

View File

@@ -15,16 +15,8 @@ export default async function detectJekyll({
return false;
}
return {
buildCommand: ['jekyll', 'build'],
buildCommand: 'jekyll build',
buildDirectory: config.destination || '_site',
devCommand: [
'bundle',
'exec',
'jekyll',
'serve',
'--watch',
'--port',
'$PORT',
],
devCommand: 'bundle exec jekyll serve --watch --port $PORT',
};
}

View File

@@ -7,8 +7,8 @@ export default async function detectMiddleman({
if (!hasConfig) return false;
return {
buildCommand: ['bundle', 'exec', 'middleman', 'build'],
buildCommand: 'bundle exec middleman build',
buildDirectory: 'build',
devCommand: ['bundle', 'exec', 'middleman', 'server', '-p', '$PORT'],
devCommand: 'bundle exec middleman server -p $PORT',
};
}

View File

@@ -6,8 +6,8 @@ export default async function detectNext({
const hasNext = await hasDependency('next');
if (!hasNext) return false;
return {
buildCommand: ['next', 'build'],
buildCommand: 'next build',
buildDirectory: 'build',
devCommand: ['next', '-p', '$PORT'],
devCommand: 'next -p $PORT',
};
}

View File

@@ -6,9 +6,9 @@ export default async function detectPolymer({
const hasPolymer = await hasDependency('polymer-cli');
if (!hasPolymer) return false;
return {
buildCommand: ['polymer', 'build'],
buildCommand: 'polymer build',
buildDirectory: 'build',
devCommand: ['polymer', 'serve', '--port', '$PORT'],
devCommand: 'polymer serve --port $PORT',
routes: [
{
handle: 'filesystem',

View File

@@ -6,9 +6,9 @@ export default async function detectPreact({
const hasPreact = await hasDependency('preact-cli');
if (!hasPreact) return false;
return {
buildCommand: ['preact', 'build'],
buildCommand: 'preact build',
buildDirectory: 'build',
devCommand: ['preact', 'watch', '--port', '$PORT'],
devCommand: 'preact watch --port $PORT',
routes: [
{
handle: 'filesystem',

View File

@@ -6,9 +6,9 @@ export default async function detectSaber({
const hasSaber = await hasDependency('saber');
if (!hasSaber) return false;
return {
buildCommand: ['saber', 'build'],
buildCommand: 'saber build',
buildDirectory: 'public',
devCommand: ['saber', '--port', '$PORT'],
devCommand: 'saber --port $PORT',
routes: [
{
src: '/_saber/.*',

View File

@@ -6,8 +6,8 @@ export default async function detectSapper({
const hasSapper = await hasDependency('sapper');
if (!hasSapper) return false;
return {
buildCommand: ['sapper', 'export'],
buildCommand: 'sapper export',
buildDirectory: '__sapper__/export',
devCommand: ['sapper', 'dev', '--port', '$PORT'],
devCommand: 'sapper dev --port $PORT',
};
}

View File

@@ -6,17 +6,9 @@ export default async function detectStencil({
const hasStencil = await hasDependency('@stencil/core');
if (!hasStencil) return false;
return {
buildCommand: ['stencil', 'build'],
buildCommand: 'stencil build',
buildDirectory: 'www',
devCommand: [
'stencil',
'build',
'--dev',
'--watch',
'--serve',
'--port',
'$PORT',
],
devCommand: 'stencil build --dev --watch --serve --port $PORT',
routes: [
{
handle: 'filesystem',

View File

@@ -6,9 +6,9 @@ export default async function detectSvelte({
const hasSvelte = await hasDependency('sirv-cli');
if (!hasSvelte) return false;
return {
buildCommand: ['rollup', '-c'],
buildCommand: 'rollup -c',
buildDirectory: 'public',
devCommand: ['sirv', 'public', '--single', '--dev', ' --port', '$PORT'],
devCommand: 'sirv public --single --dev --port $PORT',
routes: [
{
handle: 'filesystem',

View File

@@ -6,9 +6,9 @@ export default async function detectUmiJS({
const hasUmi = await hasDependency('umi');
if (!hasUmi) return false;
return {
buildCommand: ['umi', 'build'],
buildCommand: 'umi build',
buildDirectory: 'dist',
devCommand: ['umi', 'dev', '--port', '$PORT'],
devCommand: 'umi dev --port $PORT',
routes: [
{
handle: 'filesystem',

View File

@@ -6,9 +6,9 @@ export default async function detectVue({
const hasVue = await hasDependency('@vue/cli-service');
if (!hasVue) return false;
return {
buildCommand: ['vue-cli-service', 'build'],
buildCommand: 'vue-cli-service build',
buildDirectory: 'dist',
devCommand: ['vue-cli-service', 'serve', '--port', '$PORT'],
devCommand: 'vue-cli-service serve --port $PORT',
routes: [
{
src: '^/[^/]*\\.(js|txt|ico|json)',

View File

@@ -1,4 +1,5 @@
import path from 'path';
import debug from '../debug';
import FileFsRef from '../file-fs-ref';
import { File, Files, Meta } from '../types';
import { remove, mkdirp, readlink, symlink } from 'fs-extra';
@@ -39,8 +40,12 @@ export default async function download(
basePath: string,
meta?: Meta
): Promise<DownloadedFiles> {
const { isDev = false, skipDownload = false, filesChanged = null, filesRemoved = null } =
meta || {};
const {
isDev = false,
skipDownload = false,
filesChanged = null,
filesRemoved = null,
} = meta || {};
if (isDev || skipDownload) {
// In `now dev`, the `download()` function is a no-op because
@@ -48,11 +53,14 @@ export default async function download(
// source files are already available.
return files as DownloadedFiles;
}
debug('Downloading deployment source files...');
const start = Date.now();
const files2: DownloadedFiles = {};
const filenames = Object.keys(files);
await Promise.all(
Object.keys(files).map(async name => {
filenames.map(async name => {
// If the file does not exist anymore, remove it.
if (Array.isArray(filesRemoved) && filesRemoved.includes(name)) {
await removeFile(basePath, name);
@@ -71,5 +79,8 @@ export default async function download(
})
);
const duration = Date.now() - start;
debug(`Downloaded ${filenames.length} source files: ${duration}ms`);
return files2;
}

View File

@@ -1,13 +1,21 @@
import { intersects } from 'semver';
import boxen from 'boxen';
import { NodeVersion } from '../types';
import debug from '../debug';
const supportedOptions: NodeVersion[] = [
const allOptions: NodeVersion[] = [
{ major: 12, range: '12.x', runtime: 'nodejs12.x' },
{ major: 10, range: '10.x', runtime: 'nodejs10.x' },
{ major: 8, range: '8.10.x', runtime: 'nodejs8.10' },
{
major: 8,
range: '8.10.x',
runtime: 'nodejs8.10',
discontinueDate: new Date('2020-01-06'),
},
];
const supportedOptions = allOptions.filter(o => !isDiscontinued(o));
// This version should match Fargate's default in the PATH
// Today that is Node 8
export const defaultSelection = supportedOptions.find(
@@ -28,13 +36,14 @@ export async function getSupportedNodeVersion(
);
}
} else {
const found = supportedOptions.some(o => {
const found = allOptions.some(o => {
// the array is already in order so return the first
// match which will be the newest version of node
selection = o;
return intersects(o.range, engineRange);
});
if (found) {
const discontinued = isDiscontinued(selection);
if (found && !discontinued) {
if (!silent) {
debug(
'Found `engines` in `package.json`, selecting range: ' +
@@ -42,15 +51,44 @@ export async function getSupportedNodeVersion(
);
}
} else {
if (!silent) {
throw new Error(
'Found `engines` in `package.json` with an unsupported node range: ' +
engineRange +
'\nPlease use one of the following supported ranges: ' +
JSON.stringify(supportedOptions.map(o => o.range))
);
}
throw new Error(
'Found `engines` in `package.json` with an unsupported Node.js version range: ' +
engineRange +
'\nPlease use one of the following supported ranges: ' +
JSON.stringify(supportedOptions.map(o => o.range)) +
(discontinued
? '\nThis change is the result of a decision made by an upstream infrastructure provider (AWS).' +
'\nRead more: https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html'
: '')
);
}
}
const { range, discontinueDate } = selection;
if (discontinueDate && !isDiscontinued(selection)) {
const d = discontinueDate.toISOString().split('T')[0];
const validRanges = supportedOptions
.filter(o => !o.discontinueDate)
.map(o => o.range);
console.warn(
boxen(
'WARNING' +
'\n' +
`\nNode.js ${range} will be discontinued on ${d}.` +
`\nDeployments created on or after ${d} will fail to build.` +
'\nPlease use one of the following supported `engines` in `package.json`: ' +
JSON.stringify(validRanges) +
'\nThis change is the result of a decision made by an upstream infrastructure provider (AWS).' +
'\nRead more: https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html',
{ padding: 1 }
)
);
}
return selection;
}
function isDiscontinued({ discontinueDate }: NodeVersion): boolean {
const today = new Date();
return discontinueDate !== undefined && discontinueDate <= today;
}

View File

@@ -155,7 +155,7 @@ export async function runNpmInstall(
commandArgs = args.filter(a => a !== '--prefer-offline');
await spawnAsync(
'npm',
commandArgs.concat(['install', '--unsafe-perm']),
commandArgs.concat(['install', '--no-audit', '--unsafe-perm']),
opts
);
} else {

View File

@@ -3,7 +3,7 @@ import FileFsRef from './file-fs-ref';
import FileRef from './file-ref';
import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
import { Prerender } from './prerender';
import download, { DownloadedFiles } from './fs/download';
import download, { DownloadedFiles, isSymbolicLink } from './fs/download';
import getWriteableDirectory from './fs/get-writable-directory';
import glob from './fs/glob';
import rename from './fs/rename';
@@ -52,6 +52,7 @@ export {
detectBuilders,
detectRoutes,
debug,
isSymbolicLink,
getLambdaOptionsFromFunction,
};

View File

@@ -304,6 +304,7 @@ export interface NodeVersion {
major: number;
range: string;
runtime: string;
discontinueDate?: Date;
}
export interface Builder {
@@ -352,11 +353,11 @@ export interface DetectorParameters {
}
export interface DetectorOutput {
buildCommand: string[];
buildCommand: string;
buildDirectory: string;
buildEnv?: Env;
devCommand?: string[];
devEnv?: Env;
buildVariables?: Env;
devCommand?: string;
devVariables?: Env;
minNodeRange?: string;
cachePattern?: string;
routes?: Route[];

View File

@@ -68,7 +68,7 @@ test('detectDefaults() - angular', async () => {
const result = await detectDefaults({ fs });
if (!result) throw new Error('Expected result');
assert.equal(result.buildDirectory, 'dist');
assert.deepEqual(result.buildCommand, ['ng', 'build']);
assert.deepEqual(result.buildCommand, 'ng build');
});
test('detectDefaults() - brunch', async () => {
@@ -77,7 +77,7 @@ test('detectDefaults() - brunch', async () => {
const result = await detectDefaults({ fs });
if (!result) throw new Error('Expected result');
assert.equal(result.buildDirectory, 'public');
assert.deepEqual(result.buildCommand, ['brunch', 'build', '--production']);
assert.deepEqual(result.buildCommand, 'brunch build --production');
});
test('detectDefaults() - hugo', async () => {
@@ -86,7 +86,7 @@ test('detectDefaults() - hugo', async () => {
const result = await detectDefaults({ fs });
if (!result) throw new Error('Expected result');
assert.equal(result.buildDirectory, 'public');
assert.deepEqual(result.buildCommand, ['hugo']);
assert.deepEqual(result.buildCommand, 'hugo');
});
test('detectDefaults() - jekyll', async () => {
@@ -95,7 +95,7 @@ test('detectDefaults() - jekyll', async () => {
const result = await detectDefaults({ fs });
if (!result) throw new Error('Expected result');
assert.equal(result.buildDirectory, '_site');
assert.deepEqual(result.buildCommand, ['jekyll', 'build']);
assert.deepEqual(result.buildCommand, 'jekyll build');
});
test('detectDefaults() - middleman', async () => {
@@ -104,10 +104,5 @@ test('detectDefaults() - middleman', async () => {
const result = await detectDefaults({ fs });
if (!result) throw new Error('Expected result');
assert.equal(result.buildDirectory, 'build');
assert.deepEqual(result.buildCommand, [
'bundle',
'exec',
'middleman',
'build',
]);
assert.deepEqual(result.buildCommand, 'bundle exec middleman build');
});

View File

@@ -3,6 +3,7 @@ const { mkdirp, copyFile } = require('fs-extra');
const {
glob,
debug,
download,
shouldServe,
createLambda,
@@ -14,7 +15,6 @@ exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
exports.version = 3;
exports.build = async ({ workPath, files, entrypoint, meta, config }) => {
console.log('downloading files...');
const outDir = await getWritableDirectory();
await download(files, workPath, meta);

View File

@@ -1,6 +1,6 @@
{
"name": "@now/cgi",
"version": "1.0.1-canary.0",
"version": "1.0.1-canary.1",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "now",
"version": "16.6.0",
"version": "16.6.4-canary.0",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Now",
@@ -146,7 +146,6 @@
"ora": "3.4.0",
"pcre-to-regexp": "1.0.0",
"pluralize": "7.0.0",
"pre-commit": "1.2.2",
"printf": "0.2.5",
"progress": "2.0.3",
"promisepipe": "3.0.0",

View File

@@ -2,6 +2,7 @@
const fs = require('fs');
const { promisify } = require('util');
const { join, delimiter } = require('path');
const { homedir } = require('os');
const stat = promisify(fs.stat);
const unlink = promisify(fs.unlink);
@@ -39,7 +40,16 @@ function isGlobal() {
// See: https://git.io/fj4jD
function getNowPath() {
if (process.platform === 'win32') {
const path = join(process.env.LOCALAPPDATA, 'now-cli', 'now.exe');
const { LOCALAPPDATA, USERPROFILE, HOMEPATH } = process.env;
const home = homedir() || USERPROFILE || HOMEPATH;
let path;
if (LOCALAPPDATA) {
path = join(LOCALAPPDATA, 'now-cli', 'now.exe');
} else if (home) {
path = join(home, 'AppData', 'Local', 'now-cli', 'now.exe');
} else {
path = '';
}
return fs.existsSync(path) ? path : null;
}
@@ -48,7 +58,7 @@ function getNowPath() {
const paths = [
join(process.env.HOME || '/', 'bin'),
'/usr/local/bin',
'/usr/bin'
'/usr/bin',
];
for (const basePath of paths) {

View File

@@ -294,15 +294,11 @@ export default async function main(
parseEnv(argv['--env'])
);
// Enable debug mode for builders
const buildDebugEnv = debugEnabled ? { NOW_BUILDER_DEBUG: '1' } : {};
// Merge build env out of `build.env` from now.json, and `--build-env` args
const deploymentBuildEnv = Object.assign(
{},
parseEnv(localConfig.build && localConfig.build.env),
parseEnv(argv['--build-env']),
buildDebugEnv
parseEnv(argv['--build-env'])
);
// If there's any undefined values, then inherit them from this process

View File

@@ -18,7 +18,7 @@ export default async function dev(
output: Output
) {
output.dim(
`Now CLI ${pkg.version} dev (beta) — https://zeit.co/feedback/dev`
`Now CLI ${pkg.version} dev (beta) — https://zeit.co/feedback`
);
const [dir = '.'] = args;

View File

@@ -2,13 +2,7 @@ import { NowConfig } from './util/dev/types';
export type ThenArg<T> = T extends Promise<infer U> ? U : T;
export interface Config extends NowConfig {
alias?: string[] | string;
aliases?: string[] | string;
name?: string;
type?: string;
scope?: string;
}
export type Config = NowConfig;
export interface NowContext {
argv: string[];

View File

@@ -6,12 +6,14 @@ import {
createDeployment,
createLegacyDeployment,
DeploymentOptions,
} from 'now-client/dist';
NowClientOptions,
} from 'now-client';
import wait from '../output/wait';
import { Output } from '../output';
// @ts-ignore
import Now from '../../util';
import { NowConfig } from '../dev/types';
import ua from '../ua';
export default async function processDeployment({
now,
@@ -21,9 +23,9 @@ export default async function processDeployment({
requestBody,
uploadStamp,
deployStamp,
legacy,
env,
isLegacy,
quiet,
force,
nowConfig,
}: {
now: Now;
@@ -33,27 +35,36 @@ export default async function processDeployment({
requestBody: DeploymentOptions;
uploadStamp: () => number;
deployStamp: () => number;
legacy: boolean;
env: any;
isLegacy: boolean;
quiet: boolean;
nowConfig?: NowConfig;
force?: boolean;
}) {
const { warn, log, debug, note } = output;
let bar: Progress | null = null;
const path0 = paths[0];
const opts: DeploymentOptions = {
...requestBody,
debug: now._debug,
const { env = {} } = requestBody;
const nowClientOptions: NowClientOptions = {
teamId: now.currentTeam,
apiUrl: now._apiUrl,
token: now._token,
debug: now._debug,
userAgent: ua,
path: paths[0],
force,
};
if (!legacy) {
if (!isLegacy) {
let queuedSpinner = null;
let buildSpinner = null;
let deploySpinner = null;
for await (const event of createDeployment(path0, opts, nowConfig)) {
for await (const event of createDeployment(
nowClientOptions,
requestBody,
nowConfig
)) {
if (event.type === 'hashes-calculated') {
hashes = event.payload;
}
@@ -110,7 +121,7 @@ export default async function processDeployment({
now._host = event.payload.url;
if (!quiet) {
const version = legacy ? `${chalk.grey('[v1]')} ` : '';
const version = isLegacy ? `${chalk.grey('[v1]')} ` : '';
log(`https://${event.payload.url} ${version}${deployStamp()}`);
} else {
process.stdout.write(`https://${event.payload.url}`);
@@ -176,7 +187,11 @@ export default async function processDeployment({
}
}
} else {
for await (const event of createLegacyDeployment(path0, opts, nowConfig)) {
for await (const event of createLegacyDeployment(
nowClientOptions,
requestBody,
nowConfig
)) {
if (event.type === 'hashes-calculated') {
hashes = event.payload;
}
@@ -224,7 +239,7 @@ export default async function processDeployment({
now._host = event.payload.url;
if (!quiet) {
const version = legacy ? `${chalk.grey('[v1]')} ` : '';
const version = isLegacy ? `${chalk.grey('[v1]')} ` : '';
log(`${event.payload.url} ${version}${deployStamp()}`);
} else {
process.stdout.write(`https://${event.payload.url}`);

View File

@@ -158,6 +158,14 @@ export function getBuildUtils(packages: string[]): string {
return `@now/build-utils@${version}`;
}
function parseVersionSafe(rawSpec: string) {
try {
return semver.parse(rawSpec);
} catch (e) {
return null;
}
}
export function filterPackage(
builderSpec: string,
distTag: string,
@@ -165,6 +173,17 @@ export function filterPackage(
) {
if (builderSpec in localBuilders) return false;
const parsed = npa(builderSpec);
const parsedVersion = parseVersionSafe(parsed.rawSpec);
// skip install of already installed runtime
if (
parsed.name &&
parsed.type === 'version' &&
parsedVersion &&
buildersPkg.dependencies &&
parsedVersion.version == buildersPkg.dependencies[parsed.name]
) {
return false;
}
if (
parsed.name &&
parsed.type === 'tag' &&

View File

@@ -83,12 +83,6 @@ async function createBuildProcess(
NOW_REGION: 'dev1',
};
// Builders won't show debug logs by default.
// The `NOW_BUILDER_DEBUG` env variable enables them.
if (debugEnabled) {
env.NOW_BUILDER_DEBUG = '1';
}
const buildProcess = fork(modulePath, [], {
cwd: workPath,
env,

View File

@@ -88,6 +88,17 @@ export default async function(
continue;
}
if (routeConfig.check && devServer) {
const { pathname = '/' } = url.parse(destPath);
const hasDestFile = await devServer.hasFilesystem(pathname);
if (!hasDestFile) {
// If the file is not found, `check: true` will
// behave the same as `continue: true`
reqPathname = destPath;
continue;
}
}
if (isURL(destPath)) {
found = {
found: true,

View File

@@ -9,9 +9,12 @@ import {
PackageJson,
BuilderFunctions,
} from '@now/build-utils';
import { NowConfig } from 'now-client';
import { NowRedirect, NowRewrite, NowHeader, Route } from '@now/routing-utils';
import { Output } from '../output';
export { NowConfig };
export interface DevServerOptions {
output: Output;
debug: boolean;
@@ -31,24 +34,6 @@ export interface BuildMatch extends BuildConfig {
export type RouteConfig = Route;
export interface NowConfig {
name?: string;
version?: number;
env?: EnvConfig;
build?: {
env?: EnvConfig;
};
builds?: BuildConfig[];
routes?: RouteConfig[];
files?: string[];
cleanUrls?: boolean;
rewrites?: NowRewrite[];
redirects?: NowRedirect[];
headers?: NowHeader[];
trailingSlash?: boolean;
functions?: BuilderFunctions;
}
export interface HttpHandler {
(req: http.IncomingMessage, res: http.ServerResponse): void;
}

View File

@@ -53,7 +53,6 @@ export default class Now extends EventEmitter {
nowConfig = {},
hasNowJson = false,
sessionAffinity = 'random',
atlas = false,
// Latest
name,
@@ -71,39 +70,21 @@ export default class Now extends EventEmitter {
) {
const opts = { output: this._output, hasNowJson };
const { log, warn, debug } = this._output;
const isBuilds = type === null;
const isLegacy = type !== null;
let files = [];
let hashes = {};
const relatives = {};
let engines;
let deployment;
let requestBody = {};
if (isBuilds) {
requestBody = {
token: this._token,
teamId: this.currentTeam,
env,
build,
public: wantsPublic || nowConfig.public,
name,
project,
meta,
regions,
force: forceNew,
};
if (target) {
requestBody.target = target;
}
} else if (type === 'npm') {
if (type === 'npm') {
files = await getNpmFiles(paths[0], pkg, nowConfig, opts);
// A `start` or `now-start` npm script, or a `server.js` file
// in the root directory of the deployment are required
if (
!isBuilds &&
isLegacy &&
!hasNpmStart(pkg) &&
!hasFile(paths[0], files, 'server.js')
) {
@@ -139,30 +120,30 @@ export default class Now extends EventEmitter {
const uploadStamp = stamp();
if (isBuilds) {
deployment = await processDeployment({
now: this,
output: this._output,
hashes,
paths,
requestBody,
uploadStamp,
deployStamp,
quiet,
nowConfig,
});
} else {
// Read `registry.npmjs.org` authToken from .npmrc
let authToken;
let requestBody = {
...nowConfig,
env,
build,
public: wantsPublic || nowConfig.public,
name,
project,
meta,
regions,
target: target || undefined,
};
if (type === 'npm' && forwardNpm) {
authToken =
(await readAuthToken(paths[0])) || (await readAuthToken(homedir()));
}
// Ignore specific items from Now.json
delete requestBody.scope;
delete requestBody.github;
if (isLegacy) {
// Read `registry.npmjs.org` authToken from .npmrc
const registryAuthToken =
type === 'npm' && forwardNpm
? (await readAuthToken(paths[0])) || (await readAuthToken(homedir()))
: undefined;
requestBody = {
token: this._token,
teamId: this.currentTeam,
env,
build,
meta,
@@ -172,31 +153,29 @@ export default class Now extends EventEmitter {
project,
description,
deploymentType: type,
registryAuthToken: authToken,
registryAuthToken,
engines,
scale,
sessionAffinity,
limits: nowConfig.limits,
atlas,
config: nowConfig,
functions: nowConfig.functions,
};
deployment = await processDeployment({
legacy: true,
now: this,
output: this._output,
hashes,
paths,
requestBody,
uploadStamp,
deployStamp,
quiet,
env,
nowConfig,
});
}
deployment = await processDeployment({
isLegacy,
now: this,
output: this._output,
hashes,
paths,
requestBody,
uploadStamp,
deployStamp,
quiet,
nowConfig,
force: forceNew,
});
// We report about files whose sizes are too big
let missingVersion = false;
@@ -228,7 +207,7 @@ export default class Now extends EventEmitter {
}
}
if (!isBuilds && !quiet && type === 'npm' && deployment.nodeVersion) {
if (isLegacy && !quiet && type === 'npm' && deployment.nodeVersion) {
if (engines && engines.node && !missingVersion) {
log(
chalk`Using Node.js {bold ${deployment.nodeVersion}} (requested: {dim \`${engines.node}\`})`

View File

@@ -4,8 +4,8 @@ import { filterPackage } from '../src/util/dev/builder-cache';
test('[dev-builder] filter install "latest", cached canary', async t => {
const buildersPkg = {
dependencies: {
'@now/build-utils': '0.0.1-canary.0'
}
'@now/build-utils': '0.0.1-canary.0',
},
};
const result = filterPackage('@now/build-utils', 'canary', buildersPkg);
t.is(result, true);
@@ -14,8 +14,8 @@ test('[dev-builder] filter install "latest", cached canary', async t => {
test('[dev-builder] filter install "canary", cached stable', async t => {
const buildersPkg = {
dependencies: {
'@now/build-utils': '0.0.1'
}
'@now/build-utils': '0.0.1',
},
};
const result = filterPackage(
'@now/build-utils@canary',
@@ -28,8 +28,8 @@ test('[dev-builder] filter install "canary", cached stable', async t => {
test('[dev-builder] filter install "latest", cached stable', async t => {
const buildersPkg = {
dependencies: {
'@now/build-utils': '0.0.1'
}
'@now/build-utils': '0.0.1',
},
};
const result = filterPackage('@now/build-utils', 'latest', buildersPkg);
t.is(result, false);
@@ -38,8 +38,8 @@ test('[dev-builder] filter install "latest", cached stable', async t => {
test('[dev-builder] filter install "canary", cached canary', async t => {
const buildersPkg = {
dependencies: {
'@now/build-utils': '0.0.1-canary.0'
}
'@now/build-utils': '0.0.1-canary.0',
},
};
const result = filterPackage(
'@now/build-utils@canary',
@@ -52,8 +52,8 @@ test('[dev-builder] filter install "canary", cached canary', async t => {
test('[dev-builder] filter install URL, cached stable', async t => {
const buildersPkg = {
dependencies: {
'@now/build-utils': '0.0.1'
}
'@now/build-utils': '0.0.1',
},
};
const result = filterPackage('https://tarball.now.sh', 'latest', buildersPkg);
t.is(result, true);
@@ -62,8 +62,8 @@ test('[dev-builder] filter install URL, cached stable', async t => {
test('[dev-builder] filter install URL, cached canary', async t => {
const buildersPkg = {
dependencies: {
'@now/build-utils': '0.0.1-canary.0'
}
'@now/build-utils': '0.0.1-canary.0',
},
};
const result = filterPackage('https://tarball.now.sh', 'canary', buildersPkg);
t.is(result, true);
@@ -72,8 +72,8 @@ test('[dev-builder] filter install URL, cached canary', async t => {
test('[dev-builder] filter install "latest", cached URL - stable', async t => {
const buildersPkg = {
dependencies: {
'@now/build-utils': 'https://tarball.now.sh'
}
'@now/build-utils': 'https://tarball.now.sh',
},
};
const result = filterPackage('@now/build-utils', 'latest', buildersPkg);
t.is(result, true);
@@ -82,9 +82,49 @@ test('[dev-builder] filter install "latest", cached URL - stable', async t => {
test('[dev-builder] filter install "latest", cached URL - canary', async t => {
const buildersPkg = {
dependencies: {
'@now/build-utils': 'https://tarball.now.sh'
}
'@now/build-utils': 'https://tarball.now.sh',
},
};
const result = filterPackage('@now/build-utils', 'canary', buildersPkg);
t.is(result, true);
});
test('[dev-builder] filter install not bundled version, cached same version', async t => {
const buildersPkg = {
dependencies: {
'not-bundled-package': '0.0.1',
},
};
const result = filterPackage('not-bundled-package@0.0.1', '_', buildersPkg);
t.is(result, false);
});
test('[dev-builder] filter install not bundled version, cached different version', async t => {
const buildersPkg = {
dependencies: {
'not-bundled-package': '0.0.9',
},
};
const result = filterPackage('not-bundled-package@0.0.1', '_', buildersPkg);
t.is(result, true);
});
test('[dev-builder] filter install not bundled stable, cached version', async t => {
const buildersPkg = {
dependencies: {
'not-bundled-package': '0.0.1',
},
};
const result = filterPackage('not-bundled-package', '_', buildersPkg);
t.is(result, true);
});
test('[dev-builder] filter install not bundled tagged, cached tagged', async t => {
const buildersPkg = {
dependencies: {
'not-bundled-package': '16.9.0-alpha.0',
},
};
const result = filterPackage('not-bundled-package@alpha', '_', buildersPkg);
t.is(result, true);
});

View File

@@ -0,0 +1,19 @@
{
"routes": [
{
"src": "/blog/(.*)",
"check": true,
"dest": "/blog?post=$1"
},
{
"src": "/(.*)",
"check": true,
"dest": "/src/$1"
},
{
"src": "/(.*)",
"check": true,
"dest": "/fake/$1"
}
]
}

View File

@@ -0,0 +1 @@
Blog Home

View File

@@ -154,6 +154,22 @@ function testFixtureStdio(directory, fn) {
};
}
test(
'[now dev] validate routes that use `check: true`',
testFixtureStdio('routes-check-true', async (t, port) => {
const result = await fetchWithRetry(
`http://localhost:${port}/blog/post`,
3
);
const response = await result;
validateResponseHeaders(t, response);
const body = await response.text();
t.regex(body, /Blog Home/gm);
})
);
test('[now dev] validate builds', async t => {
const directory = fixture('invalid-builds');
const output = await exec(directory);
@@ -291,10 +307,10 @@ test(
await testPath(200, '/sub', 'Sub Index Page');
await testPath(200, '/sub/another', 'Sub Another Page');
await testPath(200, '/style.css', 'body { color: green }');
await testPath(301, '/index.html', '', { Location: '/' });
await testPath(301, '/about.html', '', { Location: '/about' });
await testPath(301, '/sub/index.html', '', { Location: '/sub' });
await testPath(301, '/sub/another.html', '', { Location: '/sub/another' });
await testPath(308, '/index.html', '', { Location: '/' });
await testPath(308, '/about.html', '', { Location: '/about' });
await testPath(308, '/sub/index.html', '', { Location: '/sub' });
await testPath(308, '/sub/another.html', '', { Location: '/sub/another' });
})
);
@@ -308,10 +324,10 @@ test(
await testPath(200, '/sub/', 'Sub Index Page');
await testPath(200, '/sub/another/', 'Sub Another Page');
await testPath(200, '/style.css/', 'body { color: green }');
await testPath(301, '/index.html', '', { Location: '/' });
await testPath(301, '/about.html', '', { Location: '/about/' });
await testPath(301, '/sub/index.html', '', { Location: '/sub/' });
await testPath(301, '/sub/another.html', '', {
await testPath(308, '/index.html', '', { Location: '/' });
await testPath(308, '/about.html', '', { Location: '/about/' });
await testPath(308, '/sub/index.html', '', { Location: '/sub/' });
await testPath(308, '/sub/another.html', '', {
Location: '/sub/another/',
});
}
@@ -328,9 +344,9 @@ test(
await testPath(200, '/sub/index.html/', 'Sub Index Page');
await testPath(200, '/sub/another.html/', 'Sub Another Page');
await testPath(200, '/style.css/', 'body { color: green }');
await testPath(307, '/about.html', '', { Location: '/about.html/' });
await testPath(307, '/sub', '', { Location: '/sub/' });
await testPath(307, '/sub/another.html', '', {
await testPath(308, '/about.html', '', { Location: '/about.html/' });
await testPath(308, '/sub', '', { Location: '/sub/' });
await testPath(308, '/sub/another.html', '', {
Location: '/sub/another.html/',
});
})
@@ -346,9 +362,9 @@ test(
await testPath(200, '/sub/index.html', 'Sub Index Page');
await testPath(200, '/sub/another.html', 'Sub Another Page');
await testPath(200, '/style.css', 'body { color: green }');
await testPath(307, '/about.html/', '', { Location: '/about.html' });
await testPath(307, '/sub/', '', { Location: '/sub' });
await testPath(307, '/sub/another.html/', '', {
await testPath(308, '/about.html/', '', { Location: '/about.html' });
await testPath(308, '/sub/', '', { Location: '/sub' });
await testPath(308, '/sub/another.html/', '', {
Location: '/sub/another.html',
});
})

View File

@@ -122,11 +122,6 @@ module.exports = async session => {
'single-dotfile': {
'.testing': 'i am a dotfile',
},
'config-alias-property': {
'now.json':
'{ "alias": "test.now.sh", "builds": [ { "src": "*.html", "use": "@now/static" } ] }',
'index.html': '<span>test alias</span',
},
'config-scope-property-email': {
'now.json': `{ "scope": "${session}@zeit.pub", "builds": [ { "src": "*.html", "use": "@now/static" } ], "version": 2 }`,
'index.html': '<span>test scope email</span',
@@ -204,7 +199,7 @@ fs.writeFileSync(
'index.js',
fs
.readFileSync('index.js', 'utf8')
.replace('BUILD_ENV_DEBUG', process.env.NOW_BUILDER_DEBUG),
.replace('BUILD_ENV_DEBUG', process.env.NOW_BUILDER_DEBUG ? 'on' : 'off'),
);
`,
'index.js': `
@@ -463,6 +458,18 @@ CMD ["node", "index.js"]`,
},
}),
},
'github-and-scope-config': {
'index.txt': 'I Am a Website!',
'now.json': JSON.stringify({
scope: 'i-do-not-exist',
github: {
autoAlias: true,
autoJobCancelation: true,
enabled: true,
silent: true,
},
}),
},
};
for (const typeName of Object.keys(spec)) {

View File

@@ -1427,35 +1427,6 @@ test('ensure we render a prompt when deploying home directory', async t => {
t.true(stderr.includes('> Aborted'));
});
test('ensure the `alias` property is not sent to the API', async t => {
const directory = fixture('config-alias-property');
const { stdout, stderr, exitCode } = await execa(
binaryPath,
[directory, '--public', '--name', session, ...defaultArgs, '--force'],
{
reject: false,
}
);
console.log(stderr);
console.log(stdout);
console.log(exitCode);
// Ensure the exit code is right
t.is(exitCode, 0);
// Test if the output is really a URL
const { href, host } = new URL(stdout);
t.is(host.split('-')[0], session);
// Send a test request to the deployment
const response = await fetch(href);
const contentType = response.headers.get('content-type');
t.is(contentType, 'text/html; charset=utf-8');
});
test('ensure the `scope` property works with email', async t => {
const directory = fixture('config-scope-property-email');
@@ -1995,7 +1966,7 @@ test('use `--debug` CLI flag', async t => {
// get the content
const response = await fetch(href);
const content = await response.text();
t.is(content.trim(), '1');
t.is(content.trim(), 'off');
});
test('try to deploy non-existing path', async t => {

View File

@@ -919,35 +919,6 @@ test('ensure we render a prompt when deploying home directory', async t => {
t.true(stderr.includes('> Aborted'));
});
test('ensure the `alias` property is not sent to the API', async t => {
const directory = fixture('config-alias-property');
const { stdout, stderr, exitCode } = await execa(
binaryPath,
[directory, '--public', '--name', session, ...defaultArgs, '--force'],
{
reject: false,
}
);
console.log(stderr);
console.log(stdout);
console.log(exitCode);
// Ensure the exit code is right
t.is(exitCode, 0);
// Test if the output is really a URL
const { href, host } = new URL(stdout);
t.is(host.split('-')[0], session);
// Send a test request to the deployment
const response = await fetch(href);
const contentType = response.headers.get('content-type');
t.is(contentType, 'text/html; charset=utf-8');
});
test('ensure the `scope` property works with email', async t => {
const directory = fixture('config-scope-property-email');
@@ -1456,7 +1427,7 @@ test('use `--debug` CLI flag', async t => {
// get the content
const response = await fetch(href);
const content = await response.text();
t.is(content.trim(), '1');
t.is(content.trim(), 'off');
});
test('try to deploy non-existing path', async t => {
@@ -2056,7 +2027,6 @@ test('deploy a Lambda with a specific runtime', async t => {
t.is(build.use, 'now-php@0.0.7', JSON.stringify(build, null, 2));
});
// We need to skip this test until `now-php` supports Runtime version 3
test('fail to deploy a Lambda with a specific runtime but without a locked version', async t => {
const directory = fixture('lambda-with-invalid-runtime');
const output = await execute([directory]);
@@ -2069,6 +2039,13 @@ test('fail to deploy a Lambda with a specific runtime but without a locked versi
);
});
test('ensure `github` and `scope` are not sent to the API', async t => {
const directory = fixture('github-and-scope-config');
const output = await execute([directory]);
t.is(output.exitCode, 0, formatOutput(output));
});
test.after.always(async () => {
// Make sure the token gets revoked
await execa(binaryPath, ['logout', ...defaultArgs]);

View File

@@ -1,8 +1,8 @@
{
"name": "now-client",
"version": "5.2.4",
"main": "dist/src/index.js",
"typings": "dist/src/index.d.ts",
"version": "6.0.0",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://zeit.co",
"license": "MIT",
"files": [

View File

@@ -8,7 +8,13 @@ import {
isAliasAssigned,
isAliasError,
} from './utils/ready-state';
import { Deployment, DeploymentBuild } from './types';
import { createDebug } from './utils';
import {
Dictionary,
Deployment,
NowClientOptions,
DeploymentBuild,
} from './types';
interface DeploymentStatus {
type: string;
@@ -16,22 +22,22 @@ interface DeploymentStatus {
}
/* eslint-disable */
export default async function* checkDeploymentStatus(
export async function* checkDeploymentStatus(
deployment: Deployment,
token: string,
version: number | undefined,
teamId: string | undefined,
debug: Function,
apiUrl?: string
clientOptions: NowClientOptions
): AsyncIterableIterator<DeploymentStatus> {
const { version } = deployment;
const { token, teamId, apiUrl, userAgent } = clientOptions;
const debug = createDebug(clientOptions.debug);
let deploymentState = deployment;
let allBuildsCompleted = false;
const buildsState: { [key: string]: DeploymentBuild } = {};
const buildsState: Dictionary<DeploymentBuild> = {};
const apiDeployments = getApiDeploymentsUrl({
version,
builds: deployment.builds,
functions: deployment.functions
functions: deployment.functions,
});
debug(`Using ${version ? `${version}.0` : '2.0'} API for status checks`);
@@ -54,7 +60,7 @@ export default async function* checkDeploymentStatus(
teamId ? `?teamId=${teamId}` : ''
}`,
token,
{ apiUrl }
{ apiUrl, userAgent }
);
const data = await buildsData.json();
@@ -91,7 +97,8 @@ export default async function* checkDeploymentStatus(
`${apiDeployments}/${deployment.id || deployment.deploymentId}${
teamId ? `?teamId=${teamId}` : ''
}`,
token
token,
{ apiUrl, userAgent }
);
const deploymentUpdate = await deploymentData.json();

View File

@@ -3,26 +3,22 @@ import { readdir as readRootFolder, lstatSync } from 'fs-extra';
import readdir from 'recursive-readdir';
import { relative, join, isAbsolute } from 'path';
import hashes, { mapToObject } from './utils/hashes';
import uploadAndDeploy from './upload';
import { upload } from './upload';
import { getNowIgnore, createDebug, parseNowJSON } from './utils';
import { DeploymentError } from './errors';
import {
CreateDeploymentFunction,
DeploymentOptions,
NowJsonOptions,
} from './types';
import { NowConfig, NowClientOptions, DeploymentOptions } from './types';
export { EVENTS } from './utils';
export default function buildCreateDeployment(
version: number
): CreateDeploymentFunction {
export default function buildCreateDeployment(version: number) {
return async function* createDeployment(
path: string | string[],
options: DeploymentOptions = {},
nowConfig?: NowJsonOptions
clientOptions: NowClientOptions,
deploymentOptions: DeploymentOptions,
nowConfig: NowConfig = {}
): AsyncIterableIterator<any> {
const debug = createDebug(options.debug);
const { path } = clientOptions;
const debug = createDebug(clientOptions.debug);
const cwd = process.cwd();
debug('Creating deployment...');
@@ -38,9 +34,9 @@ export default function buildCreateDeployment(
});
}
if (typeof options.token !== 'string') {
if (typeof clientOptions.token !== 'string') {
debug(
`Error: 'token' is expected to be a string. Received ${typeof options.token}`
`Error: 'token' is expected to be a string. Received ${typeof clientOptions.token}`
);
throw new DeploymentError({
@@ -49,7 +45,8 @@ export default function buildCreateDeployment(
});
}
const isDirectory = !Array.isArray(path) && lstatSync(path).isDirectory();
clientOptions.isDirectory =
!Array.isArray(path) && lstatSync(path).isDirectory();
let rootFiles: string[];
@@ -69,7 +66,7 @@ export default function buildCreateDeployment(
});
}
if (isDirectory && !Array.isArray(path)) {
if (clientOptions.isDirectory && !Array.isArray(path)) {
debug(`Provided 'path' is a directory. Reading subpaths... `);
rootFiles = await readRootFolder(path);
debug(`Read ${rootFiles.length} subpaths`);
@@ -90,7 +87,7 @@ export default function buildCreateDeployment(
debug('Building file tree...');
if (isDirectory && !Array.isArray(path)) {
if (clientOptions.isDirectory && !Array.isArray(path)) {
// Directory path
const dirContents = await readdir(path, ignores);
const relativeFileList = dirContents.map(filePath =>
@@ -156,15 +153,14 @@ export default function buildCreateDeployment(
// from getting confused about a deployment that renders 404.
if (
fileList.length === 0 ||
fileList.every((item): boolean => {
if (!item) {
return true;
}
const segments = item.split('/');
return segments[segments.length - 1].startsWith('.');
})
fileList.every(item =>
item
? item
.split('/')
.pop()!
.startsWith('.')
: true
)
) {
debug(
`Deployment path has no files (or only dotfiles). Yielding a warning event`
@@ -181,39 +177,24 @@ export default function buildCreateDeployment(
debug(`Yielding a 'hashes-calculated' event with ${files.size} hashes`);
yield { type: 'hashes-calculated', payload: mapToObject(files) };
const {
token,
teamId,
force,
defaultName,
debug: debug_,
apiUrl,
...metadata
} = options;
if (clientOptions.apiUrl) {
debug(`Using provided API URL: ${clientOptions.apiUrl}`);
}
if (apiUrl) {
debug(`Using provided API URL: ${apiUrl}`);
if (clientOptions.userAgent) {
debug(`Using provided user agent: ${clientOptions.userAgent}`);
}
debug(`Setting platform version to ${version}`);
metadata.version = version;
const deploymentOpts = {
debug: debug_,
totalFiles: files.size,
nowConfig,
token,
isDirectory,
path,
teamId,
force,
defaultName,
metadata,
apiUrl,
};
deploymentOptions.version = version;
debug(`Creating the deployment and starting upload...`);
for await (const event of uploadAndDeploy(files, deploymentOpts)) {
for await (const event of upload(
files,
nowConfig,
clientOptions,
deploymentOptions
)) {
debug(`Yielding a '${event.type}' event`);
yield event;
}

View File

@@ -5,51 +5,41 @@ import {
createDebug,
getApiDeploymentsUrl,
} from './utils';
import checkDeploymentStatus from './deployment-status';
import { checkDeploymentStatus } from './check-deployment-status';
import { generateQueryString } from './utils/query-string';
import { Deployment, DeploymentOptions, NowJsonOptions } from './types';
import { isReady, isAliasAssigned } from './utils/ready-state';
export interface Options {
metadata: DeploymentOptions;
totalFiles: number;
path: string | string[];
token: string;
teamId?: string;
force?: boolean;
isDirectory?: boolean;
defaultName?: string;
preflight?: boolean;
debug?: boolean;
nowConfig?: NowJsonOptions;
apiUrl?: string;
}
import {
Deployment,
DeploymentOptions,
NowConfig,
NowClientOptions,
} from './types';
async function* createDeployment(
metadata: DeploymentOptions,
files: Map<string, DeploymentFile>,
options: Options,
debug: Function
clientOptions: NowClientOptions,
deploymentOptions: DeploymentOptions
): AsyncIterableIterator<{ type: string; payload: any }> {
const preparedFiles = prepareFiles(files, options);
const apiDeployments = getApiDeploymentsUrl(metadata);
const debug = createDebug(clientOptions.debug);
const preparedFiles = prepareFiles(files, clientOptions);
const apiDeployments = getApiDeploymentsUrl(deploymentOptions);
debug('Sending deployment creation API request');
try {
const dpl = await fetch(
`${apiDeployments}${generateQueryString(options)}`,
options.token,
`${apiDeployments}${generateQueryString(clientOptions)}`,
clientOptions.token,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...metadata,
...deploymentOptions,
files: preparedFiles,
}),
apiUrl: options.apiUrl,
apiUrl: clientOptions.apiUrl,
userAgent: clientOptions.userAgent,
}
);
@@ -85,87 +75,88 @@ async function* createDeployment(
}
}
const getDefaultName = (
path: string | string[] | undefined,
isDirectory: boolean | undefined,
function getDefaultName(
files: Map<string, DeploymentFile>,
debug: Function
): string => {
clientOptions: NowClientOptions
): string {
const debug = createDebug(clientOptions.debug);
const { isDirectory, path } = clientOptions;
if (isDirectory && typeof path === 'string') {
debug('Provided path is a directory. Using last segment as default name');
const segments = path.split('/');
return segments[segments.length - 1];
return path.split('/').pop()!;
} else {
debug(
'Provided path is not a directory. Using last segment of the first file as default name'
);
const filePath = Array.from(files.values())[0].names[0];
const segments = filePath.split('/');
return segments[segments.length - 1];
return filePath.split('/').pop()!;
}
};
}
export default async function* deploy(
export async function* deploy(
files: Map<string, DeploymentFile>,
options: Options
nowConfig: NowConfig,
clientOptions: NowClientOptions,
deploymentOptions: DeploymentOptions
): AsyncIterableIterator<{ type: string; payload: any }> {
const debug = createDebug(options.debug);
const nowJsonMetadata = options.nowConfig || {};
delete nowJsonMetadata.github;
delete nowJsonMetadata.scope;
const meta = options.metadata || {};
const metadata = { ...nowJsonMetadata, ...meta };
const debug = createDebug(clientOptions.debug);
// Check if we should default to a static deployment
if (!metadata.version && !metadata.name) {
metadata.version = 2;
metadata.name =
options.totalFiles === 1
? 'file'
: getDefaultName(options.path, options.isDirectory, files, debug);
if (!deploymentOptions.version && !deploymentOptions.name) {
deploymentOptions.version = 2;
deploymentOptions.name =
files.size === 1 ? 'file' : getDefaultName(files, clientOptions);
if (metadata.name === 'file') {
if (deploymentOptions.name === 'file') {
debug('Setting deployment name to "file" for single-file deployment');
}
}
if (options.totalFiles === 1 && !metadata.builds && !metadata.routes) {
if (
files.size === 1 &&
!deploymentOptions.builds &&
!deploymentOptions.routes
) {
debug(`Assigning '/' route for single file deployment`);
const filePath = Array.from(files.values())[0].names[0];
const segments = filePath.split('/');
metadata.routes = [
deploymentOptions.routes = [
{
src: '/',
dest: `/${segments[segments.length - 1]}`,
dest: `/${filePath.split('/').pop()}`,
},
];
}
if (!metadata.name) {
metadata.name =
options.defaultName ||
getDefaultName(options.path, options.isDirectory, files, debug);
debug('No name provided. Defaulting to', metadata.name);
if (!deploymentOptions.name) {
deploymentOptions.name =
clientOptions.defaultName || getDefaultName(files, clientOptions);
debug('No name provided. Defaulting to', deploymentOptions.name);
}
if (metadata.version === 1 && !metadata.deploymentType) {
debug(`Setting 'type' for 1.0 deployment to '${nowJsonMetadata.type}'`);
metadata.deploymentType = nowJsonMetadata.type;
if (
deploymentOptions.version === 1 &&
!deploymentOptions.deploymentType &&
nowConfig.type
) {
debug(`Setting 'type' for 1.0 deployment to '${nowConfig.type}'`);
deploymentOptions.deploymentType = nowConfig.type.toUpperCase() as DeploymentOptions['deploymentType'];
}
if (metadata.version === 1) {
if (deploymentOptions.version === 1 && !deploymentOptions.config) {
debug(`Writing 'config' values for 1.0 deployment`);
const nowConfig = { ...nowJsonMetadata };
delete nowConfig.version;
deploymentOptions.config = { ...nowConfig };
delete deploymentOptions.config.version;
}
metadata.config = {
...nowConfig,
...metadata.config,
};
if (
deploymentOptions.version === 1 &&
!deploymentOptions.forceNew &&
clientOptions.force
) {
debug(`Setting 'forceNew' for 1.0 deployment`);
deploymentOptions.forceNew = clientOptions.force;
}
let deployment: Deployment | undefined;
@@ -173,10 +164,9 @@ export default async function* deploy(
try {
debug('Creating deployment');
for await (const event of createDeployment(
metadata,
files,
options,
debug
clientOptions,
deploymentOptions
)) {
if (event.type === 'created') {
debug('Deployment created');
@@ -203,11 +193,7 @@ export default async function* deploy(
debug('Waiting for deployment to be ready...');
for await (const event of checkDeploymentStatus(
deployment,
options.token,
metadata.version,
options.teamId,
debug,
options.apiUrl
clientOptions
)) {
yield event;
}

View File

@@ -1,18 +1,25 @@
import { BuilderFunctions } from '@now/build-utils';
import { Builder, BuilderFunctions } from '@now/build-utils';
import { NowHeader, Route, NowRedirect, NowRewrite } from '@now/routing-utils';
export interface Route {
src: string;
dest: string;
headers?: {
[key: string]: string;
};
status?: number;
methods?: string[];
export interface Dictionary<T> {
[key: string]: T;
}
export interface Build {
src: string;
use: string;
/**
* Options for `now-client` or
* properties that should not
* be part of the payload.
*/
export interface NowClientOptions {
token: string;
path: string | string[];
debug?: boolean;
teamId?: string;
apiUrl?: string;
force?: boolean;
userAgent?: string;
defaultName?: string;
isDirectory?: boolean;
}
export interface Deployment {
@@ -20,13 +27,11 @@ export interface Deployment {
deploymentId?: string;
url: string;
name: string;
meta: {
[key: string]: string | number | boolean;
};
meta: Dictionary<string | number | boolean>;
version: number;
regions: string[];
routes: Route[];
builds?: Build[];
builds?: Builder[];
functions?: BuilderFunctions;
plan: string;
public: boolean;
@@ -47,13 +52,9 @@ export interface Deployment {
| 'ERROR';
createdAt: string;
createdIn: string;
env: {
[key: string]: string;
};
env: Dictionary<string>;
build: {
env: {
[key: string]: string;
};
env: Dictionary<string>;
};
target: string;
alias: string[];
@@ -91,51 +92,69 @@ export interface DeploymentGithubData {
autoJobCancelation: boolean;
}
export interface DeploymentOptions {
interface LegacyNowConfig {
type?: string;
aliases?: string | string[];
}
export interface NowConfig extends LegacyNowConfig {
name?: string;
version?: number;
env?: Dictionary<string>;
build?: {
env?: Dictionary<string>;
};
builds?: Builder[];
routes?: Route[];
files?: string[];
cleanUrls?: boolean;
rewrites?: NowRewrite[];
redirects?: NowRedirect[];
headers?: NowHeader[];
trailingSlash?: boolean;
functions?: BuilderFunctions;
github?: DeploymentGithubData;
scope?: string;
alias?: string | string[];
}
interface LegacyDeploymentOptions {
project?: string;
forceNew?: boolean;
description?: string;
registryAuthToken?: string;
engines?: Dictionary<string>;
sessionAffinity?: 'ip' | 'key' | 'random';
deploymentType?: 'NPM' | 'STATIC' | 'DOCKER';
scale?: Dictionary<{
min?: number;
max?: number | 'auto';
}>;
limits?: {
duration?: number;
maxConcurrentReqs?: number;
timeout?: number;
};
// Can't be NowConfig, since we don't
// include all legacy types here
config?: Dictionary<any>;
}
/**
* Options that will be sent to the API.
*/
export interface DeploymentOptions extends LegacyDeploymentOptions {
version?: number;
regions?: string[];
routes?: Route[];
builds?: Build[];
builds?: Builder[];
functions?: BuilderFunctions;
env?: {
[key: string]: string;
};
env?: Dictionary<string>;
build?: {
env: {
[key: string]: string;
};
env: Dictionary<string>;
};
target?: string;
token?: string | null;
teamId?: string;
force?: boolean;
name?: string;
defaultName?: string;
isDirectory?: boolean;
path?: string | string[];
github?: DeploymentGithubData;
scope?: string;
public?: boolean;
forceNew?: boolean;
deploymentType?: 'NPM' | 'STATIC' | 'DOCKER';
registryAuthToken?: string;
engines?: { [key: string]: string };
sessionAffinity?: 'ip' | 'random';
config?: { [key: string]: any };
debug?: boolean;
apiUrl?: string;
meta?: Dictionary<string>;
}
export interface NowJsonOptions {
github?: DeploymentGithubData;
scope?: string;
type?: 'NPM' | 'STATIC' | 'DOCKER';
version?: number;
files?: string[];
}
export type CreateDeploymentFunction = (
path: string | string[],
options?: DeploymentOptions,
nowConfig?: NowJsonOptions
) => AsyncIterableIterator<any>;

View File

@@ -4,8 +4,9 @@ import retry from 'async-retry';
import { Sema } from 'async-sema';
import { DeploymentFile } from './utils/hashes';
import { fetch, API_FILES, createDebug } from './utils';
import { DeploymentError } from '.';
import deploy, { Options } from './deploy';
import { DeploymentError } from './errors';
import { deploy } from './deploy';
import { NowConfig, NowClientOptions, DeploymentOptions } from './types';
const isClientNetworkError = (err: Error | DeploymentError) => {
if (err.message) {
@@ -24,12 +25,14 @@ const isClientNetworkError = (err: Error | DeploymentError) => {
return false;
};
export default async function* upload(
export async function* upload(
files: Map<string, DeploymentFile>,
options: Options
nowConfig: NowConfig,
clientOptions: NowClientOptions,
deploymentOptions: DeploymentOptions
): AsyncIterableIterator<any> {
const { token, teamId, debug: isDebug, apiUrl } = options;
const debug = createDebug(isDebug);
const { token, teamId, apiUrl, userAgent } = clientOptions;
const debug = createDebug(clientOptions.debug);
if (!files && !token && !teamId) {
debug(`Neither 'files', 'token' nor 'teamId are present. Exiting`);
@@ -40,7 +43,12 @@ export default async function* upload(
debug('Determining necessary files for upload...');
for await (const event of deploy(files, options)) {
for await (const event of deploy(
files,
nowConfig,
clientOptions,
deploymentOptions
)) {
if (event.type === 'error') {
if (event.payload.code === 'missing_files') {
missingFiles = event.payload.missing;
@@ -105,8 +113,9 @@ export default async function* upload(
body: stream,
teamId,
apiUrl,
userAgent,
},
isDebug
clientOptions.debug
);
if (res.status === 200) {
@@ -185,7 +194,12 @@ export default async function* upload(
try {
debug('Starting deployment creation');
for await (const event of deploy(files, options)) {
for await (const event of deploy(
files,
nowConfig,
clientOptions,
deploymentOptions
)) {
if (event.type === 'alias-assigned') {
debug('Deployment is ready');
return yield event;

View File

@@ -1,12 +1,11 @@
import { DeploymentFile } from './hashes';
import { parse as parseUrl } from 'url';
import fetch_ from 'node-fetch';
import fetch_, { RequestInit } from 'node-fetch';
import { join, sep } from 'path';
import qs from 'querystring';
import ignore from 'ignore';
import { pkgVersion } from '../pkg';
import { Options } from '../deploy';
import { NowJsonOptions, DeploymentOptions } from '../types';
import { NowClientOptions, DeploymentOptions, NowConfig } from '../types';
import { Sema } from 'async-sema';
import { readFile } from 'fs-extra';
const semaphore = new Sema(10);
@@ -44,7 +43,7 @@ export function getApiDeploymentsUrl(
return '/v11/now/deployments';
}
export async function parseNowJSON(filePath?: string): Promise<NowJsonOptions> {
export async function parseNowJSON(filePath?: string): Promise<NowConfig> {
if (!filePath) {
return {};
}
@@ -111,10 +110,18 @@ export async function getNowIgnore(path: string | string[]): Promise<any> {
return { ig, ignores };
}
interface FetchOpts extends RequestInit {
apiUrl?: string;
method?: string;
teamId?: string;
headers?: { [key: string]: any };
userAgent?: string;
}
export const fetch = async (
url: string,
token: string,
opts: any = {},
opts: FetchOpts = {},
debugEnabled?: boolean
): Promise<any> => {
semaphore.acquire();
@@ -133,11 +140,14 @@ export const fetch = async (
delete opts.teamId;
}
const userAgent = opts.userAgent || `now-client-v${pkgVersion}`;
delete opts.userAgent;
opts.headers = {
...opts.headers,
authorization: `Bearer ${token}`,
accept: 'application/json',
'user-agent': `now-client-v${pkgVersion}`,
'user-agent': userAgent,
};
debug(`${opts.method || 'GET'} ${url}`);
@@ -160,7 +170,7 @@ const isWin = process.platform.includes('win');
export const prepareFiles = (
files: Map<string, DeploymentFile>,
options: Options
clientOptions: NowClientOptions
): PreparedFile[] => {
const preparedFiles = [...files.keys()].reduce(
(acc: PreparedFile[], sha: string): PreparedFile[] => {
@@ -171,10 +181,10 @@ export const prepareFiles = (
for (const name of file.names) {
let fileName: string;
if (options.isDirectory) {
if (clientOptions.isDirectory) {
// Directory
fileName = options.path
? name.substring(options.path.length + 1)
fileName = clientOptions.path
? name.substring(clientOptions.path.length + 1)
: name;
} else {
// Array of files or single file

View File

@@ -1,13 +1,16 @@
import { Options } from '../deploy';
import { URLSearchParams } from 'url';
import { NowClientOptions } from '../types';
export const generateQueryString = (options: Options): string => {
if (options.force && options.teamId) {
return `?teamId=${options.teamId}&forceNew=1`;
} else if (options.teamId) {
return `?teamId=${options.teamId}`;
} else if (options.force) {
return `?forceNew=1`;
export function generateQueryString(clientOptions: NowClientOptions): string {
const options = new URLSearchParams();
if (clientOptions.teamId) {
options.set('teamId', clientOptions.teamId);
}
return '';
};
if (clientOptions.force) {
options.set('forceNew', '1');
}
return Array.from(options.entries()).length ? `?${options.toString()}` : '';
}

View File

@@ -28,10 +28,12 @@ describe('create v2 deployment', () => {
it('will display an empty deployment warning', async () => {
for await (const event of createDeployment(
path.resolve(__dirname, 'fixtures', 'v2'),
{
token,
name: 'now-client-tests-v2',
path: path.resolve(__dirname, 'fixtures', 'v2'),
},
{
name: 'now-clien-tests-v2',
}
)) {
if (event.type === 'warning') {
@@ -47,9 +49,11 @@ describe('create v2 deployment', () => {
it('will report correct file count event', async () => {
for await (const event of createDeployment(
path.resolve(__dirname, 'fixtures', 'v2'),
{
token,
path: path.resolve(__dirname, 'fixtures', 'v2'),
},
{
name: 'now-client-tests-v2',
}
)) {
@@ -66,9 +70,11 @@ describe('create v2 deployment', () => {
it('will create a v2 deployment', async () => {
for await (const event of createDeployment(
path.resolve(__dirname, 'fixtures', 'v2'),
{
token,
path: path.resolve(__dirname, 'fixtures', 'v2'),
},
{
name: 'now-client-tests-v2',
}
)) {
@@ -82,9 +88,11 @@ describe('create v2 deployment', () => {
it('will create a v2 deployment with correct file permissions', async () => {
for await (const event of createDeployment(
path.resolve(__dirname, 'fixtures', 'v2-file-permissions'),
{
token,
path: path.resolve(__dirname, 'fixtures', 'v2-file-permissions'),
},
{
name: 'now-client-tests-v2',
}
)) {
@@ -104,10 +112,12 @@ describe('create v2 deployment', () => {
it('will create a v2 deployment and ignore files specified in .nowignore', async () => {
for await (const event of createDeployment(
path.resolve(__dirname, 'fixtures', 'nowignore'),
{
token,
name: 'now-client-tests-v2-ignore',
path: path.resolve(__dirname, 'fixtures', 'nowignore'),
},
{
name: 'now-client-tests-v2',
}
)) {
if (event.type === 'ready') {

View File

@@ -29,9 +29,11 @@ describe('create v1 deployment', () => {
it('will create a v1 static deployment', async () => {
for await (const event of createLegacyDeployment(
path.resolve(__dirname, 'fixtures', 'v1', 'static'),
{
token,
path: path.resolve(__dirname, 'fixtures', 'v1', 'static'),
},
{
name: 'now-client-tests-v1-static',
}
)) {
@@ -47,9 +49,11 @@ describe('create v1 deployment', () => {
it('will create a v1 npm deployment', async () => {
for await (const event of createLegacyDeployment(
path.resolve(__dirname, 'fixtures', 'v1', 'npm'),
{
token,
path: path.resolve(__dirname, 'fixtures', 'v1', 'npm'),
},
{
name: 'now-client-tests-v1-npm',
}
)) {
@@ -65,9 +69,11 @@ describe('create v1 deployment', () => {
it('will create a v1 Docker deployment', async () => {
for await (const event of createLegacyDeployment(
path.resolve(__dirname, 'fixtures', 'v1', 'docker'),
{
token,
path: path.resolve(__dirname, 'fixtures', 'v1', 'docker'),
},
{
name: 'now-client-tests-v1-docker',
}
)) {

View File

@@ -10,10 +10,15 @@ describe('path handling', () => {
it('will fali with a relative path', async () => {
try {
await createDeployment('./fixtures/v2/now.json', {
token,
name: 'now-client-tests-v2',
});
await createDeployment(
{
token,
path: './fixtures/v2/now.json',
},
{
name: 'now-client-tests-v2',
}
);
} catch (e) {
expect(e.code).toEqual('invalid_path');
}
@@ -21,10 +26,15 @@ describe('path handling', () => {
it('will fali with an array of relative paths', async () => {
try {
await createDeployment(['./fixtures/v2/now.json'], {
token,
name: 'now-client-tests-v2',
});
await createDeployment(
{
token,
path: ['./fixtures/v2/now.json'],
},
{
name: 'now-client-tests-v2',
}
);
} catch (e) {
expect(e.code).toEqual('invalid_path');
}

View File

@@ -70,7 +70,6 @@ Learn more: https://github.com/golang/go/wiki/Modules
`);
}
debug('Downloading user files...');
const entrypointArr = entrypoint.split(sep);
// eslint-disable-next-line prefer-const

View File

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

View File

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

View File

@@ -10,25 +10,15 @@ process.on('unhandledRejection', err => {
process.exit(1);
});
async function main(cwd: string) {
const next = require(resolveFrom(cwd, 'next'));
const app = next({ dev: true, dir: cwd });
process.once('message', async ({ dir, runtimeEnv }) => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const next = require(resolveFrom(dir, 'next'));
const app = next({ dev: true, dir });
const handler = app.getRequestHandler();
const openPort = await getPort({
port: [5000, 4000],
});
const [openPort] = await Promise.all([getPort(), app.prepare()]);
const url = `http://localhost:${openPort}`;
// Prepare for incoming requests
await app.prepare();
// The runtime env vars are passed in to `argv[2]`
// as a base64-encoded JSON string
const runtimeEnv = JSON.parse(
Buffer.from(process.argv[2], 'base64').toString()
);
syncEnvVars(process.env, process.env, runtimeEnv);
createServer((req, res) => {
@@ -39,6 +29,4 @@ async function main(cwd: string) {
process.send(url);
}
});
}
main(process.cwd());
});

View File

@@ -1,10 +1,10 @@
import { ChildProcess, fork } from 'child_process';
import url from 'url'
import {
pathExists,
readFile,
unlink as unlinkFile,
writeFile,
lstatSync,
} from 'fs-extra';
import os from 'os';
import path from 'path';
@@ -60,8 +60,8 @@ import {
import {
convertRedirects,
convertRewrites
} from '@now/routing-utils/dist/superstatic'
convertRewrites,
} from '@now/routing-utils/dist/superstatic';
interface BuildParamsMeta {
isDev: boolean | undefined;
@@ -77,7 +77,7 @@ interface BuildParamsType extends BuildOptions {
}
export const version = 2;
const htmlContentType = 'text/html; charset=utf-8';
const nowDevChildProcesses = new Set<ChildProcess>();
['SIGINT', 'SIGTERM'].forEach(signal => {
@@ -163,24 +163,20 @@ const name = '[@now/next]';
const urls: stringMap = {};
function startDevServer(entryPath: string, runtimeEnv: EnvConfig) {
// The runtime env vars are encoded and passed in as `argv[2]`, so that the
// dev-server process can replace them onto `process.env` after the Next.js
// "prepare" step
const encodedEnv = Buffer.from(JSON.stringify(runtimeEnv)).toString('base64');
// `env` is omitted since that
// makes it default to `process.env`
const forked = fork(path.join(__dirname, 'dev-server.js'), [encodedEnv], {
// `env` is omitted since that makes it default to `process.env`
const forked = fork(path.join(__dirname, 'dev-server.js'), [], {
cwd: entryPath,
execArgv: [],
});
const getUrl = () =>
new Promise<string>((resolve, reject) => {
forked.on('message', resolve);
forked.on('error', reject);
forked.once('message', resolve);
forked.once('error', reject);
});
forked.send({ dir: entryPath, runtimeEnv });
return { forked, getUrl };
}
@@ -202,7 +198,6 @@ export const build = async ({
const entryPath = path.join(workPath, entryDirectory);
const dotNextStatic = path.join(entryPath, '.next/static');
debug(`${name} Downloading user files...`);
await download(files, workPath, meta);
const pkg = await readPackageJson(entryPath);
@@ -353,7 +348,6 @@ export const build = async ({
await unlinkFile(path.join(entryPath, '.npmrc'));
}
const exportedPageRoutes: Route[] = [];
const lambdas: { [key: string]: Lambda } = {};
const prerenders: { [key: string]: Prerender | FileFsRef } = {};
const staticPages: { [key: string]: FileFsRef } = {};
@@ -484,18 +478,14 @@ export const build = async ({
return;
}
const staticRoute = path.join(entryDirectory, page);
const staticRoute = path.join(entryDirectory, pathname);
staticPages[staticRoute] = staticPageFiles[page];
staticPages[staticRoute].contentType = htmlContentType;
if (isDynamicRoute(pathname)) {
dynamicPages.push(routeName);
return;
}
exportedPageRoutes.push({
src: `^${path.join('/', entryDirectory, pathname)}$`,
dest: path.join('/', staticRoute),
});
});
const pageKeys = Object.keys(pages);
@@ -589,9 +579,11 @@ export const build = async ({
// Initial files are manually added to the lambda later
return;
}
const { mode } = lstatSync(path.join(workPath, file));
files[file] = new FileFsRef({
fsPath: path.join(workPath, file),
mode,
});
};
@@ -743,14 +735,9 @@ export const build = async ({
if (htmlFsRef == null || jsonFsRef == null) {
throw new Error('invariant: htmlFsRef != null && jsonFsRef != null');
}
const outputPathPageHtml = outputPathPage.concat('.html');
prerenders[outputPathPageHtml] = htmlFsRef;
htmlFsRef.contentType = htmlContentType;
prerenders[outputPathPage] = htmlFsRef;
prerenders[outputPathData] = jsonFsRef;
exportedPageRoutes.push({
src: path.posix.join('/', outputPathPage),
dest: outputPathPageHtml,
});
} else {
const lambda = lambdas[outputSrcPathPage];
if (lambda == null) {
@@ -832,7 +819,7 @@ export const build = async ({
let dynamicPrefix = path.join('/', entryDirectory);
dynamicPrefix = dynamicPrefix === '/' ? '' : dynamicPrefix;
const routesManifest = await getRoutesManifest(entryPath, realNextVersion)
const routesManifest = await getRoutesManifest(entryPath, realNextVersion);
const dynamicRoutes = await getDynamicRoutes(
entryPath,
@@ -842,25 +829,20 @@ export const build = async ({
routesManifest
).then(arr =>
arr.map(route => {
// make sure .html is added to dest for now until
// outputting static files to clean routes is available
if (staticPages[`${route.dest}.html`.substr(1)]) {
route.dest = `${route.dest}.html`;
}
route.src = route.src.replace('^', `^${dynamicPrefix}`);
return route;
})
);
const rewrites: Route[] = []
const redirects: Route[] = []
const rewrites: Route[] = [];
const redirects: Route[] = [];
if (routesManifest) {
switch(routesManifest.version) {
switch (routesManifest.version) {
case 1: {
redirects.push(...convertRedirects(routesManifest.redirects))
rewrites.push(...convertRewrites(routesManifest.rewrites))
break
redirects.push(...convertRedirects(routesManifest.redirects));
rewrites.push(...convertRewrites(routesManifest.rewrites));
break;
}
default: {
// update MIN_ROUTES_MANIFEST_VERSION in ./utils.ts
@@ -872,24 +854,6 @@ export const build = async ({
}
}
const topRoutes = [
// Before we handle static files we need to set proper caching headers
{
// This ensures we only match known emitted-by-Next.js files and not
// user-emitted files which may be missing a hash in their filename.
src: path.join(
'/',
entryDirectory,
'_next/static/(?:[^/]+/pages|chunks|runtime|css|media)/.+'
),
// Next.js assets contain a hash or entropy in their filenames, so they
// are guaranteed to be unique and cacheable indefinitely.
headers: { 'cache-control': 'public,max-age=31536000,immutable' },
continue: true,
},
{ src: path.join('/', entryDirectory, '_next(?!/data(?:/|$))(?:/.*)?') },
]
return {
output: {
...publicDirectoryFiles,
@@ -901,17 +865,27 @@ export const build = async ({
...staticDirectoryFiles,
},
routes: [
...topRoutes,
// redirects take the highest priority
...redirects,
...rewrites,
// we need to re-apply the routes above rewrites in-case the are
// rewriting to one of those routes
...topRoutes,
// Static exported pages (.html rewrites)
...exportedPageRoutes,
// Before we handle static files we need to set proper caching headers
{
// This ensures we only match known emitted-by-Next.js files and not
// user-emitted files which may be missing a hash in their filename.
src: path.join(
'/',
entryDirectory,
'_next/static/(?:[^/]+/pages|chunks|runtime|css|media)/.+'
),
// Next.js assets contain a hash or entropy in their filenames, so they
// are guaranteed to be unique and cacheable indefinitely.
headers: { 'cache-control': 'public,max-age=31536000,immutable' },
continue: true,
},
{ src: path.join('/', entryDirectory, '_next(?!/data(?:/|$))(?:/.*)?') },
// Next.js page lambdas, `static/` folder, reserved assets, and `public/`
// folder
{ handle: 'filesystem' },
...rewrites,
// Dynamic routes
...dynamicRoutes,
...dynamicDataRoutes,
@@ -934,7 +908,7 @@ export const build = async ({
export const prepareCache = async ({
workPath,
entrypoint,
}: PrepareCacheOptions) => {
}: PrepareCacheOptions): Promise<Files> => {
debug('Preparing cache...');
const entryDirectory = path.dirname(entrypoint);
const entryPath = path.join(workPath, entryDirectory);
@@ -954,8 +928,6 @@ export const prepareCache = async ({
const cache = {
...(await glob(path.join(cacheEntrypoint, 'node_modules/**'), workPath)),
...(await glob(path.join(cacheEntrypoint, '.next/cache/**'), workPath)),
...(await glob(path.join(cacheEntrypoint, 'package-lock.json'), workPath)),
...(await glob(path.join(cacheEntrypoint, 'yarn.lock'), workPath)),
};
debug('Cache file manifest produced');
return cache;

View File

@@ -12,6 +12,7 @@ import {
streamToBuffer,
Lambda,
Route,
isSymbolicLink,
} from '@now/build-utils';
type stringMap = { [key: string]: string };
@@ -293,37 +294,36 @@ async function getRoutes(
}
export type Rewrite = {
source: string,
destination: string,
}
source: string;
destination: string;
};
export type Redirect = Rewrite & {
statusCode?: number
}
statusCode?: number;
};
type RoutesManifestRegex = {
regex: string,
regexKeys: string[]
}
regex: string;
regexKeys: string[];
};
export type RoutesManifest = {
redirects: (Redirect & RoutesManifestRegex)[],
rewrites: (Rewrite & RoutesManifestRegex)[],
redirects: (Redirect & RoutesManifestRegex)[];
rewrites: (Rewrite & RoutesManifestRegex)[];
dynamicRoutes: {
page: string,
regex: string,
}[],
version: number
}
page: string;
regex: string;
}[];
version: number;
};
export async function getRoutesManifest(
entryPath: string,
nextVersion?: string,
): Promise< RoutesManifest | undefined> {
const shouldHaveManifest = (
nextVersion && semver.gte(nextVersion, '9.1.4-canary.0')
)
if (!shouldHaveManifest) return
nextVersion?: string
): Promise<RoutesManifest | undefined> {
const shouldHaveManifest =
nextVersion && semver.gte(nextVersion, '9.1.4-canary.0');
if (!shouldHaveManifest) return;
const pathRoutesManifest = path.join(
entryPath,
@@ -338,12 +338,13 @@ export async function getRoutesManifest(
if (shouldHaveManifest && !hasRoutesManifest) {
throw new Error(
`A routes-manifest.json couldn't be found. This could be due to a failure during the build`
)
);
}
const routesManifest: RoutesManifest = require(pathRoutesManifest)
// eslint-disable-next-line @typescript-eslint/no-var-requires
const routesManifest: RoutesManifest = require(pathRoutesManifest);
return routesManifest
return routesManifest;
}
export async function getDynamicRoutes(
@@ -455,11 +456,20 @@ function syncEnvVars(base: EnvConfig, removeEnv: EnvConfig, addEnv: EnvConfig) {
export const ExperimentalTraceVersion = `9.0.4-canary.1`;
export type PseudoLayer = {
[fileName: string]: {
crc32: number;
compBuffer: Buffer;
uncompressedSize: number;
};
[fileName: string]: PseudoFile | PseudoSymbolicLink;
};
export type PseudoFile = {
isSymlink: false;
crc32: number;
compBuffer: Buffer;
uncompressedSize: number;
};
export type PseudoSymbolicLink = {
isSymlink: true;
file: FileFsRef;
symlinkTarget: string;
};
const compressBuffer = (buf: Buffer): Promise<Buffer> => {
@@ -482,13 +492,22 @@ export async function createPseudoLayer(files: {
for (const fileName of Object.keys(files)) {
const file = files[fileName];
const origBuffer = await streamToBuffer(file.toStream());
const compBuffer = await compressBuffer(origBuffer);
pseudoLayer[fileName] = {
compBuffer,
crc32: crc32.unsigned(origBuffer),
uncompressedSize: origBuffer.byteLength,
};
if (isSymbolicLink(file.mode)) {
pseudoLayer[fileName] = {
file,
isSymlink: true,
symlinkTarget: await fs.readlink(file.fsPath),
} as PseudoSymbolicLink;
} else {
const origBuffer = await streamToBuffer(file.toStream());
const compBuffer = await compressBuffer(origBuffer);
pseudoLayer[fileName] = {
compBuffer,
crc32: crc32.unsigned(origBuffer),
uncompressedSize: origBuffer.byteLength,
} as PseudoFile;
}
}
return pseudoLayer;
@@ -521,10 +540,31 @@ export async function createLambdaFromPseudoLayers({
const zipFile = new ZipFile();
const addedFiles = new Set();
const names = Object.keys(files).sort();
const symlinkTargets = new Map<string, string>();
for (const name of names) {
const file = files[name];
if (file.mode && isSymbolicLink(file.mode) && file.type === 'FileFsRef') {
const symlinkTarget = await fs.readlink((file as FileFsRef).fsPath);
symlinkTargets.set(name, symlinkTarget);
}
}
// apply pseudo layers (already compressed objects)
for (const layer of layers) {
for (const seedKey of Object.keys(layer)) {
const { compBuffer, crc32, uncompressedSize } = layer[seedKey];
const item = layer[seedKey];
if (item.isSymlink) {
const { symlinkTarget, file } = item;
zipFile.addBuffer(Buffer.from(symlinkTarget, 'utf8'), seedKey, {
mode: file.mode,
});
continue;
}
const { compBuffer, crc32, uncompressedSize } = item;
// @ts-ignore: `addDeflatedBuffer` is a valid function, but missing on the type
zipFile.addDeflatedBuffer(compBuffer, seedKey, {
@@ -540,8 +580,16 @@ export async function createLambdaFromPseudoLayers({
// was already added in a pseudo layer
if (addedFiles.has(fileName)) continue;
const file = files[fileName];
const fileBuffer = await streamToBuffer(file.toStream());
zipFile.addBuffer(fileBuffer, fileName);
const symlinkTarget = symlinkTargets.get(fileName);
if (typeof symlinkTarget === 'string') {
zipFile.addBuffer(Buffer.from(symlinkTarget, 'utf8'), fileName, {
mode: file.mode,
});
} else {
const fileBuffer = await streamToBuffer(file.toStream());
zipFile.addBuffer(fileBuffer, fileName);
}
}
zipFile.end();

View File

@@ -1,6 +1,6 @@
{
"dependencies": {
"next": "^9.1.2-canary.8",
"next": "^9.1.6-canary.1",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}

View File

@@ -1,16 +1,16 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getStaticParams() {
export async function unstable_getStaticPaths () {
return [
'/blog/post-1/comment-1',
{ post: 'post-2', comment: 'comment-2' },
{ params: { post: 'post-2', comment: 'comment-2' } },
'/blog/post-1337/comment-1337',
];
}
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps({ params }) {
export async function unstable_getStaticProps ({ params }) {
return {
props: {
post: params.post,

View File

@@ -1,13 +1,14 @@
import React from 'react'
// eslint-disable-next-line camelcase
export async function unstable_getStaticParams () {
export async function unstable_getStaticPaths () {
return [
'/blog/post-1',
{ post: 'post-2' },
{ params: { post: 'post-2' } },
]
}
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps ({ params }) {
if (params.post === 'post-10') {

View File

@@ -0,0 +1,6 @@
node_modules
.next
.env
tsconfig.tsbuildinfo
.DS_Store
*.log

View File

@@ -0,0 +1,5 @@
{
"packages": ["packages/*"],
"npmClient": "yarn",
"version": "0.0.0"
}

View File

@@ -0,0 +1,11 @@
{
"version": 2,
"builds": [{ "src": "packages/web/next.config.js", "use": "@now/next" }],
"routes": [{ "src": "/(.*)", "dest": "/packages/web/$1", "continue": true }],
"probes": [
{
"path": "/",
"mustContain": "hello world <!-- -->6"
}
]
}

View File

@@ -0,0 +1,14 @@
{
"private": true,
"workspaces": {
"packages": [
"packages/*"
]
},
"scripts": {
"postinstall": "lerna run build --scope=@jimmy/common"
},
"devDependencies": {
"lerna": "^3.19.0"
}
}

View File

@@ -0,0 +1 @@
export declare const add: (a: number, b: number) => number;

View File

@@ -0,0 +1,6 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.add = (a, b) => {
return a + b;
};
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAa,QAAA,GAAG,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE;IAC1C,OAAO,CAAC,GAAG,CAAC,CAAC;AACf,CAAC,CAAC"}

View File

@@ -0,0 +1,14 @@
{
"name": "@jimmy/common",
"version": "1.0.64",
"types": "dist/index.d.ts",
"main": "dist/index.js",
"license": "MIT",
"scripts": {
"watch": "tsc -w",
"build": "tsc"
},
"devDependencies": {
"typescript": "^3.7.3"
}
}

View File

@@ -0,0 +1,3 @@
export const add = (a: number, b: number) => {
return a + b;
};

View File

@@ -0,0 +1,32 @@
{
"compilerOptions": {
"sourceMap": true,
"removeComments": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"composite": true,
"rootDir": "src",
"outDir": "dist",
"target": "es6",
"module": "commonjs",
"lib": ["dom", "es2017", "esnext.asynciterable", "es2017.object"],
"declaration": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.json"],
"exclude": ["dist"]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />

View File

@@ -0,0 +1 @@
module.exports = {};

View File

@@ -0,0 +1,24 @@
{
"name": "@jimmy/web",
"version": "1.0.67",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@jimmy/common": "^1.0.64",
"next": "^9.1.4",
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
"devDependencies": {
"@babel/plugin-syntax-class-properties": "^7.7.4",
"@types/next": "^9.0.0",
"@types/node": "^12.12.14",
"@types/react": "^16.9.15",
"@types/react-dom": "16.9.4",
"typescript": "3.7.3"
},
"license": "ISC"
}

View File

@@ -0,0 +1,58 @@
import App from "next/app";
import React from "react";
class MyApp extends App<any> {
static async getInitialProps() {
console.log("i am props");
return { q: 5, pageProps: {} };
}
render() {
const { Component, pageProps } = this.props;
return (
<>
<div>yo</div>
<Component {...pageProps} />
<style jsx global>
{`
html {
min-height: 100%;
display: flex;
flex-direction: column;
position: relative;
}
h1 {
font-size: 40px;
}
body,
body > div {
flex: 1;
display: flex;
flex-direction: column;
}
div,
input,
form,
li,
ul {
box-sizing: border-box;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
`}
</style>
</>
);
}
}
export default MyApp;

View File

@@ -0,0 +1,5 @@
import { add } from "@jimmy/common";
export default () => {
return <div>hello world {add(1, 5)}</div>;
};

View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"jsx": "preserve",
"lib": ["dom", "es2017"],
"baseUrl": ".",
"moduleResolution": "node",
"strict": true,
"allowJs": true,
"noEmit": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"isolatedModules": true,
"removeComments": false,
"preserveConstEnums": true,
"sourceMap": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"exclude": ["dist", ".next", "out", "next.config.js"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ const FOUR_MINUTES = 240000;
beforeAll(() => {
process.env.NEXT_TELEMETRY_DISABLED = '1';
})
});
it(
'Should build the standard example',
@@ -14,7 +14,7 @@ it(
const {
buildResult: { output },
} = await runBuildLambda(path.join(__dirname, 'standard'));
expect(output['index.html']).toBeDefined();
expect(output['index']).toBeDefined();
expect(output.goodbye).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath =>
@@ -274,7 +274,7 @@ it(
buildResult: { output },
} = await runBuildLambda(path.join(__dirname, 'serverless-config-object'));
expect(output['index.html']).toBeDefined();
expect(output['index']).toBeDefined();
expect(output.goodbye).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath =>
@@ -308,7 +308,7 @@ it(
buildResult: { output },
} = await runBuildLambda(path.join(__dirname, 'serverless-no-config'));
expect(output['index.html']).toBeDefined();
expect(output['index']).toBeDefined();
expect(output.goodbye).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath =>
@@ -344,7 +344,7 @@ it(
path.join(__dirname, 'serverless-no-config-build')
);
expect(output['index.html']).toBeDefined();
expect(output['index']).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath =>
filePath.match(/_error/)

View File

@@ -8,7 +8,7 @@ jest.setTimeout(45000);
beforeAll(() => {
process.env.NEXT_TELEMETRY_DISABLED = '1';
})
});
describe('build meta dev', () => {
const files = {
@@ -18,40 +18,40 @@ describe('build meta dev', () => {
module.exports = {
target: 'serverless'
}
`
`,
}),
'pages/index.js': new FileBlob({
mode: 0o777,
data: `
export default () => 'Index page'
`
`,
}),
'pages/nested/[param].js': new FileBlob({
mode: 0o777,
data: `
export default () => 'Dynamic page'
`
`,
}),
'pages/nested/page.tsx': new FileBlob({
mode: 0o777,
data: `
export default () => 'Nested page'
`
`,
}),
'pages/api/test.js': new FileBlob({
mode: 0o777,
data: `
export default (req, res) => res.status(200).end('API Route')
`
`,
}),
// This file should be omitted because `pages/index.js` will use the same route
'public/index': new FileBlob({
mode: 0o777,
data: 'text'
data: 'text',
}),
'public/data.txt': new FileBlob({
mode: 0o777,
data: 'data'
data: 'data',
}),
'package.json': new FileBlob({
mode: 0o777,
@@ -71,8 +71,8 @@ describe('build meta dev', () => {
"typescript": "3"
}
}
`
})
`,
}),
};
const entrypoint = 'next.config.js';
const workPath = path.join(
@@ -92,7 +92,7 @@ describe('build meta dev', () => {
await execa('yarn', ['install'], {
cwd: workPath,
env: process.env,
reject: true
reject: true,
});
const meta = { isDev: true, requestPath: null };
@@ -100,11 +100,11 @@ describe('build meta dev', () => {
files,
workPath,
entrypoint,
meta
meta,
});
routes.forEach(route => {
// eslint-disable-next-line no-param-reassign
route.dest = route.dest.replace(':4000', ':5000');
route.dest = route.dest.replace(/:\d+/, ':5000');
});
expect(output).toEqual({});
expect(routes).toEqual([
@@ -116,9 +116,9 @@ describe('build meta dev', () => {
{ src: '/api/test', dest: 'http://localhost:5000/api/test' },
{
src: '^/(nested\\/([^\\/]+?)(?:\\/)?)$',
dest: 'http://localhost:5000/$1'
dest: 'http://localhost:5000/$1',
},
{ src: '/data.txt', dest: 'http://localhost:5000/data.txt' }
{ src: '/data.txt', dest: 'http://localhost:5000/data.txt' },
]);
expect(watch).toEqual([
'next.config.js',
@@ -128,7 +128,7 @@ describe('build meta dev', () => {
'pages/api/test.js',
'public/index',
'public/data.txt',
'package.json'
'package.json',
]);
childProcesses.forEach(cp => cp.kill());
});

View File

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

View File

@@ -26,6 +26,7 @@ import {
shouldServe,
Config,
debug,
isSymbolicLink,
} from '@now/build-utils';
export { shouldServe };
export { NowRequest, NowResponse } from './types';
@@ -55,13 +56,6 @@ const BRIDGE_FILENAME = '___now_bridge';
const HELPERS_FILENAME = '___now_helpers';
const SOURCEMAP_SUPPORT_FILENAME = '__sourcemap_support';
const S_IFMT = 61440; /* 0170000 type of file */
const S_IFLNK = 40960; /* 0120000 symbolic link */
function isSymbolicLink(mode: number): boolean {
return (mode & S_IFMT) === S_IFLNK;
}
async function downloadInstallAndBundle({
files,
entrypoint,
@@ -69,10 +63,7 @@ async function downloadInstallAndBundle({
config,
meta,
}: DownloadOptions) {
debug('Downloading user files...');
const downloadTime = Date.now();
const downloadedFiles = await download(files, workPath, meta);
debug(`download complete [${Date.now() - downloadTime}ms]`);
console.log('Installing dependencies...');
const installTime = Date.now();
@@ -393,10 +384,9 @@ export async function build({
return { output: lambda, watch };
}
export async function prepareCache({ workPath }: PrepareCacheOptions) {
return {
...(await glob('node_modules/**', workPath)),
...(await glob('package-lock.json', workPath)),
...(await glob('yarn.lock', workPath)),
};
export async function prepareCache({
workPath,
}: PrepareCacheOptions): Promise<Files> {
const cache = await glob('node_modules/**', workPath);
return cache;
}

View File

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

View File

@@ -77,7 +77,6 @@ export const build = async ({
meta = {},
config,
}: BuildOptions) => {
debug('Downloading user files...');
let downloadedFiles = await download(originalFiles, workPath, meta);
if (meta.isDev) {

View File

@@ -1,6 +1,6 @@
{
"name": "@now/routing-utils",
"version": "1.3.4-canary.2",
"version": "1.4.0",
"description": "ZEIT Now routing utilities",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -20,7 +20,7 @@
"test-unit": "jest --env node --verbose --runInBand"
},
"dependencies": {
"path-to-regexp": "3.1.0"
"path-to-regexp": "6.1.0"
},
"devDependencies": {
"ajv": "^6.0.0",

View File

@@ -17,6 +17,7 @@ import {
} from './superstatic';
export { getCleanUrls } from './superstatic';
export { mergeRoutes } from './merge';
export function isHandler(route: Route): route is Handler {
return typeof (route as Handler).handle !== 'undefined';

Some files were not shown because too many files have changed in this diff Show More