Compare commits

...

79 Commits

Author SHA1 Message Date
Andy Bitz
4300882d12 Publish
- @now/build-utils@0.8.8
2019-07-16 19:30:24 +02:00
Andy
0711e094f3 [now-build-utils] Fix file order (#772)
* [now-build-utils] Fix file order

* Use localeCompare
2019-07-16 19:30:08 +02:00
Andy Bitz
4ec1883262 Publish
- @now/build-utils@0.8.7
2019-07-16 17:25:37 +02:00
Andy
cb53db4157 [now-build-utils] Ignore more default routes and builds (#771)
* [now-build-utils] Ignore more default routes

* Don't export

* Update packages/now-build-utils/src/detect-builder.ts

Co-Authored-By: Steven <steven@ceriously.com>

* Update packages/now-build-utils/src/detect-builder.ts

Co-Authored-By: Steven <steven@ceriously.com>
2019-07-16 17:25:15 +02:00
Andy Bitz
49aea85638 Publish
- @now/build-utils@0.8.6
 - @now/static-build@0.7.2
2019-07-16 11:43:43 +02:00
Andy
3971b5a8cf [now-static-build] Remove default routes for now dev (#769)
* [now-static-build] Remove default routes for now dev

* Update packages/now-static-build/src/index.ts

Co-Authored-By: Leo Lamprecht <mindrun@icloud.com>
2019-07-16 11:41:41 +02:00
Igor Klopov
958946d01a Publish
- @now/build-utils@0.8.5
2019-07-13 22:01:47 +03:00
Igor Klopov
f3d284476f remove mutex because it is not reentrant and hangs on sub-invocation (#758) 2019-07-13 22:00:17 +03:00
Andy Bitz
6076b12067 Publish
- @now/build-utils@0.8.4
 - @now/static-build@0.7.1
2019-07-12 06:35:38 +02:00
Andy
5c7eba6049 [now-static-build] Fix warning and framework support (#750)
* [now-static-build] Fix template string

* [now-static-build] Handle different dist dir for Angular

* Fix type issue

* Fix hexo test

* Log more

* Change hexo test

* Adjust Hexo test

* Removed extra logging

* Continue with prev test

* Use @now/node@canary

* Fix tests

* Increase timeout

* Increase timeout

* Add framework

* Reset timeout for now-build-utils

* Increase timeout

* Adjust test
2019-07-12 06:34:03 +02:00
Andy Bitz
3ed99a5b25 Publish
- @now/next@0.5.4
2019-07-10 23:43:45 +02:00
Andy
f8b08926f2 [now-next] Create a serverless config by creating a new config (#688)
* [now-next] Create a serverless config by creating a new config

* Adjust config for promises

* Add tests and fixes

* Fix the tests

* Always create the config for non-dev mode

* Removed unused version

* Handle promises

* Adjust test

* Removed semver check

* Removed import

* Try tests

* Update tests

* Log results

* Add more logging

* More logging

* Fixed typo and removed logging
2019-07-10 23:43:03 +02:00
Andy Bitz
354e80b3e3 Publish
- @now/build-utils@0.8.3
2019-07-10 17:47:53 +02:00
Andy Bitz
0cfb4a8466 Revert "Remove deprecated builders (#733)"
This reverts commit 6766e9a099.
2019-07-10 17:44:31 +02:00
Andy Bitz
8facd2845f Revert "[now-node-server] Remove package source code (#721)"
This reverts commit d327426c93.
2019-07-10 17:44:28 +02:00
Andy Bitz
9dae464fa6 Revert "Publish"
This reverts commit 6ff4b25d79.
2019-07-10 17:43:42 +02:00
Andy Bitz
6ff4b25d79 Publish
- @now/build-utils@0.8.3
 - @now/node@0.11.2
2019-07-10 17:39:49 +02:00
Steven
6766e9a099 Remove deprecated builders (#733)
* Remove deprecated builders

* Update comment so that tests  run
2019-07-10 17:38:59 +02:00
Steven
d327426c93 [now-node-server] Remove package source code (#721)
* [now-node-server] Remove package

* Update comment so tests run
2019-07-10 17:36:07 +02:00
Andy Bitz
47f73c856f Revert "Remove deprecated builders (#733)"
This reverts commit 9d15c35623.
2019-07-10 17:34:33 +02:00
Andy Bitz
9fdd247773 Revert "[now-node-server] Remove package source code (#721)"
This reverts commit a55ce5da8f.
2019-07-10 17:34:31 +02:00
Andy Bitz
fe59cabf15 Revert "Publish"
This reverts commit da37a9bc06.
2019-07-10 15:30:00 +02:00
Andy Bitz
da37a9bc06 Publish
- @now/build-utils@0.8.3
 - @now/node@0.11.2
2019-07-10 15:19:34 +02:00
Andy
bad779be1f Use --pure-lockfile on build (#738)
* Use --pure-lockfile on build

* Add --pure-lockfile to publish
2019-07-10 15:17:31 +02:00
Andy Bitz
5eb5deb8eb Revert "Publish"
This reverts commit f9afee7dba.
2019-07-10 15:09:42 +02:00
Andy Bitz
f9afee7dba Publish
- @now/build-utils@0.8.3
 - @now/node@0.11.2
2019-07-10 14:28:37 +02:00
Andy
547e9ed684 Add --pure-lockfile option (#737) 2019-07-10 14:26:31 +02:00
Andy Bitz
68d2927cbf Revert "Publish"
This reverts commit ca7f716432.
2019-07-10 14:16:22 +02:00
Andy Bitz
ca7f716432 Publish
- @now/build-utils@0.8.3
 - @now/node@0.11.2
2019-07-10 14:02:12 +02:00
Steven
9d15c35623 Remove deprecated builders (#733)
* Remove deprecated builders

* Update comment so that tests  run
2019-07-10 13:51:37 +02:00
Steven
a55ce5da8f [now-node-server] Remove package source code (#721)
* [now-node-server] Remove package

* Update comment so tests run
2019-07-10 13:49:40 +02:00
Connor Davis
f0a06b797e Update CODEOWNERS 2019-07-10 13:46:33 +02:00
Andy
0a77f43832 [now-build-utils] Add warnings and simpler routes (#734)
* [now-build-utils] Add warnings

* Make routes regexes simpler

* Adjust tests

* Adjust tests

* Fix tests

* Remove now-build script check
2019-07-10 13:46:13 +02:00
Andy Bitz
4ce0d31688 Publish
- @now/bash@1.0.1
 - @now/go@0.5.5
 - @now/python@0.2.11
2019-07-09 01:09:23 +02:00
Andy
280802615f [now-bash][now-go][now-python] Support zero config (#723)
* [now-bash][now-go][now-python] Support zero config

* Remove default config
2019-07-09 01:01:27 +02:00
Andy Bitz
c7db281065 Publish
- @now/build-utils@0.8.2
2019-07-08 17:14:04 +02:00
Andy
ca00739041 [now-build-utils] Fix default routes on windows and adjust error message (#720)
* [now-build-utils] Fix default routes on windows and adjust error message

* Remove extra condition
2019-07-08 17:13:29 +02:00
Andy Bitz
11f8b17599 Publish
- @now/build-utils@0.8.1
2019-07-08 12:40:23 +02:00
Andy Bitz
970ab7d5c5 Revert "Fixed yarn.lock (#717)"
This reverts commit 5bfbd63e13.
2019-07-08 12:39:19 +02:00
Andy Bitz
71c082dccd Revert "Publish"
This reverts commit 76a185eb90.
2019-07-08 12:34:31 +02:00
Andy Bitz
76a185eb90 Publish
- @now/build-utils@0.8.1
2019-07-08 12:23:30 +02:00
Andy
8c0f3d107d [now-build-utils] Ignore dot and underscore prefix for api directory (#718)
* [now-build-utils] Add support for `_` and `.` prefixes in `api/`

 * Remove log statement

* Ignore dot and underscore prefix for api directory

* Set one builder per file if there are files to ignore

* Always return all builds with their exact match
2019-07-08 12:22:40 +02:00
Leo Lamprecht
5bfbd63e13 Fixed yarn.lock (#717)
This reverts commit d83b09ffbd.
2019-07-07 16:00:20 +00:00
Leo Lamprecht
f7bc3b3490 Publish
- @now/bash@1.0.0
 - @now/build-utils@0.8.0
 - @now/go@0.5.4
 - @now/html-minifier@1.1.4
 - @now/md@0.5.5
 - @now/mdx-deck@0.5.5
 - @now/next@0.5.3
 - @now/node-server@0.8.2
 - @now/node@0.11.1
 - @now/optipng@0.6.4
 - @now/php@0.5.6
 - @now/python@0.2.10
 - @now/ruby@0.1.2
 - @now/rust@0.2.8
 - @now/static-build@0.7.0
2019-07-07 15:54:56 +00:00
Leo Lamprecht
d83b09ffbd Revert "Fixed yarn.lock (#717)"
This reverts commit 0c120f6202.
2019-07-07 15:53:06 +00:00
Leo Lamprecht
0c120f6202 Fixed yarn.lock (#717) 2019-07-07 14:54:41 +00:00
Leo Lamprecht
48a01415f8 Complete cleanup (#716) 2019-07-07 16:48:28 +02:00
Leo Lamprecht
9198b72382 Revert "Bumps ncc to 0.20.x (#641)"
This reverts commit cd45dce724405968e9e6f58ae4fad983c5d35f20.
2019-07-07 14:28:26 +00:00
Andy
79048b83de [now-build-utils] Add methods to detect builders and routes from files and package.json (#705)
* [now-build-utils] Add methods to detect builders and routes from files
and package.json

* Update packages/now-build-utils/src/detect-routes.ts

Co-Authored-By: Steven <steven@ceriously.com>

* Update packages/now-build-utils/src/detect-routes.ts

Co-Authored-By: Steven <steven@ceriously.com>

* Update packages/now-build-utils/src/detect-routes.ts

Co-Authored-By: Steven <steven@ceriously.com>

* Added tests for new functions

* Adjusted tests

* Tests

* Tests

* Fix routes detection

* Fix routes and tests

* Update concatArrayOfText
2019-07-07 13:30:53 +00:00
Leo Lamprecht
f798e2cf19 [now-static-build] Optimize more frameworks out of the box (#704)
* Added Preact CLI

* Added Hexo

* Added Aurelia

* Correctly check for projects

* Added Gridsome

* Added UmiJS

* Added Polymer

* Added Polymer tests

* Better umi test

* Correctly test for 404

* Make Polymer tests work perfectly

* Removed aurelia

* Removed tests from preact

* Removed aurelia from the framework list

* Removed broken probe from hexo
2019-07-07 13:30:48 +00:00
Steven
be5057a738 [now-static-build] Add support for Node 10 in shell scripts (#708) 2019-07-07 13:30:44 +00:00
Steven
059d44fde7 [now-build-utils] Add shell script args / opts (#707)
* [now-build-utils] Add shell script args / opts

* Remove dead code
2019-07-07 13:30:40 +00:00
Igor Klopov
11ad481546 [now-build-utils] Add option to skip download + yarn mutex (#700)
* dontDownload in Meta

* yarn --mutex network

* dontDownload -> skipDownload
2019-07-07 13:30:34 +00:00
Luc
4061ed2eb7 [now-node] Make types.d.ts the main types export (#706) 2019-07-07 13:30:30 +00:00
dependabot[bot]
b54c79e619 Bump fstream from 1.0.11 to 1.0.12 (#702)
Bumps [fstream](https://github.com/npm/fstream) from 1.0.11 to 1.0.12.
- [Release notes](https://github.com/npm/fstream/releases)
- [Commits](https://github.com/npm/fstream/compare/v1.0.11...v1.0.12)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-07 13:30:24 +00:00
Luc
2203ae1a20 [now-node] Reduce risks of conflict with entrypoint's name (#626)
* helper -> ___help3rs , bridge -> ___bridg3

* launcher -> ___launch3r

* use const to configure filenames

* revert yarn.lock

* add `now` in file names

Co-Authored-By: Steven <steven@ceriously.com>

* add test to prevent regression

* remove 3 in filenames

* rename fixtures folder

* Apply suggestions from code review

Co-Authored-By: Steven <steven@ceriously.com>

* fix errors
2019-07-07 13:30:16 +00:00
Steven
5e58c1738b Add lucleray to codeowners (#695) 2019-07-07 13:30:12 +00:00
Luc
290577ddfb [now-node] Benchmark helpers (#613)
* add bench

* remove yarn.lock

* refactor and extract launcher logic

* add entrypoints to bench

* Update packages/now-node/src/launcher.ts

Co-Authored-By: Steven <steven@ceriously.com>
2019-07-07 13:30:08 +00:00
Steven
a5e651403e [now-node] Add support for server.listen() (#691)
* [now-node] Add support for listener

* Fix listener

* Add hapi-async test

* Add timeout for listening

* Add additional case for invalid export

* Increase timeout to 5 seconds
2019-07-07 13:30:03 +00:00
ywg-jean
df2769717b Bumps ncc to 0.20.x (#641)
* bumps ncc to 0.20.0

* upgrades to ncc@0.20.2

this includes the fixes for https://github.com/zeit/ncc/issues/434

* defines filterAssetBase when calling ncc

This prevents assets from being evicted by ncc's filtering.

* scopes includeFiles under the package root

it was previously scoped under the directory of the input file currently
being processed.
This should fix #522 along with fixing the 09-include-files tests for
now-node

* Bump to ncc 0.20.3

* Bump ncc-watcher to 1.1.0
2019-07-07 13:29:57 +00:00
Tim Craft
880ef77b7b [now-ruby] Use BUNDLE_JOBS=4 for parallel Ruby gem downloads (#689) 2019-07-07 13:29:23 +00:00
Tim Craft
af2616c283 [now-ruby] Use ruby 2.5.5 (#690) 2019-07-07 13:29:19 +00:00
Steven
4300f4d797 Add homepage to all packages (#685) 2019-07-07 13:29:13 +00:00
Nathan Rajlich
4921e541af [now-node] Use the local version of node when running via now dev (#683)
This is a companion to https://github.com/zeit/now-cli/pull/2480.
2019-07-07 13:29:07 +00:00
Leo Lamprecht
6ce00eda8c [now-static-build] Optimize static frameworks (#696)
* Optimize frameworks in @now/static-build

* Make it shorter

* Disable build.sh for zero config

* Adjust error message according to zero config prop

* Renamed wrong fixtures

* Added Gatsby fixture

* Ignore static-build fixtures when linting

* Fix test

* Support Svelte

* Added Create React app and Svelte defaults

* Added comma

* Merge routes properly

* Removed test file

* Polished test

* Fixed types

* Added type

* Added correct type

* Added default routes for Svelte

* Extended route type

* Temporarily removed check

* Revert "Temporarily removed check"

This reverts commit bb5e2843eca60753228d499efbb6dd4aa1c28c5b.

* Enable routes

* Added package.json type

* Added Framework type

* Added Route type

* Increase size

* Export Route type

* Added Vue

* Fix test

* Fixed all tests

* Added Angular app

* Removed garbage

* Ignore ts stuff

* Added type

* Added type for function

* Made it async

* Require minimum node version

* Push pretter polishment

* Add support for node version

* Make minNodeVersion optional

* Make node version silent

* Added optional type

* Log everything

* Fixed tests

* Changed version

* Pushed Node.js version update

* Update packages/now-build-utils/src/fs/node-version.ts

Co-Authored-By: Andy <AndyBitz@users.noreply.github.com>

* Cleaner syntax

* Silence more messages

* Hide more text

* Update packages/now-build-utils/src/fs/run-user-scripts.ts

Co-Authored-By: Steven <steven@ceriously.com>

* Update packages/now-build-utils/src/fs/run-user-scripts.ts

Co-Authored-By: Steven <steven@ceriously.com>
2019-07-07 13:27:38 +00:00
Andy Bitz
060e952f6c Publish
- @now/build-utils@0.7.4
2019-07-04 22:58:06 +02:00
Andy Bitz
1639127b24 Publish
- @now/next@0.5.2
2019-07-01 18:45:01 +02:00
Joe Haddad
ebbb62eaea [now-next] Reorder process overrides (#682)
* Reorder process overrides

* Remove next.config.js to test the feature

* Correct src

* Fix more broken code

* Fix tests
2019-07-01 18:44:43 +02:00
Andy Bitz
66954e84fe Publish
- @now/bash@0.3.0
 - @now/build-utils@0.7.3
 - @now/node@0.11.0
 - @now/ruby@0.1.1
2019-07-01 13:10:01 +02:00
Andy
15a21bb28c [now-bash] Add import config prop (#681)
* [now-bash] Add imports config prop

* Fixed type

* Rename to import
2019-07-01 13:08:18 +02:00
Nathan Rajlich
96ca1e1d8c [now-node] Watch consumed assets when running in now dev (#612)
Watch assets that `ncc` reports as part of the compiled bundle.

For example, "pug" template files:

```js
app.set("views", path.join(__dirname, "../../", "/views/"));
```
2019-07-01 13:07:47 +02:00
Luc
587cb52191 [now-node] Add support for server instance exports (#657)
* check for listen method on export

* revert setting helpers on __proto__

* fix launcher

* add missing semicolon

* fix missing bridge parameter

* add server test fixture

* move express test fixtures to servers

* add missing entrypoints in fixtures

* temporary fix before we update node-bridge

* refactor express test fixture

* add fixtures for hapi, fastify, koa

* fix now.json in servers fixtures

* remove fastify as it is not yet supported

* remove fs-extra as a dependency
2019-07-01 13:07:38 +02:00
Luc
95422ffd46 [now-node] Make helpers 100% match expressjs API (#678)
* remove Stream support

* text/plain -> text/html

* add etags

* bring test suite from expressjs

* add TODO comment

* remove body for 204 and 304

* do not send body when req.method is HEAD

* fix tests

* lazy load etag

* add type safeguards

* avoid type casting
2019-07-01 13:07:33 +02:00
Steven
391a883799 [docs] Add codeowners for ruby (#676) 2019-07-01 13:07:26 +02:00
Steven
43d6960df4 [now-ruby] Move typescript to devDependency (#675) 2019-07-01 13:07:06 +02:00
Andy Bitz
5c128003d8 Publish
- @now/build-utils@0.7.2
 - @now/static-build@0.6.2
2019-06-30 00:30:37 +02:00
Andy
2f8fd1b14b [now-static-build] Default to now-dev when zeroConfig is false (#679)
* [now-static-build] Default to `now-dev` when `zeroConfig` is false

* Adjust tests

* Fix build

* Return nowCmd

* Adjusted type

* Changed type

* Removed type

* Cast type

* [now-build-utils] Export config
2019-06-30 00:30:00 +02:00
Andy Bitz
625553c146 Publish
- @now/build-utils@0.7.1
2019-06-29 23:12:38 +02:00
Andy
3b0ce7bad3 [now-builds-util] Add zeroConfig to config type (#680) 2019-06-29 23:11:41 +02:00
412 changed files with 87509 additions and 590 deletions

View File

@@ -14,3 +14,4 @@
/packages/now-rust/dist/*
/packages/now-ruby/dist/*
/packages/now-static-build/dist/*
/packages/now-static-build/test/fixtures/**

14
.github/CODEOWNERS vendored
View File

@@ -1,9 +1,11 @@
# Documentation
# https://help.github.com/en/articles/about-code-owners
* @styfle
/packages/now-node @styfle @tootallnate
/packages/now-next @timer @dav-is
/packages/now-go @styfle @sophearak
/packages/now-python @styfle @sophearak
/packages/now-rust @styfle @mike-engel @anmonteiro
* @styfle
/packages/now-node @styfle @tootallnate @lucleray
/packages/now-node-bridge @styfle @tootallnate @lucleray
/packages/now-next @timer
/packages/now-go @styfle @sophearak
/packages/now-python @styfle @sophearak
/packages/now-rust @styfle @mike-engel @anmonteiro
/packages/now-ruby @styfle @coetry @nathancahill

12
.github/main.workflow vendored
View File

@@ -18,21 +18,21 @@ action "1. Canary yarn install" {
uses = "actions/npm@59b64a598378f31e49cb76f27d6f3312b582f680"
needs = ["0. Canary PR not deleted"]
runs = "yarn"
args = "install"
args = "--pure-lockfile install"
}
action "2. Canary yarn run build" {
uses = "actions/npm@59b64a598378f31e49cb76f27d6f3312b582f680"
needs = ["1. Canary yarn install"]
runs = "yarn"
args = "run build"
args = "--pure-lockfile run build"
}
action "3. Canary yarn run publish" {
uses = "actions/npm@59b64a598378f31e49cb76f27d6f3312b582f680"
needs = ["2. Canary yarn run build"]
runs = "yarn"
args = "run publish-from-github"
args = "--pure-lockfile run publish-from-github"
secrets = ["NPM_TOKEN"]
}
@@ -57,20 +57,20 @@ action "1. Master yarn install" {
uses = "actions/npm@59b64a598378f31e49cb76f27d6f3312b582f680"
needs = ["0. Master PR not deleted"]
runs = "yarn"
args = "install"
args = "--pure-lockfile install"
}
action "2. Master yarn run build" {
uses = "actions/npm@59b64a598378f31e49cb76f27d6f3312b582f680"
needs = ["1. Master yarn install"]
runs = "yarn"
args = "run build"
args = "--pure-lockfile run build"
}
action "3. Master yarn run publish" {
uses = "actions/npm@59b64a598378f31e49cb76f27d6f3312b582f680"
needs = ["2. Master yarn run build"]
runs = "yarn"
args = "run publish-from-github"
args = "--pure-lockfile run publish-from-github"
secrets = ["NPM_TOKEN"]
}

View File

@@ -10,9 +10,18 @@ const {
} = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
exports.config = {
maxLambdaSize: '10mb',
maxLambdaSize: '30mb',
};
// From this list: https://import.pw/importpw/import/docs/config.md
const allowedConfigImports = new Set([
'CACHE',
'CURL_OPTS',
'DEBUG',
'RELOAD',
'SERVER',
]);
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
exports.build = async ({
@@ -24,10 +33,23 @@ exports.build = async ({
await download(files, srcDir);
const configEnv = Object.keys(config).reduce((o, v) => {
o[`IMPORT_${snakeCase(v).toUpperCase()}`] = config[v]; // eslint-disable-line no-param-reassign
const name = snakeCase(v).toUpperCase();
if (allowedConfigImports.has(name)) {
o[`IMPORT_${name}`] = config[v]; // eslint-disable-line no-param-reassign
}
return o;
}, {});
if (config && config.import) {
Object.keys(config.import).forEach((key) => {
const name = snakeCase(key).toUpperCase();
// eslint-disable-next-line no-param-reassign
configEnv[`IMPORT_${name}`] = config.import[key];
});
}
const IMPORT_CACHE = `${workPath}/.import-cache`;
const env = Object.assign({}, process.env, configEnv, {
PATH: `${IMPORT_CACHE}/bin:${process.env.PATH}`,

View File

@@ -1,10 +1,11 @@
{
"name": "@now/bash",
"version": "0.2.3",
"version": "1.0.1",
"description": "Now 2.0 builder for HTTP endpoints written in Bash",
"main": "index.js",
"author": "Nathan Rajlich <nate@zeit.co>",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/bash-now-bash",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,9 +1,10 @@
{
"name": "@now/build-utils",
"version": "0.7.0",
"version": "0.8.8",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
"homepage": "https://zeit.co/docs/v2/deployments/builders/developer-guide",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",
@@ -22,6 +23,7 @@
"fs-extra": "7.0.0",
"glob": "7.1.3",
"into-stream": "5.0.0",
"minimatch": "3.0.4",
"multistream": "2.1.1",
"node-fetch": "2.2.0",
"semver": "6.1.1",

View File

@@ -0,0 +1,110 @@
import { PackageJson, Builder, Config } from './types';
import minimatch from 'minimatch';
const src: string = 'package.json';
const config: Config = { zeroConfig: true };
// Static builders are special cased in `@now/static-build`
const BUILDERS = new Map<string, Builder>([
['next', { src, use: '@now/next', config }],
]);
const API_BUILDERS: Builder[] = [
{ src: 'api/**/*.js', use: '@now/node@canary', config },
{ src: 'api/**/*.ts', use: '@now/node@canary', config },
{ src: 'api/**/*.rs', use: '@now/rust', config },
{ src: 'api/**/*.go', use: '@now/go', config },
{ src: 'api/**/*.php', use: '@now/php', config },
{ src: 'api/**/*.py', use: '@now/python', config },
{ src: 'api/**/*.rb', use: '@now/ruby', config },
{ src: 'api/**/*.sh', use: '@now/bash', config },
];
interface Warning {
code: string;
message: string;
}
export async function detectBuilder(
pkg: PackageJson
): Promise<{
builder: null | Builder;
warnings: null | Warning[];
}> {
let warnings: null | Warning[] = null;
const scripts = pkg.scripts || {};
if (!scripts.build) {
warnings = [
{
code: 'missing_build_script',
message:
'Your `package.json` file is missing a `build` property inside the `script` property',
},
];
}
for (const [dependency, builder] of BUILDERS) {
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
// Return the builder when a dependency matches
if (deps[dependency]) {
return { builder, warnings };
}
}
// If there is no `build` and `now-build` script
// we'll not select `@now/static-build`
// since it would fail
if (!scripts.build) {
return { builder: null, warnings };
}
// By default we'll choose the `static-build` builder
const builder = { src, use: '@now/static-build', config };
return { builder, warnings };
}
// Files that match a specific pattern will get ignored
export function ignoreApiFilter(file: string) {
if (file.includes('/.')) {
return false;
}
if (file.includes('/_')) {
return false;
}
// If the file does not match any builder we also
// don't want to create a route e.g. `package.json`
if (API_BUILDERS.every(({ src }) => !minimatch(file, src))) {
return false;
}
return true;
}
// We need to sort the file paths by alphabet to make
// sure the routes stay in the same order e.g. for deduping
export function sortFiles(fileA: string, fileB: string) {
return fileA.localeCompare(fileB);
}
export async function detectApiBuilders(
files: string[]
): Promise<Builder[] | null> {
const builds = files
.sort(sortFiles)
.filter(ignoreApiFilter)
.map(file => {
const result = API_BUILDERS.find(
({ src }): boolean => minimatch(file, src)
);
return result ? { ...result, src: file } : null;
});
const finishedBuilds = builds.filter(Boolean);
return finishedBuilds.length > 0 ? (finishedBuilds as Builder[]) : null;
}

View File

@@ -0,0 +1,236 @@
import { Route } from './types';
import { parse as parsePath } from 'path';
import { ignoreApiFilter, sortFiles } from './detect-builder';
function joinPath(...segments: string[]) {
const joinedPath = segments.join('/');
return joinedPath.replace(/\/{2,}/g, '/');
}
function concatArrayOfText(texts: string[]): string {
if (texts.length <= 2) {
return texts.join(' and ');
}
const last = texts.pop();
return `${texts.join(', ')}, and ${last}`;
}
// Takes a filename or foldername, strips the extension
// gets the part between the "[]" brackets.
// It will return `null` if there are no brackets
// and therefore no segment.
function getSegmentName(segment: string): string | null {
const { name } = parsePath(segment);
if (name.startsWith('[') && name.endsWith(']')) {
return name.slice(1, -1);
}
return null;
}
function createRouteFromPath(filePath: string): Route {
const parts = filePath.split('/');
let counter: number = 1;
const query: string[] = [];
const srcParts = parts.map(
(segment, index): string => {
const name = getSegmentName(segment);
const isLast = index === parts.length - 1;
if (name !== null) {
// We can't use `URLSearchParams` because `$` would get escaped
query.push(`${name}=$${counter++}`);
return `([^\\/]+)`;
} else if (isLast) {
const { name: fileName } = parsePath(segment);
return fileName;
}
return segment;
}
);
const src = `^/${srcParts.join('/')}$`;
const dest = `/${filePath}${query.length ? '?' : ''}${query.join('&')}`;
return { src, dest };
}
// Check if the path partially matches and has the same
// name for the path segment at the same position
function partiallyMatches(pathA: string, pathB: string): boolean {
const partsA = pathA.split('/');
const partsB = pathB.split('/');
const long = partsA.length > partsB.length ? partsA : partsB;
const short = long === partsA ? partsB : partsA;
let index = 0;
for (const segmentShort of short) {
const segmentLong = long[index];
const nameLong = getSegmentName(segmentLong);
const nameShort = getSegmentName(segmentShort);
// If there are no segments or the paths differ we
// return as they are not matching
if (segmentShort !== segmentLong && (!nameLong || !nameShort)) {
return false;
}
if (nameLong !== nameShort) {
return true;
}
index += 1;
}
return false;
}
// Counts how often a path occurres when all placeholders
// got resolved, so we can check if they have conflicts
function pathOccurrences(filePath: string, files: string[]): string[] {
const getAbsolutePath = (unresolvedPath: string): string => {
const { dir, name } = parsePath(unresolvedPath);
const parts = joinPath(dir, name).split('/');
return parts.map(part => part.replace(/\[.*\]/, '1')).join('/');
};
const currentAbsolutePath = getAbsolutePath(filePath);
return files.reduce((prev: string[], file: string): string[] => {
const absolutePath = getAbsolutePath(file);
if (absolutePath === currentAbsolutePath) {
prev.push(file);
} else if (partiallyMatches(filePath, file)) {
prev.push(file);
}
return prev;
}, []);
}
// Checks if a placeholder with the same name is used
// multiple times inside the same path
function getConflictingSegment(filePath: string): string | null {
const segments = new Set<string>();
for (const segment of filePath.split('/')) {
const name = getSegmentName(segment);
if (name !== null && segments.has(name)) {
return name;
}
if (name) {
segments.add(name);
}
}
return null;
}
function sortFilesBySegmentCount(fileA: string, fileB: string): number {
const lengthA = fileA.split('/').length;
const lengthB = fileB.split('/').length;
if (lengthA > lengthB) {
return -1;
}
if (lengthA < lengthB) {
return 1;
}
// Paths that have the same segment length but
// less placeholders are preferred
const countSegments = (prev: number, segment: string) =>
getSegmentName(segment) ? prev + 1 : 0;
const segmentLengthA = fileA.split('/').reduce(countSegments, 0);
const segmentLengthB = fileB.split('/').reduce(countSegments, 0);
if (segmentLengthA > segmentLengthB) {
return 1;
}
if (segmentLengthA < segmentLengthB) {
return -1;
}
return 0;
}
export async function detectApiRoutes(
files: string[]
): Promise<{
defaultRoutes: Route[] | null;
error: { [key: string]: string } | null;
}> {
if (!files || files.length === 0) {
return { defaultRoutes: null, error: null };
}
// The deepest routes need to be
// the first ones to get handled
const sortedFiles = files
.filter(ignoreApiFilter)
.sort(sortFiles)
.sort(sortFilesBySegmentCount);
const defaultRoutes: Route[] = [];
for (const file of sortedFiles) {
// We only consider every file in the api directory
// as we will strip extensions as well as resolving "[segments]"
if (!file.startsWith('api/')) {
continue;
}
const conflictingSegment = getConflictingSegment(file);
if (conflictingSegment) {
return {
defaultRoutes: null,
error: {
code: 'conflicting_path_segment',
message:
`The segment "${conflictingSegment}" occurres more than ` +
`one time in your path "${file}". Please make sure that ` +
`every segment in a path is unique`,
},
};
}
const occurrences = pathOccurrences(file, sortedFiles).filter(
name => name !== file
);
if (occurrences.length > 0) {
const messagePaths = concatArrayOfText(
occurrences.map(name => `"${name}"`)
);
return {
defaultRoutes: null,
error: {
code: 'conflicting_file_path',
message:
`Two or more files have conflicting paths or names. ` +
`Please make sure path segments and filenames, without their extension, are unique. ` +
`The path "${file}" has conflicts with ${messagePaths}`,
},
};
}
defaultRoutes.push(createRouteFromPath(file));
}
return { defaultRoutes, error: null };
}

View File

@@ -39,10 +39,10 @@ export default async function download(
basePath: string,
meta?: Meta
): Promise<DownloadedFiles> {
const { isDev = false, filesChanged = null, filesRemoved = null } =
const { isDev = false, skipDownload = false, filesChanged = null, filesRemoved = null } =
meta || {};
if (isDev) {
if (isDev || skipDownload) {
// In `now dev`, the `download()` function is a no-op because
// the `basePath` matches the `cwd` of the dev server, so the
// source files are already available.

View File

@@ -13,15 +13,18 @@ export const defaultSelection = supportedOptions.find(
) as NodeVersion;
export async function getSupportedNodeVersion(
engineRange?: string
engineRange?: string,
silent?: boolean
): Promise<NodeVersion> {
let selection = defaultSelection;
if (!engineRange) {
console.log(
'missing `engines` in `package.json`, using default range: ' +
selection.range
);
if (!silent) {
console.log(
'missing `engines` in `package.json`, using default range: ' +
selection.range
);
}
} else {
const found = supportedOptions.some(o => {
// the array is already in order so return the first
@@ -30,15 +33,20 @@ export async function getSupportedNodeVersion(
return intersects(o.range, engineRange);
});
if (found) {
console.log(
'found `engines` in `package.json`, selecting range: ' + selection.range
);
if (!silent) {
console.log(
'Found `engines` in `package.json`, selecting range: ' +
selection.range
);
}
} else {
throw new Error(
'found `engines` in `package.json` with an unsupported node range: ' +
engineRange +
'\nplease use `10.x` or `8.10.x` instead'
);
if (!silent) {
throw new Error(
'found `engines` in `package.json` with an unsupported node range: ' +
engineRange +
'\nplease use `10.x` or `8.10.x` instead'
);
}
}
}
return selection;

View File

@@ -46,11 +46,15 @@ async function chmodPlusX(fsPath: string) {
await fs.chmod(fsPath, base8);
}
export async function runShellScript(fsPath: string) {
export async function runShellScript(
fsPath: string,
args: string[] = [],
spawnOpts?: SpawnOptions
) {
assert(path.isAbsolute(fsPath));
const destPath = path.dirname(fsPath);
await chmodPlusX(fsPath);
await spawnAsync(`./${path.basename(fsPath)}`, [], destPath);
await spawnAsync(`./${path.basename(fsPath)}`, args, destPath, spawnOpts);
return true;
}
@@ -69,10 +73,15 @@ export function getSpawnOptions(
return opts;
}
export async function getNodeVersion(destPath: string): Promise<NodeVersion> {
export async function getNodeVersion(
destPath: string,
minNodeVersion?: string
): Promise<NodeVersion> {
const { packageJson } = await scanParentDirs(destPath, true);
const range = packageJson && packageJson.engines && packageJson.engines.node;
return getSupportedNodeVersion(range);
const range =
(packageJson && packageJson.engines && packageJson.engines.node) ||
minNodeVersion;
return getSupportedNodeVersion(range, typeof minNodeVersion !== 'undefined');
}
async function scanParentDirs(destPath: string, readPackageJson = false) {
@@ -130,7 +139,11 @@ export async function runNpmInstall(
} else {
await spawnAsync(
'yarn',
commandArgs.concat(['--ignore-engines', '--cwd', destPath]),
commandArgs.concat([
'--ignore-engines',
'--cwd',
destPath,
]),
destPath,
opts
);

View File

@@ -1,15 +1,6 @@
import FileBlob from './file-blob';
import FileFsRef from './file-fs-ref';
import FileRef from './file-ref';
import {
File,
Files,
AnalyzeOptions,
BuildOptions,
PrepareCacheOptions,
ShouldServeOptions,
Meta,
} from './types';
import { Lambda, createLambda } from './lambda';
import download, { DownloadedFiles } from './fs/download';
import getWriteableDirectory from './fs/get-writable-directory';
@@ -25,14 +16,13 @@ import {
} from './fs/run-user-scripts';
import streamToBuffer from './fs/stream-to-buffer';
import shouldServe from './should-serve';
import { detectBuilder, detectApiBuilders } from './detect-builder';
import { detectApiRoutes } from './detect-routes';
export {
FileBlob,
FileFsRef,
FileRef,
Files,
File,
Meta,
Lambda,
createLambda,
download,
@@ -47,9 +37,10 @@ export {
getNodeVersion,
getSpawnOptions,
streamToBuffer,
AnalyzeOptions,
BuildOptions,
PrepareCacheOptions,
ShouldServeOptions,
shouldServe,
detectBuilder,
detectApiBuilders,
detectApiRoutes,
};
export * from './types';

View File

@@ -15,8 +15,24 @@ export interface Files {
[filePath: string]: File;
}
export interface Route {
src?: string;
dest?: string;
handle?: string;
type?: string;
headers?: {
[key: string]: string;
};
}
export interface Config {
[key: string]: string | string[] | boolean | number | undefined;
[key: string]:
| string
| string[]
| boolean
| number
| { [key: string]: string }
| undefined;
maxLambdaSize?: string;
includeFiles?: string | string[];
bundle?: boolean;
@@ -24,10 +40,13 @@ export interface Config {
helpers?: boolean;
rust?: string;
debug?: boolean;
zeroConfig?: boolean;
import?: { [key: string]: string };
}
export interface Meta {
isDev?: boolean;
skipDownload?: boolean;
requestPath?: string;
filesChanged?: string[];
filesRemoved?: string[];
@@ -187,3 +206,9 @@ export interface NodeVersion {
range: string;
runtime: string;
}
export interface Builder {
use: string;
src: string;
config?: Config;
}

View File

@@ -4,7 +4,7 @@
{
"src": "index.js",
"use": "@now/node",
"config": { "maxLambdaSize": "15mb" }
"config": { "maxLambdaSize": "18mb" }
}
],
"probes": [{ "path": "/", "mustContain": "found:RANDOMNESS_PLACEHOLDER" }]

View File

@@ -16,6 +16,12 @@ const {
testDeployment,
} = require('../../../test/lib/deployment/test-deployment.js');
const {
detectBuilder,
detectApiBuilders,
detectApiRoutes,
} = require('../dist');
jest.setTimeout(4 * 60 * 1000);
const builderUrl = '@canary';
let buildUtilsUrl;
@@ -133,7 +139,7 @@ for (const fixture of fs.readdirSync(fixturesPath)) {
// few foreign tests
const buildersToTestWith = ['now-node-server', 'now-static-build'];
const buildersToTestWith = ['now-node', 'now-static-build'];
// eslint-disable-next-line no-restricted-syntax
for (const builder of buildersToTestWith) {
@@ -158,3 +164,110 @@ for (const builder of buildersToTestWith) {
}
}
}
it('Test `detectBuilder`', async () => {
{
const pkg = { dependencies: { next: '1.0.0' } };
const { builder, warnings } = await detectBuilder(pkg);
expect(builder.use).toBe('@now/next');
expect(warnings.length).toBe(1);
}
{
const pkg = { devDependencies: { next: '1.0.0' } };
const { builder } = await detectBuilder(pkg);
expect(builder.use).toBe('@now/next');
}
{
const pkg = {};
const { builder } = await detectBuilder(pkg);
expect(builder).toBe(null);
}
});
it('Test `detectApiBuilders`', async () => {
{
const files = ['package.json', 'api/user.js', 'api/team.js'];
const builders = await detectApiBuilders(files);
expect(builders[0].use).toBe('@now/node@canary');
}
{
const files = ['package.json', 'api/user.go', 'api/team.js'];
const builders = await detectApiBuilders(files);
expect(builders.some(({ use }) => use === '@now/go')).toBeTruthy();
expect(builders.some(({ use }) => use === '@now/node@canary')).toBeTruthy();
}
{
const files = ['package.json'];
const builders = await detectApiBuilders(files);
expect(builders).toBe(null);
}
{
const files = [
'api/users/[id].js',
'api/_utils/handler.js',
'api/users/.helper.js',
'api/teams/_helper.js',
];
const builders = await detectApiBuilders(files);
expect(builders.length).toBe(1);
}
});
it('Test `detectApiRoutes`', async () => {
{
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
const { defaultRoutes } = await detectApiRoutes(files);
expect(defaultRoutes.length).toBe(2);
expect(defaultRoutes[0].dest).toBe('/api/team.js');
expect(defaultRoutes[1].dest).toBe('/api/user.go');
}
{
const files = ['api/user.go', 'api/user.js'];
const { error } = await detectApiRoutes(files);
expect(error.code).toBe('conflicting_file_path');
}
{
const files = ['api/[user].go', 'api/[team]/[id].js'];
const { error } = await detectApiRoutes(files);
expect(error.code).toBe('conflicting_file_path');
}
{
const files = ['api/[team]/[team].js'];
const { error } = await detectApiRoutes(files);
expect(error.code).toBe('conflicting_path_segment');
}
{
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
const { defaultRoutes } = await detectApiRoutes(files);
expect(defaultRoutes.length).toBe(2);
}
{
const files = [
'api/_utils/handler.js',
'api/[endpoint]/.helper.js',
'api/[endpoint]/[id].js',
];
const builders = await detectApiBuilders(files);
expect(builders.length).toBe(1);
}
});

View File

@@ -120,7 +120,14 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
const parsedAnalyzed = JSON.parse(analyzed) as Analyzed;
if (meta.isDev) {
const base = dirname(downloadedFiles['now.json'].fsPath);
let base = null;
if (config && config.zeroConfig) {
base = workPath;
} else {
base = dirname(downloadedFiles['now.json'].fsPath);
}
const destNow = join(
base,
'.now',

View File

@@ -1,7 +1,8 @@
{
"name": "@now/go",
"version": "0.5.3",
"version": "0.5.5",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/go-now-go",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,7 +1,8 @@
{
"name": "@now/html-minifier",
"version": "1.1.3",
"version": "1.1.4",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/html-minifier-now-html-minifier",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,7 +1,8 @@
{
"name": "@now/md",
"version": "0.5.4",
"version": "0.5.5",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/markdown-now-md",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,7 +1,8 @@
{
"name": "@now/mdx-deck",
"version": "0.5.4",
"version": "0.5.5",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/mdx-deck-now-mdx-deck",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,8 +1,9 @@
{
"name": "@now/next",
"version": "0.5.1",
"version": "0.5.4",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/next-js-now-next",
"scripts": {
"build": "./getBridgeTypes.sh && tsc",
"test": "npm run build && jest",

View File

@@ -0,0 +1,51 @@
import path from 'path';
import fs from 'fs-extra';
function getCustomData(importName: string) {
return `
module.exports = function(...args) {
let original = require('./${importName}');
const finalConfig = {};
const target = { target: 'serverless' };
if (typeof original === 'function' && original.constructor.name === 'AsyncFunction') {
// AsyncFunctions will become promises
original = original(...args);
}
if (original instanceof Promise) {
// Special case for promises, as it's currently not supported
// and will just error later on
return original
.then((orignalConfig) => Object.assign(finalConfig, orignalConfig))
.then((config) => Object.assign(config, target));
} else if (typeof original === 'function') {
Object.assign(finalConfig, original(...args));
} else if (typeof original === 'object') {
Object.assign(finalConfig, original);
}
Object.assign(finalConfig, target);
return finalConfig;
}
`.trim();
}
function getDefaultData() {
return `module.exports = { target: 'serverless' };`;
}
export default async function createServerlessConfig(workPath: string) {
const configPath = path.join(workPath, 'next.config.js');
const backupConfigName = `next.config.original.${Date.now()}.js`;
const backupConfigPath = path.join(workPath, backupConfigName);
if (fs.existsSync(configPath)) {
await fs.rename(configPath, backupConfigPath);
await fs.writeFile(configPath, getCustomData(backupConfigName));
} else {
await fs.writeFile(configPath, getDefaultData());
}
}

View File

@@ -42,6 +42,7 @@ import {
getDynamicRoutes,
isDynamicRoute,
} from './utils';
import createServerlessConfig from './create-serverless-config';
interface BuildParamsMeta {
isDev: boolean | undefined;
@@ -174,23 +175,23 @@ export const build = async ({
console.log(`${name} Downloading user files...`);
await download(files, workPath, meta);
const nodeVersion = await getNodeVersion(entryPath);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
const pkg = await readPackageJson(entryPath);
const nextVersion = getNextVersion(pkg);
if (!meta.isDev) {
await createServerlessConfig(workPath);
}
const nodeVersion = await getNodeVersion(entryPath);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
if (!nextVersion) {
throw new Error(
'No Next.js version could be detected in "package.json". Make sure `"next"` is installed in "dependencies" or "devDependencies"'
);
}
process.env.__NEXT_BUILDER_EXPERIMENTAL_TARGET = 'serverless';
if (meta.isDev) {
// eslint-disable-next-line no-underscore-dangle
process.env.__NEXT_BUILDER_EXPERIMENTAL_DEBUG = 'true';
let childProcess: ChildProcess | undefined;
// If this is the initial build, we want to start the server
@@ -287,10 +288,9 @@ export const build = async ({
console.log('running user script...');
const memoryToConsume = Math.floor(os.totalmem() / 1024 ** 2) - 128;
const buildSpawnOptions = { ...spawnOpts };
const env = { ...buildSpawnOptions.env } as any;
const env = { ...spawnOpts.env } as any;
env.NODE_OPTIONS = `--max_old_space_size=${memoryToConsume}`;
await runPackageJsonScript(entryPath, 'now-build', buildSpawnOptions);
await runPackageJsonScript(entryPath, 'now-build', { ...spawnOpts, env });
if (isLegacy) {
console.log('running npm install --production...');
@@ -389,7 +389,7 @@ export const build = async ({
'now__launcher.js': new FileBlob({ data: launcher }),
},
handler: 'now__launcher.launcher',
runtime: 'nodejs8.10',
runtime: nodeVersion.runtime,
});
console.log(`Created lambda for page: "${page}"`);
})
@@ -474,7 +474,7 @@ export const build = async ({
'page.js': pages[page],
},
handler: 'now__launcher.launcher',
runtime: 'nodejs8.10',
runtime: nodeVersion.runtime,
});
console.log(`Created lambda for page: "${page}"`);
})

View File

@@ -1,5 +1,6 @@
/* global it, expect */
const path = require('path');
const fs = require('fs-extra');
const runBuildLambda = require('../../../../test/lib/run-build-lambda');
const FOUR_MINUTES = 240000;
@@ -10,7 +11,8 @@ it(
const {
buildResult: { output },
} = await runBuildLambda(path.join(__dirname, 'standard'));
expect(output.index).toBeDefined();
expect(output['index.html']).toBeDefined();
expect(output.goodbye).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath => filePath.match(/_error/));
const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/));
@@ -110,3 +112,119 @@ it(
},
FOUR_MINUTES,
);
it(
'Should build the serverless-config example',
async () => {
const {
workPath,
buildResult: { output },
} = await runBuildLambda(path.join(__dirname, 'serverless-config'));
expect(output.index).toBeDefined();
expect(output.goodbye).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath => filePath.match(/_error/));
const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/));
const hasUnderScoreErrorStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_error\.js$/));
expect(hasUnderScoreAppStaticFile).toBeTruthy();
expect(hasUnderScoreErrorStaticFile).toBeTruthy();
expect(serverlessError).toBeTruthy();
const contents = await fs.readdir(workPath);
expect(contents.some(name => name === 'next.config.js')).toBeTruthy();
expect(
contents.some(name => name.includes('next.config.original.')),
).toBeTruthy();
},
FOUR_MINUTES,
);
it(
'Should not build the serverless-config-async example',
async () => {
let error = null;
try {
await runBuildLambda(path.join(__dirname, 'serverless-config-async'));
} catch (err) {
error = err;
}
expect(error).not.toBe(null);
},
FOUR_MINUTES,
);
it(
'Should not build the serverless-config-promise example',
async () => {
let error = null;
try {
await runBuildLambda(path.join(__dirname, 'serverless-config-promise'));
} catch (err) {
error = err;
}
expect(error).not.toBe(null);
},
FOUR_MINUTES,
);
it(
'Should build the serverless-config-object example',
async () => {
const {
workPath,
buildResult: { output },
} = await runBuildLambda(path.join(__dirname, 'serverless-config-object'));
expect(output['index.html']).toBeDefined();
expect(output.goodbye).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath => filePath.match(/_error/));
const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/));
const hasUnderScoreErrorStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_error\.js$/));
expect(hasUnderScoreAppStaticFile).toBeTruthy();
expect(hasUnderScoreErrorStaticFile).toBeTruthy();
expect(serverlessError).toBeTruthy();
const contents = await fs.readdir(workPath);
expect(contents.some(name => name === 'next.config.js')).toBeTruthy();
expect(
contents.some(name => name.includes('next.config.original.')),
).toBeTruthy();
},
FOUR_MINUTES,
);
it(
'Should build the serverless-no-config example',
async () => {
const {
workPath,
buildResult: { output },
} = await runBuildLambda(path.join(__dirname, 'serverless-no-config'));
expect(output['index.html']).toBeDefined();
expect(output.goodbye).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath => filePath.match(/_error/));
const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/));
const hasUnderScoreErrorStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_error\.js$/));
expect(hasUnderScoreAppStaticFile).toBeTruthy();
expect(hasUnderScoreErrorStaticFile).toBeTruthy();
expect(serverlessError).toBeTruthy();
const contents = await fs.readdir(workPath);
expect(contents.some(name => name === 'next.config.js')).toBeTruthy();
expect(
contents.some(name => name.includes('next.config.original.')),
).toBeFalsy();
},
FOUR_MINUTES,
);

View File

@@ -0,0 +1,3 @@
module.exports = async function () {
return {};
};

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
const F = () => 'Goodbye World!';
F.getInitialProps = async () => ({});
export default F;

View File

@@ -0,0 +1 @@
export default () => 'Hello World!';

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
const F = () => 'Goodbye World!';
F.getInitialProps = async () => ({});
export default F;

View File

@@ -0,0 +1 @@
export default () => 'Hello World!';

View File

@@ -0,0 +1 @@
module.exports = new Promise(res => res({}));

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
const F = () => 'Goodbye World!';
F.getInitialProps = async () => ({});
export default F;

View File

@@ -0,0 +1 @@
export default () => 'Hello World!';

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
const F = () => 'Goodbye World!';
F.getInitialProps = async () => ({});
export default F;

View File

@@ -0,0 +1 @@
export default () => 'Hello World!';

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
const F = () => 'Goodbye World!';
F.getInitialProps = async () => ({});
export default F;

View File

@@ -0,0 +1 @@
export default () => 'Hello World!';

View File

@@ -1,3 +0,0 @@
module.exports = {
target: 'serverless',
};

View File

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

View File

@@ -0,0 +1,3 @@
const F = () => 'Goodbye World!';
F.getInitialProps = () => ({});
export default F;

View File

@@ -157,10 +157,13 @@ exports.build = async ({
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
};
// Use the system-installed version of `node` when running via `now dev`
const runtime = meta.isDev ? 'nodejs' : nodeVersion.runtime;
const lambda = await createLambda({
files: { ...preparedFiles, ...launcherFiles },
handler: 'launcher.launcher',
runtime: nodeVersion.runtime,
runtime,
});
return { [entrypoint]: lambda };

View File

@@ -1,7 +1,8 @@
{
"name": "@now/node-server",
"version": "0.8.1",
"version": "0.8.2",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/node-js-server-now-node-server",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

1
packages/now-node/bench/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
lambda

View File

@@ -0,0 +1,19 @@
const express = require('express');
const app = express();
module.exports = app;
app.use(express.json());
app.post('*', (req, res) => {
if (req.body == null) {
return res.status(400).send({ error: 'no JSON object in the request' });
}
return res.status(200).send(JSON.stringify(req.body, null, 4));
});
app.all('*', (req, res) => {
res.status(405).send({ error: 'only POST requests are accepted' });
});

View File

@@ -0,0 +1,7 @@
module.exports = (req, res) => {
if (req.body == null) {
return res.status(400).send({ error: 'no JSON object in the request' });
}
return res.status(200).send(JSON.stringify(req.body, null, 4));
};

View File

@@ -0,0 +1,9 @@
function doNothing() {}
module.exports = (req, res) => {
doNothing(req.query.who);
doNothing(req.body);
doNothing(req.cookies);
res.end('hello');
};

View File

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

View File

@@ -0,0 +1,10 @@
{
"name": "bench",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"express": "4.17.1",
"fs-extra": "8.0.1"
}
}

View File

@@ -0,0 +1,91 @@
const fs = require('fs-extra');
const { join } = require('path');
const { makeLauncher } = require('../dist/launcher');
const setupFiles = async (entrypoint, shouldAddHelpers) => {
await fs.remove(join(__dirname, 'lambda'));
await fs.ensureDir(join(__dirname, 'lambda'));
await fs.copyFile(
join(__dirname, '../dist/helpers.js'),
join(__dirname, 'lambda/helpers.js'),
);
await fs.copyFile(
require.resolve('@now/node-bridge/bridge'),
join(__dirname, 'lambda/bridge.js'),
);
await fs.copyFile(
join(process.cwd(), entrypoint),
join(__dirname, 'lambda/entrypoint.js'),
);
let launcher = makeLauncher('./entrypoint', shouldAddHelpers);
launcher += '\nexports.bridge=bridge';
await fs.writeFile(join(__dirname, 'lambda/launcher.js'), launcher);
};
const createBigJSONObj = () => {
const obj = {};
for (let i = 0; i < 1000; i += 1) {
obj[`idx${i}`] = `val${i}`;
}
};
const createEvent = () => ({
Action: 'Invoke',
body: JSON.stringify({
method: 'POST',
path: '/',
headers: { 'content-type': 'application/json' },
encoding: undefined,
body: createBigJSONObj(),
}),
});
const runTests = async (entrypoint, shouldAddHelpers = true, nb) => {
console.log(
`setting up files with entrypoint ${entrypoint} and ${
shouldAddHelpers ? 'helpers' : 'no helpers'
}`,
);
await setupFiles(entrypoint, shouldAddHelpers);
console.log('importing launcher');
const launcher = require('./lambda/launcher');
const event = createEvent();
const context = {};
const start = process.hrtime();
console.log(`throwing ${nb} events at lambda`);
for (let i = 0; i < nb; i += 1) {
// eslint-disable-next-line
await launcher.launcher(event, context);
}
const timer = process.hrtime(start);
const ms = (timer[0] * 1e9 + timer[1]) / 1e6;
await launcher.bridge.server.close();
delete require.cache[require.resolve('./lambda/launcher')];
console.log({ nb, sum: ms, avg: ms / nb });
};
const main = async () => {
if (process.argv.length !== 5) {
console.log(
'usage : node run.js <entrypoint-file> <add-helpers> <nb-of-request>',
);
return;
}
const [, , entrypoint, helpers, nbRequests] = process.argv;
const shouldAddHelpers = helpers !== 'false' && helpers !== 'no';
const nb = Number(nbRequests);
await runTests(entrypoint, shouldAddHelpers, nb);
};
main();

View File

@@ -0,0 +1,378 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
dependencies:
mime-types "~2.1.24"
negotiator "0.6.2"
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
body-parser@1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
dependencies:
bytes "3.1.0"
content-type "~1.0.4"
debug "2.6.9"
depd "~1.1.2"
http-errors "1.7.2"
iconv-lite "0.4.24"
on-finished "~2.3.0"
qs "6.7.0"
raw-body "2.4.0"
type-is "~1.6.17"
bytes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
content-disposition@0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
dependencies:
safe-buffer "5.1.2"
content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
cookie@0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
destroy@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
express@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
dependencies:
accepts "~1.3.7"
array-flatten "1.1.1"
body-parser "1.19.0"
content-disposition "0.5.3"
content-type "~1.0.4"
cookie "0.4.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "~1.1.2"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "~1.1.2"
fresh "0.5.2"
merge-descriptors "1.0.1"
methods "~1.1.2"
on-finished "~2.3.0"
parseurl "~1.3.3"
path-to-regexp "0.1.7"
proxy-addr "~2.0.5"
qs "6.7.0"
range-parser "~1.2.1"
safe-buffer "5.1.2"
send "0.17.1"
serve-static "1.14.1"
setprototypeof "1.1.1"
statuses "~1.5.0"
type-is "~1.6.18"
utils-merge "1.0.1"
vary "~1.1.2"
finalhandler@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
dependencies:
debug "2.6.9"
encodeurl "~1.0.2"
escape-html "~1.0.3"
on-finished "~2.3.0"
parseurl "~1.3.3"
statuses "~1.5.0"
unpipe "~1.0.0"
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
fs-extra@8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.0.1.tgz#90294081f978b1f182f347a440a209154344285b"
integrity sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A==
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
universalify "^0.1.0"
graceful-fs@^4.1.2, graceful-fs@^4.1.6:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
http-errors@1.7.2, http-errors@~1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
dependencies:
depd "~1.1.2"
inherits "2.0.3"
setprototypeof "1.1.1"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ipaddr.js@1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
optionalDependencies:
graceful-fs "^4.1.6"
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
mime-db@1.40.0:
version "1.40.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
mime-types@~2.1.24:
version "2.1.24"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
dependencies:
mime-db "1.40.0"
mime@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
dependencies:
ee-first "1.1.1"
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
proxy-addr@~2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==
dependencies:
forwarded "~0.1.2"
ipaddr.js "1.9.0"
qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
dependencies:
bytes "3.1.0"
http-errors "1.7.2"
iconv-lite "0.4.24"
unpipe "1.0.0"
safe-buffer@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
dependencies:
debug "2.6.9"
depd "~1.1.2"
destroy "~1.0.4"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "~1.7.2"
mime "1.6.0"
ms "2.1.1"
on-finished "~2.3.0"
range-parser "~1.2.1"
statuses "~1.5.0"
serve-static@1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.17.1"
setprototypeof@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
type-is@~1.6.17, type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
dependencies:
media-typer "0.3.0"
mime-types "~2.1.24"
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=

View File

@@ -13,12 +13,17 @@ cp -v "$bridge_defs" src
# build ts files
tsc
# todo: improve
# copy type file for ts test
cp dist/types.d.ts test/fixtures/15-helpers/ts/types.d.ts
# use types.d.ts as the main types export
mv dist/types.d.ts dist/types
rm dist/*.d.ts
mv dist/types dist/index.d.ts
# bundle helpers.ts with ncc
rm dist/helpers.js
ncc build src/helpers.ts -o dist/helpers
mv dist/helpers/index.js dist/helpers.js
rm -rf dist/helpers
# todo: improve
# copy type file for ts test
cp dist/types.d.ts test/fixtures/15-helpers/ts/types.d.ts

View File

@@ -1,8 +1,9 @@
{
"name": "@now/node",
"version": "0.10.1",
"version": "0.11.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/node-js-now-node",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",
@@ -12,8 +13,7 @@
"@now/node-bridge": "1.2.2",
"@types/node": "*",
"@zeit/ncc": "0.18.5",
"@zeit/ncc-watcher": "1.0.3",
"fs-extra": "7.0.1"
"@zeit/ncc-watcher": "1.0.3"
},
"scripts": {
"build": "./build.sh",
@@ -26,9 +26,11 @@
"devDependencies": {
"@types/content-type": "1.1.3",
"@types/cookie": "0.3.3",
"@types/etag": "1.8.0",
"@types/test-listen": "1.1.0",
"content-type": "1.0.4",
"cookie": "0.4.0",
"etag": "1.8.1",
"node-fetch": "2.6.0",
"test-listen": "1.1.0",
"typescript": "3.5.2"

View File

@@ -5,7 +5,6 @@ import {
NowRequestQuery,
NowRequestBody,
} from './types';
import { Stream } from 'stream';
import { Server } from 'http';
import { Bridge } from './bridge';
@@ -78,83 +77,122 @@ function status(res: NowResponse, statusCode: number): NowResponse {
return res;
}
function setContentHeaders(
res: NowResponse,
type: string,
length?: number
): void {
if (!res.getHeader('content-type')) {
res.setHeader('content-type', type);
}
if (length !== undefined) {
res.setHeader('content-length', length);
}
function setCharset(type: string, charset: string) {
const { parse, format } = require('content-type');
const parsed = parse(type);
parsed.parameters.charset = charset;
return format(parsed);
}
function send(res: NowResponse, body: any) {
const t = typeof body;
if (body === null || t === 'undefined') {
res.end();
return res;
}
if (t === 'string') {
setContentHeaders(
res,
'text/plain; charset=utf-8',
Buffer.byteLength(body)
);
res.end(body);
return res;
}
if (Buffer.isBuffer(body)) {
setContentHeaders(res, 'application/octet-stream', body.length);
res.end(body);
return res;
}
if (body instanceof Stream) {
setContentHeaders(res, 'application/octet-stream');
body.pipe(res);
return res;
}
switch (t) {
case 'boolean':
case 'number':
case 'bigint':
case 'object':
return json(res, body);
}
throw new Error(
'`body` is not a valid string, object, boolean, number, Stream, or Buffer'
);
function createETag(body: any, encoding: string | undefined) {
const etag = require('etag');
const buf = !Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body;
return etag(buf, { weak: true });
}
function json(res: NowResponse, jsonBody: any): NowResponse {
switch (typeof jsonBody) {
case 'object':
case 'boolean':
case 'number':
case 'bigint':
function send(req: NowRequest, res: NowResponse, body: any): NowResponse {
let chunk: unknown = body;
let encoding: string | undefined;
switch (typeof chunk) {
// string defaulting to html
case 'string':
const body = JSON.stringify(jsonBody);
setContentHeaders(
res,
'application/json; charset=utf-8',
Buffer.byteLength(body)
);
res.end(body);
return res;
if (!res.getHeader('content-type')) {
res.setHeader('content-type', 'text/html');
}
break;
case 'boolean':
case 'number':
case 'object':
if (chunk === null) {
chunk = '';
} else if (Buffer.isBuffer(chunk)) {
if (!res.getHeader('content-type')) {
res.setHeader('content-type', 'application/octet-stream');
}
} else {
return json(req, res, chunk);
}
break;
}
throw new Error(
'`jsonBody` is not a valid object, boolean, string, number, or null'
);
// write strings in utf-8
if (typeof chunk === 'string') {
encoding = 'utf8';
// reflect this in content-type
const type = res.getHeader('content-type');
if (typeof type === 'string') {
res.setHeader('content-type', setCharset(type, 'utf-8'));
}
}
// populate Content-Length
let len: number | undefined;
if (chunk !== undefined) {
if (Buffer.isBuffer(chunk)) {
// get length of Buffer
len = chunk.length;
} else if (typeof chunk === 'string') {
if (chunk.length < 1000) {
// just calculate length small chunk
len = Buffer.byteLength(chunk, encoding);
} else {
// convert chunk to Buffer and calculate
const buf = Buffer.from(chunk, encoding);
len = buf.length;
chunk = buf;
encoding = undefined;
}
} else {
throw new Error(
'`body` is not a valid string, object, boolean, number, Stream, or Buffer'
);
}
if (len !== undefined) {
res.setHeader('content-length', len);
}
}
// populate ETag
let etag: string | undefined;
if (
!res.getHeader('etag') &&
len !== undefined &&
(etag = createETag(chunk, encoding))
) {
res.setHeader('etag', etag);
}
// strip irrelevant headers
if (204 === res.statusCode || 304 === res.statusCode) {
res.removeHeader('Content-Type');
res.removeHeader('Content-Length');
res.removeHeader('Transfer-Encoding');
chunk = '';
}
if (req.method === 'HEAD') {
// skip body for HEAD
res.end();
} else {
// respond
res.end(chunk, encoding);
}
return res;
}
function json(req: NowRequest, res: NowResponse, jsonBody: any): NowResponse {
const body = JSON.stringify(jsonBody);
// content-type
if (!res.getHeader('content-type')) {
res.setHeader('content-type', 'application/json; charset=utf-8');
}
return send(req, res, body);
}
export class ApiError extends Error {
@@ -219,8 +257,8 @@ export function createServerWithHelpers(
setLazyProp<NowRequestBody>(req, 'body', getBodyParser(req, event.body));
res.status = statusCode => status(res, statusCode);
res.send = body => send(res, body);
res.json = jsonBody => json(res, jsonBody);
res.send = body => send(req, res, body);
res.json = jsonBody => json(req, res, jsonBody);
await listener(req, res);
} catch (err) {

View File

@@ -1,4 +1,3 @@
import { readFile } from 'fs-extra';
import { Assets, NccOptions } from '@zeit/ncc';
import { join, dirname, relative, sep } from 'path';
import { NccWatcher, WatcherResult } from '@zeit/ncc-watcher';
@@ -19,6 +18,7 @@ import {
shouldServe,
} from '@now/build-utils';
export { NowRequest, NowResponse } from './types';
import { makeLauncher } from './launcher';
interface CompilerConfig {
includeFiles?: string | string[];
@@ -33,6 +33,10 @@ interface DownloadOptions {
const watchers: Map<string, NccWatcher> = new Map();
const LAUNCHER_FILENAME = '___now_launcher';
const BRIDGE_FILENAME = '___now_bridge';
const HELPERS_FILENAME = '___now_helpers';
function getWatcher(entrypoint: string, options: NccOptions): NccWatcher {
let watcher = watchers.get(entrypoint);
if (!watcher) {
@@ -101,7 +105,8 @@ async function compile(
assets = result.assets;
watch = [...result.files, ...result.dirs, ...result.missing]
.filter(f => f.startsWith(workPath))
.map(f => relative(workPath, f));
.map(f => relative(workPath, f))
.concat(Object.keys(assets || {}));
} else {
const ncc = require('@zeit/ncc');
const result = await ncc(input, {
@@ -202,44 +207,37 @@ export async function build({
config,
meta
);
const launcherPath = join(__dirname, 'launcher.js');
let launcherData = await readFile(launcherPath, 'utf8');
launcherData = launcherData.replace(
'// PLACEHOLDER:shouldStoreProxyRequests',
shouldAddHelpers ? 'shouldStoreProxyRequests = true;' : ''
);
launcherData = launcherData.replace(
'// PLACEHOLDER:setServer',
[
`let listener = require("./${entrypoint}");`,
'if (listener.default) listener = listener.default;',
shouldAddHelpers
? 'const server = require("./helpers").createServerWithHelpers(listener, bridge);'
: 'const server = require("http").createServer(listener);',
'bridge.setServer(server);',
].join(' ')
);
const launcherFiles: Files = {
'launcher.js': new FileBlob({ data: launcherData }),
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
[`${LAUNCHER_FILENAME}.js`]: new FileBlob({
data: makeLauncher({
entrypointPath: `./${entrypoint}`,
bridgePath: `./${BRIDGE_FILENAME}`,
helpersPath: `./${HELPERS_FILENAME}`,
shouldAddHelpers,
}),
}),
[`${BRIDGE_FILENAME}.js`]: new FileFsRef({
fsPath: require('@now/node-bridge'),
}),
};
if (shouldAddHelpers) {
launcherFiles['helpers.js'] = new FileFsRef({
launcherFiles[`${HELPERS_FILENAME}.js`] = new FileFsRef({
fsPath: join(__dirname, 'helpers.js'),
});
}
// Use the system-installed version of `node` when running via `now dev`
const runtime = meta.isDev ? 'nodejs' : nodeVersion.runtime;
const lambda = await createLambda({
files: {
...preparedFiles,
...launcherFiles,
},
handler: 'launcher.launcher',
runtime: nodeVersion.runtime,
handler: `${LAUNCHER_FILENAME}.launcher`,
runtime,
});
const output = { [entrypoint]: lambda };

View File

@@ -1,9 +1,29 @@
import { Bridge } from './bridge';
type LauncherConfiguration = {
entrypointPath: string;
bridgePath: string;
helpersPath: string;
shouldAddHelpers?: boolean;
};
let shouldStoreProxyRequests: boolean = false;
// PLACEHOLDER:shouldStoreProxyRequests
export function makeLauncher({
entrypointPath,
bridgePath,
helpersPath,
shouldAddHelpers = false,
}: LauncherConfiguration): string {
return `const { Bridge } = require("${bridgePath}");
const { Server } = require("http");
const bridge = new Bridge(undefined, shouldStoreProxyRequests);
let isServerListening = false;
let bridge = new Bridge();
const saveListen = Server.prototype.listen;
Server.prototype.listen = function listen() {
isServerListening = true;
console.log('Legacy server listening...');
bridge.setServer(this);
Server.prototype.listen = saveListen;
return bridge.listen();
};
if (!process.env.NODE_ENV) {
process.env.NODE_ENV =
@@ -11,13 +31,43 @@ if (!process.env.NODE_ENV) {
}
try {
// PLACEHOLDER:setServer
let listener = require("${entrypointPath}");
if (listener.default) listener = listener.default;
if (typeof listener.listen === 'function') {
Server.prototype.listen = saveListen;
const server = listener;
bridge.setServer(server);
bridge.listen();
} else if (typeof listener === 'function') {
Server.prototype.listen = saveListen;
let server;
${
shouldAddHelpers
? [
'bridge = new Bridge(undefined, true);',
`server = require("${helpersPath}").createServerWithHelpers(listener, bridge);`,
].join('\n')
: ['server = require("http").createServer(listener);'].join('\n')
}
bridge.setServer(server);
bridge.listen();
} else if (typeof listener === 'object' && Object.keys(listener).length === 0) {
setTimeout(() => {
if (!isServerListening) {
console.error('No exports found in module "${entrypointPath}".');
console.error('Did you forget to export a function or a server?');
process.exit(1);
}
}, 5000);
} else {
console.error('Invalid export found in module "${entrypointPath}".');
console.error('The default export must be a function or server.');
}
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.error(err.message);
console.error(
'Did you forget to add it to "dependencies" in `package.json`?'
);
console.error('Did you forget to add it to "dependencies" in \`package.json\`?');
process.exit(1);
} else {
console.error(err);
@@ -25,6 +75,5 @@ try {
}
}
bridge.listen();
exports.launcher = bridge.launcher;
exports.launcher = bridge.launcher;`;
}

View File

@@ -0,0 +1,25 @@
const Hapi = require('@hapi/hapi');
const init = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost',
});
server.route({
method: 'GET',
path: '/{p*}',
handler: () => 'hapi-async:RANDOMNESS_PLACEHOLDER',
});
await server.start();
console.log('Hapi server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.log('Hapi failed in an unexpected way');
console.log(err);
process.exit(1);
});
init();

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"@hapi/hapi": "18.3.1"
}
}

View File

@@ -0,0 +1,7 @@
const http = require('http');
const server = http.createServer((req, resp) => {
resp.end('root:RANDOMNESS_PLACEHOLDER');
});
server.listen();

View File

@@ -0,0 +1,15 @@
{
"version": 2,
"builds": [{ "src": "**/*.js", "use": "@now/node" }],
"probes": [
{ "path": "/", "mustContain": "root:RANDOMNESS_PLACEHOLDER" },
{
"path": "/subdirectory/",
"mustContain": "subdir:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/hapi-async/",
"mustContain": "hapi-async:RANDOMNESS_PLACEHOLDER"
}
]
}

View File

@@ -0,0 +1,7 @@
const http = require('http');
const server = http.createServer((req, resp) => {
resp.end('subdir:RANDOMNESS_PLACEHOLDER');
});
server.listen();

View File

@@ -1,20 +0,0 @@
/* eslint-disable prefer-destructuring */
const express = require('express');
const app = express();
module.exports = app;
app.use(express.json());
app.all('*', (req, res) => {
res.status(200);
let who = 'anonymous';
if (req.body && req.body.who) {
who = req.body.who;
}
res.send(`hello ${who}:RANDOMNESS_PLACEHOLDER`);
});

View File

@@ -3,7 +3,6 @@
"builds": [
{ "src": "index.js", "use": "@now/node" },
{ "src": "ts/index.ts", "use": "@now/node" },
{ "src": "express-compat/index.js", "use": "@now/node" },
{ "src": "micro-compat/index.js", "use": "@now/node" },
{
"src": "no-helpers/index.js",
@@ -35,12 +34,6 @@
"path": "/ts",
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/express-compat",
"method": "POST",
"body": { "who": "sara" },
"mustContain": "hello sara:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/micro-compat",
"method": "POST",

View File

@@ -0,0 +1,9 @@
const express = require('express');
const app = express();
app.all('*', (req, res) => {
res.send('hello from express:RANDOMNESS_PLACEHOLDER');
});
module.exports = app;

View File

@@ -0,0 +1,17 @@
const Hapi = require('@hapi/hapi');
const server = Hapi.server({
port: 3000,
host: 'localhost',
});
server.route({
method: 'GET',
path: '/{p*}',
handler: () => 'hello from hapi:RANDOMNESS_PLACEHOLDER',
});
// server.listener is a node's http.Server
// server does not have the `listen` method so we need to export this instead
module.exports = server.listener;

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"@hapi/hapi": "18.3.1"
}
}

View File

@@ -0,0 +1,7 @@
const http = require('http');
const server = http.createServer((req, res) => {
res.end('hello:RANDOMNESS_PLACEHOLDER');
});
module.exports = server;

View File

@@ -0,0 +1,9 @@
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx) => {
ctx.body = 'hello from koa:RANDOMNESS_PLACEHOLDER';
});
module.exports = app;

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"koa": "2.7.0"
}
}

View File

@@ -0,0 +1,22 @@
{
"version": 2,
"builds": [{ "src": "**/*.js", "use": "@now/node" }],
"probes": [
{
"path": "/",
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/express",
"mustContain": "hello from express:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/koa",
"mustContain": "hello from koa:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/hapi",
"mustContain": "hello from hapi:RANDOMNESS_PLACEHOLDER"
}
]
}

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end('bridge:RANDOMNESS_PLACEHOLDER');
};

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end('helpers:RANDOMNESS_PLACEHOLDER');
};

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end('launcher:RANDOMNESS_PLACEHOLDER');
};

View File

@@ -0,0 +1,18 @@
{
"version": 2,
"builds": [{ "src": "*.js", "use": "@now/node" }],
"probes": [
{
"path": "/helpers.js",
"mustContain": "helpers:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/bridge.js",
"mustContain": "bridge:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/launcher.js",
"mustContain": "launcher:RANDOMNESS_PLACEHOLDER"
}
]
}

View File

@@ -272,304 +272,7 @@ describe('req.body', () => {
});
});
describe('res.send()', () => {
test('res.send() should set body to ""', async () => {
mockListener.mockImplementation((req, res) => {
res.send();
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(await res.text()).toBe('');
});
test('res.send(null) should set body to ""', async () => {
mockListener.mockImplementation((req, res) => {
res.send(null);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(await res.text()).toBe('');
});
test('res.send(undefined) should set body to ""', async () => {
mockListener.mockImplementation((req, res) => {
res.send(undefined);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(await res.text()).toBe('');
});
test('res.send(String) should send as text/plain', async () => {
mockListener.mockImplementation((req, res) => {
res.send('hey');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('text/plain; charset=utf-8');
expect(await res.text()).toBe('hey');
});
test('res.send(String) should not override Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('Content-Type', 'text/html');
res.send('hey');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('text/html');
expect(await res.text()).toBe('hey');
});
test('res.send(String) should set Content-Length', async () => {
mockListener.mockImplementation((req, res) => {
res.send('½ + ¼ = ¾');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(Number(res.headers.get('content-length'))).toBe(12);
expect(await res.text()).toBe('½ + ¼ = ¾');
});
test('res.send(Buffer) should send as octet-stream', async () => {
mockListener.mockImplementation((req, res) => {
res.send(Buffer.from('hello'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('application/octet-stream');
expect(await res.text()).toBe('hello');
});
test('res.send(Buffer) should not override Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('Content-Type', 'text/plain');
res.send(Buffer.from('hello'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('text/plain');
expect(await res.text()).toBe('hello');
});
test('res.send(Buffer) should set Content-Length', async () => {
mockListener.mockImplementation((req, res) => {
res.send(Buffer.from('½ + ¼ = ¾'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(Number(res.headers.get('content-length'))).toBe(12);
expect(await res.text()).toBe('½ + ¼ = ¾');
});
test('res.send(Object) should send as application/json', async () => {
mockListener.mockImplementation((req, res) => {
res.send({ name: 'tobi' });
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('{"name":"tobi"}');
});
test('res.send(Stream) should send as application/octet-stream', async () => {
const { PassThrough } = require('stream');
mockListener.mockImplementation((req, res) => {
const stream = new PassThrough();
res.send(stream);
stream.push('hello');
stream.end();
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('application/octet-stream');
expect(await res.text()).toBe('hello');
});
test('res.send() should send be chainable', async () => {
const spy = jest.fn();
mockListener.mockImplementation((req, res) => {
spy(res, res.send('hello'));
});
await fetchWithProxyReq(url);
const [a, b] = spy.mock.calls[0];
expect(a).toBe(b);
});
});
describe('res.json()', () => {
test('res.json() should not override previous Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('Content-Type', 'application/vnd.example+json');
res.json({ hello: 'world' });
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/vnd.example+json'
);
expect(await res.text()).toBe('{"hello":"world"}');
});
test('res.json() should send as application/json', async () => {
mockListener.mockImplementation((req, res) => {
res.json({ hello: 'world' });
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('{"hello":"world"}');
});
test('res.json() should set Content-Length and Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.json({ hello: '½ + ¼ = ¾' });
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(Number(res.headers.get('content-length'))).toBe(24);
expect(await res.text()).toBe('{"hello":"½ + ¼ = ¾"}');
});
test('res.json(null) should respond with json for null', async () => {
mockListener.mockImplementation((req, res) => {
res.json(null);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('null');
});
test('res.json() should throw an error', async () => {
let _err;
mockListener.mockImplementation((req, res) => {
try {
res.json();
} catch (err) {
_err = err;
} finally {
res.end();
}
});
await fetchWithProxyReq(url);
expect(_err).toBeDefined();
expect(_err.message).toMatch(/not a valid object/);
});
test('res.json(undefined) should throw an error', async () => {
let _err;
mockListener.mockImplementation((req, res) => {
try {
res.json(undefined);
} catch (err) {
_err = err;
} finally {
res.end();
}
});
await fetchWithProxyReq(url);
expect(_err).toBeDefined();
expect(_err.message).toMatch(/not a valid object/);
});
test('res.json(Number) should respond with json for number', async () => {
mockListener.mockImplementation((req, res) => {
res.json(300);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('300');
});
test('res.json(String) should respond with json for string', async () => {
mockListener.mockImplementation((req, res) => {
res.json('str');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('"str"');
});
test('res.json(Array) should respond with json for array', async () => {
mockListener.mockImplementation((req, res) => {
res.json(['foo', 'bar', 'baz']);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('["foo","bar","baz"]');
});
test('res.json(Object) should respond with json for object', async () => {
mockListener.mockImplementation((req, res) => {
res.json({ name: 'tobi' });
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('{"name":"tobi"}');
});
test('res.json() should send be chainable', async () => {
const spy = jest.fn();
mockListener.mockImplementation((req, res) => {
spy(res, res.json({ hello: 'world' }));
});
await fetchWithProxyReq(url);
const [a, b] = spy.mock.calls[0];
expect(a).toBe(b);
});
});
describe('res.status()', () => {
describe('res.status', () => {
test('res.status() should set the status code', async () => {
mockListener.mockImplementation((req, res) => {
res.status(404);
@@ -595,3 +298,483 @@ describe('res.status()', () => {
expect(a).toBe(b);
});
});
// tests based on expressjs test suite
// see https://github.com/expressjs/express/blob/master/test/res.send.js
describe('res.send', () => {
test('should be chainable', async () => {
const spy = jest.fn();
mockListener.mockImplementation((req, res) => {
spy(res, res.send('hello'));
});
await fetchWithProxyReq(url);
const [a, b] = spy.mock.calls[0];
expect(a).toBe(b);
});
describe('res.send()', () => {
test('should set body to ""', async () => {
mockListener.mockImplementation((req, res) => {
res.send();
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(await res.text()).toBe('');
});
});
describe('.send(null)', () => {
test('should set body to ""', async () => {
mockListener.mockImplementation((req, res) => {
res.send(null);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-length')).toBe('0');
expect(await res.text()).toBe('');
});
});
describe('.send(undefined)', () => {
test('should set body to ""', async () => {
mockListener.mockImplementation((req, res) => {
res.send(undefined);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(await res.text()).toBe('');
});
});
describe('.send(String)', () => {
test('should send as html', async () => {
mockListener.mockImplementation((req, res) => {
res.send('<p>hey</p>');
});
const res = await fetchWithProxyReq(url);
expect(res.headers.get('content-type')).toBe('text/html; charset=utf-8');
expect(await res.text()).toBe('<p>hey</p>');
});
test('should set Content-Length', async () => {
mockListener.mockImplementation((req, res) => {
res.send('½ + ¼ = ¾');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(Number(res.headers.get('content-length'))).toBe(12);
expect(await res.text()).toBe('½ + ¼ = ¾');
});
test('should set ETag', async () => {
mockListener.mockImplementation((req, res) => {
res.send(Array(1000).join('-'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe(
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
);
});
test('should not override Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('Content-Type', 'text/plain');
res.send('hey');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
expect(await res.text()).toBe('hey');
});
test('should override charset in Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('Content-Type', 'text/plain; charset=iso-8859-1');
res.send('hey');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
expect(await res.text()).toBe('hey');
});
});
describe('.send(Buffer)', () => {
test('should keep charset in Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('Content-Type', 'text/plain; charset=iso-8859-1');
res.send(Buffer.from('hi'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toBe(
'text/plain; charset=iso-8859-1'
);
expect(await res.text()).toBe('hi');
});
test('should set Content-Length', async () => {
mockListener.mockImplementation((req, res) => {
res.send(Buffer.from('½ + ¼ = ¾'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(Number(res.headers.get('content-length'))).toBe(12);
expect(await res.text()).toBe('½ + ¼ = ¾');
});
test('should send as octet-stream', async () => {
mockListener.mockImplementation((req, res) => {
res.send(Buffer.from('hello'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toBe('application/octet-stream');
expect((await res.buffer()).toString('hex')).toBe(
Buffer.from('hello').toString('hex')
);
});
test('should set ETag', async () => {
mockListener.mockImplementation((req, res) => {
res.send(Buffer.alloc(999, '-'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe(
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
);
});
test('should not override Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.send(Buffer.from('hey'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
expect(await res.text()).toBe('hey');
});
test('should not override ETag', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('ETag', '"foo"');
res.send(Buffer.from('hey'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe('"foo"');
expect(await res.text()).toBe('hey');
});
});
describe('.send(Object)', () => {
test('should send as application/json', async () => {
mockListener.mockImplementation((req, res) => {
res.send({ name: 'tobi' });
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('{"name":"tobi"}');
});
});
describe('when the request method is HEAD', () => {
test('should ignore the body', async () => {
mockListener.mockImplementation((req, res) => {
res.send('yay');
});
// TODO: fix this test
// node-fetch is automatically ignoring the body so this test will never fail
const res = await fetchWithProxyReq(url, { method: 'HEAD' });
expect(res.status).toBe(200);
expect((await res.buffer()).toString()).toBe('');
});
});
describe('when .statusCode is 204', () => {
test('should strip Content-* fields, Transfer-Encoding field, and body', async () => {
mockListener.mockImplementation((req, res) => {
res.statusCode = 204;
res.setHeader('Transfer-Encoding', 'chunked');
res.send('foo');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(204);
expect(res.headers.get('Content-Type')).toBe(null);
expect(res.headers.get('Content-Length')).toBe(null);
expect(res.headers.get('Transfer-Encoding')).toBe(null);
expect(await res.text()).toBe('');
});
});
describe('when .statusCode is 304', () => {
test('should strip Content-* fields, Transfer-Encoding field, and body', async () => {
mockListener.mockImplementation((req, res) => {
res.statusCode = 304;
res.setHeader('Transfer-Encoding', 'chunked');
res.send('foo');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(304);
expect(res.headers.get('Content-Type')).toBe(null);
expect(res.headers.get('Content-Length')).toBe(null);
expect(res.headers.get('Transfer-Encoding')).toBe(null);
expect(await res.text()).toBe('');
});
});
// test('should always check regardless of length', async () => {
// const etag = '"asdf"';
// mockListener.mockImplementation((req, res) => {
// res.setHeader('ETag', etag);
// res.send('hey');
// });
// const res = await fetchWithProxyReq(url, {
// headers: { 'If-None-Match': etag },
// });
// expect(res.status).toBe(304);
// });
// test('should respond with 304 Not Modified when fresh', async () => {
// const etag = '"asdf"';
// mockListener.mockImplementation((req, res) => {
// res.setHeader('ETag', etag);
// res.send(Array(1000).join('-'));
// });
// const res = await fetchWithProxyReq(url, {
// headers: { 'If-None-Match': etag },
// });
// expect(res.status).toBe(304);
// });
// test('should not perform freshness check unless 2xx or 304', async () => {
// const etag = '"asdf"';
// mockListener.mockImplementation((req, res) => {
// res.status(500);
// res.setHeader('ETag', etag);
// res.send('hey');
// });
// const res = await fetchWithProxyReq(url, {
// headers: { 'If-None-Match': etag },
// });
// expect(res.status).toBe(500);
// expect(await res.text()).toBe('hey');
// });
describe('etag', () => {
test('should send ETag', async () => {
mockListener.mockImplementation((req, res) => {
res.send('kajdslfkasdf');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe('W/"c-IgR/L5SF7CJQff4wxKGF/vfPuZ0"');
});
test('should send ETag for empty string response', async () => {
mockListener.mockImplementation((req, res) => {
res.send('');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe('W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"');
});
test('should send ETag for long response', async () => {
mockListener.mockImplementation((req, res) => {
res.send(Array(1000).join('-'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe(
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
);
});
test('should not override ETag when manually set', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('etag', '"asdf"');
res.send('hello');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe('"asdf"');
});
test('should not send ETag for res.send()', async () => {
mockListener.mockImplementation((req, res) => {
res.send();
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe(null);
});
});
});
// tests based on expressjs test suite
// see https://github.com/expressjs/express/blob/master/test/res.json.js
describe('res.json', () => {
test('should send be chainable', async () => {
const spy = jest.fn();
mockListener.mockImplementation((req, res) => {
spy(res, res.json({ hello: 'world' }));
});
await fetchWithProxyReq(url);
const [a, b] = spy.mock.calls[0];
expect(a).toBe(b);
});
test('res.json() should send an empty body', async () => {
mockListener.mockImplementation((req, res) => {
res.json();
});
await fetchWithProxyReq(url);
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('');
});
describe('.json(object)', () => {
test('should not override previous Content-Types', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('content-type', 'application/vnd.example+json');
res.json({ hello: 'world' });
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/vnd.example+json; charset=utf-8'
);
expect(await res.text()).toBe('{"hello":"world"}');
});
test('should set Content-Length and Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.json({ hello: '½ + ¼ = ¾' });
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(Number(res.headers.get('content-length'))).toBe(24);
expect(await res.text()).toBe('{"hello":"½ + ¼ = ¾"}');
});
describe('when given primitives', () => {
test('should respond with json for null', async () => {
mockListener.mockImplementation((req, res) => {
res.json(null);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('null');
});
test('should respond with json for Number', async () => {
mockListener.mockImplementation((req, res) => {
res.json(300);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('300');
});
test('should respond with json for String', async () => {
mockListener.mockImplementation((req, res) => {
res.json('str');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('"str"');
});
});
test('should respond with json when given an array', async () => {
mockListener.mockImplementation((req, res) => {
res.json(['foo', 'bar', 'baz']);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('["foo","bar","baz"]');
});
test('should respond with json when given an object', async () => {
mockListener.mockImplementation((req, res) => {
res.json({ name: 'tobi' });
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('{"name":"tobi"}');
});
});
});

View File

@@ -1,8 +1,9 @@
{
"name": "@now/optipng",
"version": "0.6.3",
"version": "0.6.4",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/optipng-now-optipng",
"files": [
"dist"
],

View File

@@ -1,7 +1,8 @@
{
"name": "@now/php",
"version": "0.5.5",
"version": "0.5.6",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/php-now-php",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,8 +1,9 @@
{
"name": "@now/python",
"version": "0.2.9",
"version": "0.2.11",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/python-now-python",
"files": [
"dist",
"now_init.py"

View File

@@ -92,12 +92,20 @@ export const build = async ({
files: originalFiles,
entrypoint,
meta = {},
config,
}: BuildOptions) => {
console.log('downloading files...');
let downloadedFiles = await download(originalFiles, workPath, meta);
if (meta.isDev) {
const base = dirname(downloadedFiles['now.json'].fsPath);
let base = null;
if (config && config.zeroConfig) {
base = workPath;
} else {
base = dirname(downloadedFiles['now.json'].fsPath);
}
const destNow = join(base, '.now', 'cache', basename(entrypoint, '.py'));
await download(downloadedFiles, destNow);
downloadedFiles = await glob('**', destNow);

View File

@@ -64,6 +64,7 @@ async function bundleInstall(
env: {
BUNDLE_SILENCE_ROOT_WARNING: '1',
BUNDLE_APP_CONFIG: bundleAppConfig,
BUNDLE_JOBS: '4',
},
}
);

View File

@@ -2,7 +2,7 @@ import { join } from 'path';
import execa from 'execa';
import { getWriteableDirectory } from '@now/build-utils';
const RUBY_VERSION = '2.5.3';
const RUBY_VERSION = '2.5.5';
async function installRuby(version: string = RUBY_VERSION) {
const baseDir = await getWriteableDirectory();

View File

@@ -1,8 +1,10 @@
{
"name": "@now/ruby",
"author": "Nathan Cahill <nathan@nathancahill.com>",
"version": "0.1.0",
"version": "0.1.2",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/ruby-now-ruby",
"files": [
"dist",
"now_init.rb"
@@ -12,7 +14,6 @@
"url": "https://github.com/zeit/now-builders.git",
"directory": "packages/now-ruby"
},
"license": "MIT",
"scripts": {
"build": "tsc",
"test": "tsc && jest",
@@ -20,7 +21,9 @@
},
"dependencies": {
"execa": "^1.0.0",
"fs-extra": "^7.0.1",
"fs-extra": "^7.0.1"
},
"devDependencies": {
"typescript": "3.5.2"
}
}

View File

@@ -1,8 +1,9 @@
{
"name": "@now/rust",
"version": "0.2.7",
"version": "0.2.8",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/now-rust",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,8 +1,9 @@
{
"name": "@now/static-build",
"version": "0.6.1",
"version": "0.7.2",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/static-build-now-static-build",
"files": [
"dist"
],

View File

@@ -0,0 +1,211 @@
import { readdir, stat } from 'fs';
import { promisify } from 'util';
import { join } from 'path';
const readirPromise = promisify(readdir);
const statPromise = promisify(stat);
const isDir = async (file: string): Promise<boolean> =>
(await statPromise(file)).isDirectory();
// Please note that is extremely important
// that the `dependency` property needs
// to reference a CLI. This is needed because
// you might want (for example) a Gatsby
// site that is powered by Preact, so you
// can't look for the `preact` dependency.
// Instead, you need to look for `preact-cli`
// when optimizing Preact CLI projects.
export default [
{
name: 'Gatsby.js',
dependency: 'gatsby',
getOutputDirName: async () => 'public',
},
{
name: 'Hexo',
dependency: 'hexo',
getOutputDirName: async () => 'public',
},
{
name: 'Preact',
dependency: 'preact-cli',
getOutputDirName: async () => 'build',
defaultRoutes: [
{
handle: 'filesystem',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
},
{
name: 'Vue.js',
dependency: '@vue/cli-service',
getOutputDirName: async () => 'dist',
defaultRoutes: [
{
handle: 'filesystem',
},
{
src: '^/js/(.*)',
headers: { 'cache-control': 'max-age=31536000, immutable' },
dest: '/js/$1',
},
{
src: '^/css/(.*)',
headers: { 'cache-control': 'max-age=31536000, immutable' },
dest: '/css/$1',
},
{
src: '^/img/(.*)',
headers: { 'cache-control': 'max-age=31536000, immutable' },
dest: '/img/$1',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
},
{
name: 'Angular',
dependency: '@angular/cli',
minNodeRange: '10.x',
getOutputDirName: async (dirPrefix: string) => {
const base = 'dist';
const location = join(dirPrefix, base);
const content = await readirPromise(location);
// If there is only one file in it that is a dir we'll use it as dist dir
if (content.length === 1 && (await isDir(join(location, content[0])))) {
return join(base, content[0]);
}
return base;
},
defaultRoutes: [
{
handle: 'filesystem',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
},
{
name: 'Polymer',
dependency: 'polymer-cli',
getOutputDirName: async (dirPrefix: string) => {
const base = 'build';
const location = join(dirPrefix, base);
const content = await readirPromise(location);
const paths = content.filter(item => !item.includes('.'));
return join(base, paths[0]);
},
defaultRoutes: [
{
handle: 'filesystem',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
},
{
name: 'Svelte',
dependency: 'sirv-cli',
getOutputDirName: async () => 'public',
defaultRoutes: [
{
handle: 'filesystem',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
},
{
name: 'Create React App',
dependency: 'react-scripts',
getOutputDirName: async () => 'build',
defaultRoutes: [
{
src: '/static/(.*)',
headers: { 'cache-control': 's-maxage=31536000, immutable' },
dest: '/static/$1',
},
{
src: '/favicon.ico',
dest: '/favicon.ico',
},
{
src: '/asset-manifest.json',
dest: '/asset-manifest.json',
},
{
src: '/manifest.json',
dest: '/manifest.json',
},
{
src: '/precache-manifest.(.*)',
dest: '/precache-manifest.$1',
},
{
src: '/service-worker.js',
headers: { 'cache-control': 's-maxage=0' },
dest: '/service-worker.js',
},
{
src: '/sockjs-node/(.*)',
dest: '/sockjs-node/$1',
},
{
src: '/(.*)',
headers: { 'cache-control': 's-maxage=0' },
dest: '/index.html',
},
],
},
{
name: 'Gridsome',
dependency: 'gridsome',
getOutputDirName: async () => 'dist',
},
{
name: 'UmiJS',
dependency: 'umi',
getOutputDirName: async () => 'dist',
defaultRoutes: [
{
handle: 'filesystem',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
},
{
name: 'Docusaurus',
dependency: 'docusaurus',
getOutputDirName: async (dirPrefix: string) => {
const base = 'build';
const location = join(dirPrefix, base);
const content = await readirPromise(location);
// If there is only one file in it that is a dir we'll use it as dist dir
if (content.length === 1 && (await isDir(join(location, content[0])))) {
return join(base, content[0]);
}
return base;
},
},
];

View File

@@ -3,6 +3,7 @@ import spawn from 'cross-spawn';
import getPort from 'get-port';
import { timeout } from 'promise-timeout';
import { existsSync, readFileSync, statSync, readdirSync } from 'fs';
import frameworks from './frameworks';
import {
glob,
download,
@@ -12,13 +13,29 @@ import {
getNodeVersion,
getSpawnOptions,
Files,
Route,
BuildOptions,
Config,
} from '@now/build-utils';
interface PackageJson {
scripts?: {
[key: string]: string;
};
dependencies?: {
[key: string]: string;
};
devDependencies?: {
[key: string]: string;
};
}
interface Framework {
name: string;
dependency: string;
getOutputDirName: (dirPrefix: string) => Promise<string>;
defaultRoutes?: Route[];
minNodeRange?: string;
}
function validateDistDir(distDir: string, isDev: boolean | undefined) {
@@ -50,9 +67,16 @@ function validateDistDir(distDir: string, isDev: boolean | undefined) {
}
}
function getCommand(pkg: PackageJson, cmd: string) {
const scripts = (pkg && pkg.scripts) || {};
function getCommand(pkg: PackageJson, cmd: string, config: Config) {
// The `dev` script can be `now dev`
const nowCmd = `now-${cmd}`;
const { zeroConfig } = config;
if (!zeroConfig && cmd === 'dev') {
return nowCmd;
}
const scripts = (pkg && pkg.scripts) || {};
if (scripts[nowCmd]) {
return nowCmd;
@@ -69,6 +93,19 @@ export const version = 2;
const nowDevScriptPorts = new Map();
const getDevRoute = (srcBase: string, devPort: number, route: Route) => {
const basic: Route = {
src: `${srcBase}${route.src}`,
dest: `http://localhost:${devPort}${route.dest}`,
};
if (route.headers) {
basic.headers = route.headers;
}
return basic;
};
export async function build({
files,
entrypoint,
@@ -80,10 +117,9 @@ export async function build({
await download(files, workPath, meta);
const mountpoint = path.dirname(entrypoint);
const entrypointFsDirname = path.join(workPath, mountpoint);
const nodeVersion = await getNodeVersion(entrypointFsDirname);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
const distPath = path.join(
const entrypointDir = path.join(workPath, mountpoint);
let distPath = path.join(
workPath,
path.dirname(entrypoint),
(config && (config.distDir as string)) || 'dist'
@@ -91,25 +127,63 @@ export async function build({
const entrypointName = path.basename(entrypoint);
if (entrypointName.endsWith('.sh')) {
console.log(`Running build script "${entrypoint}"`);
await runShellScript(path.join(workPath, entrypoint));
validateDistDir(distPath, meta.isDev);
return glob('**', distPath, mountpoint);
}
if (entrypointName === 'package.json') {
await runNpmInstall(entrypointFsDirname, ['--prefer-offline'], spawnOpts);
const pkgPath = path.join(workPath, entrypoint);
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
let output: Files = {};
const routes: { src: string; dest: string }[] = [];
const devScript = getCommand(pkg, 'dev');
let framework: Framework | undefined = undefined;
let minNodeRange: string | undefined = undefined;
const routes: Route[] = [];
const devScript = getCommand(pkg, 'dev', config as Config);
if (config.zeroConfig) {
const dependencies = Object.assign(
{},
pkg.dependencies,
pkg.devDependencies
);
framework = frameworks.find(({ dependency }) => dependencies[dependency]);
}
if (framework) {
console.log(
`Detected ${framework.name} framework. Optimizing your deployment...`
);
if (framework.minNodeRange) {
minNodeRange = framework.minNodeRange;
console.log(
`${framework.name} requires Node.js ${
framework.minNodeRange
}. Switching...`
);
} else {
console.log(
`${
framework.name
} does not require a specific Node.js version. Continuing ...`
);
}
}
const nodeVersion = await getNodeVersion(entrypointDir, minNodeRange);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
await runNpmInstall(entrypointDir, ['--prefer-offline'], spawnOpts);
if (meta.isDev && pkg.scripts && pkg.scripts[devScript]) {
let devPort = nowDevScriptPorts.get(entrypoint);
let devPort: number | undefined = nowDevScriptPorts.get(entrypoint);
if (framework && framework.defaultRoutes) {
// We need to delete the routes for `now dev`
// since in this case it will get proxied to
// a custom server we don't have controll over
delete framework.defaultRoutes;
}
if (typeof devPort === 'number') {
console.log(
'`%s` server already running for %j',
@@ -121,10 +195,12 @@ export async function build({
// it will launch a dev server that never "completes"
devPort = await getPort();
nowDevScriptPorts.set(entrypoint, devPort);
const opts = {
cwd: entrypointFsDirname,
cwd: entrypointDir,
env: { ...process.env, PORT: String(devPort) },
};
const child = spawn('npm', ['run', devScript], opts);
child.on('exit', () => nowDevScriptPorts.delete(entrypoint));
child.stdout.setEncoding('utf8');
@@ -160,40 +236,80 @@ export async function build({
}
let srcBase = mountpoint.replace(/^\.\/?/, '');
if (srcBase.length > 0) {
srcBase = `/${srcBase}`;
}
routes.push({
src: `${srcBase}/(.*)`,
dest: `http://localhost:${devPort}/$1`,
});
if (framework && framework.defaultRoutes) {
for (const route of framework.defaultRoutes) {
routes.push(getDevRoute(srcBase, devPort, route));
}
}
routes.push(
getDevRoute(srcBase, devPort, {
src: '/(.*)',
dest: '/$1',
})
);
} else {
if (meta.isDev) {
console.log('WARN: "${devScript}" script is missing from package.json');
console.log(`WARN: "${devScript}" script is missing from package.json`);
console.log(
'See the local development docs: https://zeit.co/docs/v2/deployments/official-builders/static-build-now-static-build/#local-development'
);
}
const buildScript = getCommand(pkg, 'build');
const buildScript = getCommand(pkg, 'build', config as Config);
console.log(`Running "${buildScript}" script in "${entrypoint}"`);
const found = await runPackageJsonScript(
entrypointFsDirname,
entrypointDir,
buildScript,
spawnOpts
);
if (!found) {
throw new Error(
`Missing required "${buildScript}" script in "${entrypoint}"`
);
}
if (framework) {
const outputDirPrefix = path.join(workPath, path.dirname(entrypoint));
const outputDirName = await framework.getOutputDirName(outputDirPrefix);
distPath = path.join(outputDirPrefix, outputDirName);
}
validateDistDir(distPath, meta.isDev);
output = await glob('**', distPath, mountpoint);
if (framework && framework.defaultRoutes) {
routes.push(...framework.defaultRoutes);
}
}
const watch = [path.join(mountpoint.replace(/^\.\/?/, ''), '**/*')];
return { routes, watch, output };
}
throw new Error(
`Build "src" is "${entrypoint}" but expected "package.json" or "build.sh"`
);
if (!config.zeroConfig && entrypointName.endsWith('.sh')) {
console.log(`Running build script "${entrypoint}"`);
const nodeVersion = await getNodeVersion(entrypointDir);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
await runShellScript(path.join(workPath, entrypoint), [], spawnOpts);
validateDistDir(distPath, meta.isDev);
return glob('**', distPath, mountpoint);
}
let message = `Build "src" is "${entrypoint}" but expected "package.json"`;
if (!config.zeroConfig) {
message += ' or "build.sh"';
}
throw new Error(message);
}

View File

@@ -0,0 +1,7 @@
NODEVERSION=$(node --version)
NPMVERSION=$(npm --version)
mkdir dist
echo "node:$NODEVERSION:RANDOMNESS_PLACEHOLDER" >> dist/index.txt
echo "npm:$NPMVERSION:RANDOMNESS_PLACEHOLDER" >> dist/index.txt

View File

@@ -0,0 +1,5 @@
{
"engines": {
"node": "10.x"
}
}

View File

@@ -2,10 +2,12 @@
"version": 2,
"builds": [
{ "src": "some-build.sh", "use": "@now/static-build" },
{ "src": "node10sh/build.sh", "use": "@now/static-build" },
{ "src": "subdirectory/some-build.sh", "use": "@now/static-build" }
],
"probes": [
{ "path": "/", "mustContain": "cow:RANDOMNESS_PLACEHOLDER" },
{ "path": "/node10sh/", "mustContain": "node:v10" },
{ "path": "/subdirectory/", "mustContain": "yoda:RANDOMNESS_PLACEHOLDER" }
]
}

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