Compare commits

...

82 Commits

Author SHA1 Message Date
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
Andy Bitz
489ec1dfa5 Publish
- @now/go@0.5.3
 - @now/next@0.5.1
 - @now/node-bridge@1.2.2
 - @now/node-server@0.8.1
 - @now/node@0.10.1
 - @now/python@0.2.9
 - @now/static-build@0.6.1
2019-06-28 18:32:41 +02:00
Steven
d3f92d7143 [now-static-build] Add missing random to test (#674) 2019-06-28 18:28:38 +02:00
Andy
3072b044ef [now-static-build] Use build and dev command if there is no… (#673)
* [now-static-build] Use `build` and `dev` command if there is no `now-` version

* Fix default dev command

* Get correct command

* Added type

* Add getCommands and replace now-dev occurrences

* Linting

* Added test for build

* Adjusted test

* Adjusted message

* Adjusted tests
2019-06-28 18:28:30 +02:00
Steven
3b4968657f [now-static-build] Improve msg when missing script (#672)
* [now-static-build] Improve msg when missing script

* Add 07-nonzero-sh test fixture

* Use uppercase

* Print bash script name
2019-06-28 18:28:24 +02:00
Steven
cafae4c800 [now-static-build] Use typescript (#671)
* [now-static-build] Use typescript

* Add tsconfig and add missing types

* Move to /src folder

* Fix type error

* Remove accidental commit of tsconfig.json
2019-06-28 18:28:17 +02:00
Luc
64952d24f1 [now-node] Fix res.send() and res.json() helpers (#669)
* fix res.send/res.json discrepancies with express

* add tests and refactor

* throw error on res.json(undefined)

* re-add streams

* do not console.warn

* move hello to fixtures-helpers folder

* be more explicit about accepted types

* setDefaultCT -> setContentType

* improve error messages

* make `fixtures-helpers/hello` appear in the code

* refactor setContentType

* set correct `content-length` header

* use PassThrough stream to remove fixture

* remove `.only` in test
2019-06-28 18:28:10 +02:00
Luc
72758b6e0d [tests] Improve git diff in jest config and refactor (#663)
* improve git diff in jest config

* refactor jest.config.js

* try with empty testMatch

* Revert "try with empty testMatch"

This reverts commit ec69a03cc7953a8e6e2d7b2f3ba2bb08d2fcbfa5.

* Update jest.config.js

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

* trim branch name

* move trim up

* add empty files to test behaviour

* Remove empty files
2019-06-28 18:28:03 +02:00
JJ Kasper
4f80bc74d5 Update now-next for new dynamic syntax (#660) 2019-06-28 18:27:55 +02:00
Sophearak Tha
a6e62ed61c [now-go] Ignore vendor folder when looking for go.mod (#593)
* Ignore `vendor` folder when looking for `go.mod`

* add warning message

* Apply suggestions from code review

Co-Authored-By: Steven <steven@ceriously.com>
2019-06-28 18:27:46 +02:00
Luc
d8eecd6172 Revert "Only run github action on 'release' event (#662)" (#665)
This reverts commit 5ff2c37147eb203f5199ffe4d4aa9480e3ebaa85.
2019-06-28 18:27:37 +02:00
Luc
0e70608511 Only run github action on 'release' event (#662)
* only run github action on 'release' event

* only run master build on 'release' event
2019-06-28 18:27:25 +02:00
Luc
da0de150df add --exact to lerna version (#664) 2019-06-28 18:27:19 +02:00
Luc
a58c35fb9e [now-node-bridge] Use a callback on server.listen() method rather than server.on('listening') (#661)
* rely on the listen method rather than events

* add empty files to trigger tests suites

* improve types

* remove empty files
2019-06-28 18:27:11 +02:00
Nathan Cahill
fe88a69ab7 [now-python] Add Python ASGI support (#654)
* add python asgi support

* Update packages/now-python/test/fixtures/11-asgi/index.py

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

* Update packages/now-python/test/fixtures/11-asgi/now.json

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

* Update packages/now-python/test/fixtures/11-asgi/now.json

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

* add raw_path
2019-06-28 18:27:02 +02:00
Steven
9767682006 Publish
- @now/build-utils@0.7.0
 - @now/go@0.5.2
 - @now/next@0.5.0
 - @now/node-bridge@1.2.1
 - @now/node-server@0.8.0
 - @now/node@0.10.0
 - @now/optipng@0.6.3
 - @now/python@0.2.8
 - @now/ruby@0.1.0
 - @now/rust@0.2.7
 - @now/static-build@0.6.0
2019-06-24 17:02:17 -04:00
Steven
3285b31721 Revert "[now-node] Fix express not overriding helpers properties" (#659)
This reverts commit 7a7d8a55fbbe4f8ee89ce50a3c62816fc95f28f5.
2019-06-24 17:00:18 -04:00
Steven
70353c7fc0 [now-build-utils] Fail if engines is invalid (#658)
* [now-build-utils] Fail if engines does not match

* Add test to throw when invalid semver

* Apply suggestions from leo

Co-Authored-By: Leo Lamprecht <mindrun@icloud.com>

* Fix ts error

* Print range so its clear what to add to engines
2019-06-24 15:50:48 -04:00
Nathan Cahill
f85cf99325 support local pipfile (#652) 2019-06-24 15:50:37 -04:00
Luc
8b14a46d04 Add git diff to publishing steps in readme (#646)
* add `git diff canary` to publishing to stable

* Improve readme
2019-06-24 15:50:32 -04:00
Steven
383cbfd82f [now-build-utils] Remove enginesMatch export (#650)
* [now-build-utils] Remove `enginesMatch` export

* Remove unused import
2019-06-24 15:50:23 -04:00
Steven
81e268a3c9 Add support for node 10 in all builders (#649)
* Add support for node 10 in all builders

* Fix meta undefined
2019-06-24 15:50:16 -04:00
Steven
ac8b33213b [now-build-utils] Enhance node version selection (#648)
* [now-build-utils] Enhance node version selection

* Fix test
2019-06-24 15:50:10 -04:00
piousdeer
de12e7b8c8 [now-static-build] Use cross-spawn and npm for now dev (#639)
* [now-static-build] Use `cross-spawn` and npm

* [now-static-build] Add `cross-spawn` dependency
2019-06-24 15:50:04 -04:00
ywg-jean
b9346603f0 Proposes a contributing guideline (#644)
* proposes a contributing guideline

the guideline outlines the process to open a pull request on this
repository and offers guidance on interpreting test errors.

* Wording improvement

As per @styfle suggestion

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

* Reword reference to code of conduct

As per @styfle's suggestion.

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

* fix local developement section title

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

* Improves project description wording

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

* Proper setup instructions

The ones provided initially were incomplete (might explain why I had some issues :) )

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

* updates description of tests

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

* Improve explanation of integration tests

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

* Update ncc bug qualification section

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

* fixes typo

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

* clarifies when and why to run ncc manually

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

* clarifies how to access to test deployements and associated logs

Co-Authored-By: Steven <steven@ceriously.com>
2019-06-24 15:50:00 -04:00
Luc
0b793dfc35 [now-node] Fix express not overriding helpers properties (#638)
* add breaking test

* fix test

* fix tests (bis)

* be explicit about @ts-ignore

* be more specific in test name
2019-06-24 15:49:52 -04:00
Steven
9dd672c383 [now-node] Change PATH to use node10 (#637)
* [now-node] Change PATH to use node10

* Fallback to node8
2019-06-24 15:49:46 -04:00
Steven
1b743aeea8 [now-ruby] Fix ruby publish step (#642) 2019-06-24 15:49:41 -04:00
Steven
d4af4b9f5c Run prettier (#635) 2019-06-24 15:49:35 -04:00
Steven
b734ca3e01 [now-build-utils] Add spawnOpts param to runNpmInstall() (#634) 2019-06-24 15:49:27 -04:00
Luc
f81d753104 [now-go] Fix failing build when go.mod exists in a subfolder (#633)
* add test

* fix test

* fix 14-go-mod-sub test fixtures

* add index.go to make fixtures correct
2019-06-24 15:49:21 -04:00
Steven
db31b9a207 [tests] Bump typescript to 3.5.2 (#631) 2019-06-24 15:49:07 -04:00
Nathan Cahill
b80b5182e6 [now-ruby] Add @now/ruby Builder (#454)
* add @now/ruby

* changes from feedback

* remove mm

* increase timeout

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

* address changes

* fix linting errors

* support arrays in includeFiles

* undo type change
2019-06-24 15:48:56 -04:00
Steven
268a7c2b81 [now-node] Enable node10.x runtime (#630) 2019-06-24 15:48:48 -04:00
Sophearak Tha
667a16c996 [now-go] add support for nested packages (#623)
* add support for nested packages

* properly clone array
2019-06-24 15:48:20 -04:00
Steven
7b851f81c0 Publish
- @now/build-utils@0.6.0
2019-06-17 14:25:30 -04:00
Steven
80fbbcd194 [now-build-utils] Add enginesMatch export (#618)
* Add engineSatisifies for node

* Rename to enginesMatch

* Add test for node10

* Minor refactor

* Add tests for engines, uses semver.intersect()

* Revert @now/node, new PR later
2019-06-17 14:22:22 -04:00
497 changed files with 83374 additions and 827 deletions

View File

@@ -12,3 +12,6 @@
/packages/now-optipng/dist/*
/packages/now-go/*
/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 @dav-is
/packages/now-go @styfle @sophearak
/packages/now-python @styfle @sophearak
/packages/now-rust @styfle @mike-engel @anmonteiro
/packages/now-ruby @styfle @coetry @nathancahill

85
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,85 @@
# Contributing
When contributing to this repository, please first discuss the change you wish to make via issue or [spectrum](https://spectrum.chat/zeit) with the owners of this repository before submitting a Pull Request.
Please read our [code of conduct](CODE_OF_CONDUCT.md) and follow it in all your interactions with the project.
## Local development
This project is configured in a monorepo pattern where one repo contains multiple npm packages. Dependencies are installed and managed with `yarn`, not `npm` CLI.
To get started, execute the following:
```
git clone https://github.com/zeit/now-builders
yarn install
yarn bootstrap
yarn build
yarn lint
yarn test
```
Make sure all the tests pass before making changes.
## Verifying your change
Once you are done with your changes (we even suggest doing it along the way ), make sure all the test still run by running
```
yarn build && yarn test
```
from the root of the project.
If any test fails, make sure to fix it along with your changes. See [Interpreting test errors](#Interpreting-test-errors) for more information about how the tests are executed, especially the integration tests.
## Pull Request Process
Once you are confident that your changes work properly, open a pull request on the main repository.
The pull request will be reviewed by the maintainers and the tests will be checked by our continuous integration platform.
## Interpreting test errors
There are 2 kinds of tests in this repository Unit tests and Integration tests.
Unit tests are run locally with `jest` and execute quickly because they are testing the smallest units of code.
### Integration tests
Integration tests create deployments to your ZEIT account using the `test` project name. After each test is deployed, the `probes` key is used to check if the response is the expected value. If the value doesn't match, you'll see a message explaining the difference. If the deployment failed to build, you'll see a more generic message like the following:
```
[Error: Fetched page https://test-8ashcdlew.now.sh/root.js does not contain hello Root!. Instead it contains An error occurred with this application.
NO_STATUS_CODE_FRO Response headers:
cache-control=s-maxage=0
connection=close
content-type=text/plain; charset=utf-8
date=Wed, 19 Jun 2019 18:01:37 GMT
server=now
strict-transport-security=max-age=63072000
transfer-encoding=chunked
x-now-id=iad1:hgtzj-1560967297876-44ae12559f95
x-now-trace=iad1]
```
In such cases you can visit the URL of the failed deployment and append `/_logs` so see the build error. In the case above, that would be https://test-8ashcdlew.now.sh/_logs
The logs of this deployment will contain the actual error which may help you to understand what went wrong.
### @zeit/ncc integration
Some of the builders use `@zeit/ncc` to bundle files before deployment. If you suspect an error with the bundling mechanism, you can run the `ncc` CLI with a couple modifications to the test.
For example if an error occurred in `now-node/test/fixtures/08-assets`
```
cd packages/now-node/test/fixtures/08-assets
yarn install
echo 'require("http").createServer(module.exports).listen(3000)' >> index.js
npx @zeit/ncc@0.20.1 build index.js --source-map
node dist
```
This will compile the test with the specific version of `ncc` and run the resulting file. If it fails here, then there is likely a bug in `ncc` and not the Builder.

View File

@@ -23,7 +23,11 @@ For the Canary Channel, publish the modified Builders to npm with the following:
yarn publish-canary
```
For the Stable Channel, you must cherry pick each commit from canary to master and then deploy the modified Builders:
For the Stable Channel, you must do the following:
- Cherry pick each commit from canary to master
- Verify that you are _in-sync_ with canary (with the exception of the `version` line in `package.json`)
- Deploy the modified Builders
```
git checkout master
@@ -33,6 +37,7 @@ git cherry-pick <PR502_COMMIT_SHA>
git cherry-pick <PR503_COMMIT_SHA>
git cherry-pick <PR504_COMMIT_SHA>
# ... etc ...
git diff origin/canary
yarn publish-stable
```
@@ -41,3 +46,7 @@ After running this publish step, GitHub Actions will take care of publishing the
If for some reason GitHub Actions fails to publish the npm package, you may do so
manually by running `npm publish` from the package directory. Make sure to
use `npm publish --tag canary` if you are publishing a canary release!
### Contributing
See the [Contribution guidelines for this project](CONTRIBUTING.md), it also contains guidance on interpreting tests failures.

View File

@@ -1,33 +1,29 @@
const { execSync } = require('child_process');
const { relative } = require('path');
const branch = execSync('git branch | grep "*" | cut -d " " -f2').toString();
const branch = execSync('git branch | grep "*" | cut -d " " -f2')
.toString()
.trim();
console.log(`Running tests on branch "${branch}"`);
const base = branch === 'master' ? 'HEAD~1' : 'origin/canary';
const diff = execSync(`git diff ${base} --name-only`).toString();
const gitPath = branch === 'master' ? 'HEAD~1' : 'origin/canary...HEAD';
const diff = execSync(`git diff ${gitPath} --name-only`).toString();
const changed = diff
.split('\n')
.filter(item => Boolean(item) && item.includes('packages/'))
.map(item => relative('packages', item).split('/')[0]);
const matches = [];
const matches = Array.from(new Set(changed));
if (changed.length > 0) {
console.log('The following packages have changed:');
changed.map((item) => {
matches.push(item);
console.log(item);
return null;
});
} else {
if (matches.length === 0) {
matches.push('now-node');
console.log(`No packages changed, defaulting to ${matches[0]}`);
} else {
console.log('The following packages have changed:');
console.log(matches.join('\n'));
}
const testMatch = Array.from(new Set(matches)).map(
const testMatch = matches.map(
item => `**/${item}/**/?(*.)+(spec|test).[jt]s?(x)`,
);

View File

@@ -12,8 +12,8 @@
"scripts": {
"lerna": "lerna",
"bootstrap": "lerna bootstrap",
"publish-stable": "git checkout master && git pull && lerna version",
"publish-canary": "git checkout canary && git pull && lerna version prerelease --preid canary",
"publish-stable": "git checkout master && git pull && lerna version --exact",
"publish-canary": "git checkout canary && git pull && lerna version prerelease --preid canary --exact",
"publish-from-github": "./.circleci/publish.sh",
"build": "./.circleci/build.sh",
"lint": "eslint .",

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,16 +1,21 @@
{
"name": "@now/build-utils",
"version": "0.5.8",
"version": "0.8.2",
"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",
"directory": "packages/now-build-utils"
},
"scripts": {
"build": "tsc",
"test": "tsc && jest",
"prepublishOnly": "tsc"
},
"dependencies": {
"@types/cross-spawn": "6.0.0",
"async-retry": "1.2.3",
"async-sema": "2.1.4",
"cross-spawn": "6.0.5",
@@ -18,24 +23,22 @@
"fs-extra": "7.0.0",
"glob": "7.1.3",
"into-stream": "5.0.0",
"memory-fs": "0.4.1",
"minimatch": "3.0.4",
"multistream": "2.1.1",
"node-fetch": "2.2.0",
"semver": "6.1.1",
"yazl": "2.4.3"
},
"scripts": {
"build": "tsc",
"test": "tsc && jest",
"prepublish": "tsc"
},
"devDependencies": {
"@types/async-retry": "^1.2.1",
"@types/cross-spawn": "6.0.0",
"@types/end-of-stream": "^1.4.0",
"@types/fs-extra": "^5.0.5",
"@types/glob": "^7.1.1",
"@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0",
"@types/yazl": "^2.4.1",
"execa": "^1.0.0",
"typescript": "3.3.4000"
"typescript": "3.5.2"
}
}

View File

@@ -0,0 +1,63 @@
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', config },
{ src: 'api/**/*.ts', use: '@now/node', 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 },
];
export async function detectBuilder(pkg: PackageJson): Promise<Builder> {
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;
}
}
// By default we'll choose the `static-build` builder
return { src, use: '@now/static-build', config };
}
// Files that match a specific pattern will get ignored
export function ignoreApiFilter(file: string) {
if (file.includes('/.')) {
return false;
}
if (file.includes('/_')) {
return false;
}
return true;
}
export async function detectApiBuilders(
files: string[]
): Promise<Builder[] | null> {
const builds = files.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,249 @@
import { Route } from './types';
import { parse as parsePath } from 'path';
import { ignoreApiFilter } 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 append: string = '';
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) {
query.push(`${name}=$${counter++}`);
if (isLast) {
// We append this to the last one
// to make sure GET params still work
// and are just appended to our GET params
append += `$${counter++}`;
return `([^\\/|\\?]+)\\/?(?:\\?(.*))?`;
}
return `([^\\/]+)`;
} else if (isLast) {
// If it is the last part we want to remove the extension
// and capture everything after that as regex group and append it
const { name: fileName } = parsePath(segment);
append += `$${counter++}`;
return `${fileName}(.*)`;
}
return segment;
}
);
const src = `^/${srcParts.join('/')}$`;
const dest = `/${filePath}${query.length ? '?' : ''}${query.join('&')}${
query.length ? '&' : ''
}${append}`;
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(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

@@ -0,0 +1,53 @@
import { intersects } from 'semver';
import { NodeVersion } from '../types';
const supportedOptions: NodeVersion[] = [
{ major: 10, range: '10.x', runtime: 'nodejs10.x' },
{ major: 8, range: '8.10.x', runtime: 'nodejs8.10' },
];
// This version should match Fargate's default in the PATH
// Today that is Node 8
export const defaultSelection = supportedOptions.find(
o => o.major === 8
) as NodeVersion;
export async function getSupportedNodeVersion(
engineRange?: string,
silent?: boolean
): Promise<NodeVersion> {
let selection = defaultSelection;
if (!engineRange) {
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
// match which will be the newest version of node
selection = o;
return intersects(o.range, engineRange);
});
if (found) {
if (!silent) {
console.log(
'Found `engines` in `package.json`, selecting range: ' +
selection.range
);
}
} else {
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

@@ -4,6 +4,8 @@ import path from 'path';
import spawn from 'cross-spawn';
import { SpawnOptions } from 'child_process';
import { deprecate } from 'util';
import { Meta, PackageJson, NodeVersion } from '../types';
import { getSupportedNodeVersion } from './node-version';
function spawnAsync(
command: string,
@@ -44,19 +46,49 @@ 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;
}
async function scanParentDirs(destPath: string, scriptName?: string) {
export function getSpawnOptions(
meta: Meta,
nodeVersion: NodeVersion
): SpawnOptions {
const opts = {
env: { ...process.env },
};
if (!meta.isDev) {
opts.env.PATH = `/node${nodeVersion.major}/bin:${opts.env.PATH}`;
}
return opts;
}
export async function getNodeVersion(
destPath: string,
minNodeVersion?: string
): Promise<NodeVersion> {
const { packageJson } = await scanParentDirs(destPath, true);
const range =
(packageJson && packageJson.engines && packageJson.engines.node) ||
minNodeVersion;
return getSupportedNodeVersion(range, typeof minNodeVersion !== 'undefined');
}
async function scanParentDirs(destPath: string, readPackageJson = false) {
assert(path.isAbsolute(destPath));
let hasScript = false;
let hasPackageLockJson = false;
let packageJson: PackageJson | undefined;
let currentDestPath = destPath;
// eslint-disable-next-line no-constant-condition
@@ -65,13 +97,8 @@ async function scanParentDirs(destPath: string, scriptName?: string) {
// eslint-disable-next-line no-await-in-loop
if (await fs.pathExists(packageJsonPath)) {
// eslint-disable-next-line no-await-in-loop
if (scriptName) {
const packageJson = JSON.parse(
await fs.readFile(packageJsonPath, 'utf8')
);
hasScript = Boolean(
packageJson.scripts && scriptName && packageJson.scripts[scriptName]
);
if (readPackageJson) {
packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
}
// eslint-disable-next-line no-await-in-loop
hasPackageLockJson = await fs.pathExists(
@@ -85,13 +112,13 @@ async function scanParentDirs(destPath: string, scriptName?: string) {
currentDestPath = newDestPath;
}
return { hasScript, hasPackageLockJson };
return { hasPackageLockJson, packageJson };
}
export async function runNpmInstall(
destPath: string,
args: string[] = [],
cmd?: string
spawnOpts?: SpawnOptions
) {
assert(path.isAbsolute(destPath));
@@ -99,24 +126,26 @@ export async function runNpmInstall(
console.log(`installing to ${destPath}`);
const { hasPackageLockJson } = await scanParentDirs(destPath);
const opts: SpawnOptions = {
env: {
...process.env,
},
};
const opts = spawnOpts || { env: process.env };
if (hasPackageLockJson) {
commandArgs = args.filter(a => a !== '--prefer-offline');
await spawnAsync(
cmd || 'npm',
'npm',
commandArgs.concat(['install', '--unsafe-perm']),
destPath,
opts
);
} else {
await spawnAsync(
cmd || 'yarn',
commandArgs.concat(['--ignore-engines', '--cwd', destPath]),
'yarn',
commandArgs.concat([
'--ignore-engines',
'--mutex',
'network',
'--cwd',
destPath,
]),
destPath,
opts
);
@@ -129,9 +158,15 @@ export async function runPackageJsonScript(
opts?: SpawnOptions
) {
assert(path.isAbsolute(destPath));
const { hasScript, hasPackageLockJson } = await scanParentDirs(
const { packageJson, hasPackageLockJson } = await scanParentDirs(
destPath,
scriptName
true
);
const hasScript = Boolean(
packageJson &&
packageJson.scripts &&
scriptName &&
packageJson.scripts[scriptName]
);
if (!hasScript) return false;
@@ -152,7 +187,7 @@ export async function runPackageJsonScript(
}
/**
* installDependencies() is deprecated.
* @deprecate installDependencies() is deprecated.
* Please use runNpmInstall() instead.
*/
export const installDependencies = deprecate(

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';
@@ -20,17 +11,18 @@ import {
runPackageJsonScript,
runNpmInstall,
runShellScript,
getNodeVersion,
getSpawnOptions,
} 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,
@@ -42,10 +34,13 @@ export {
runPackageJsonScript,
runNpmInstall,
runShellScript,
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[];
@@ -162,3 +181,34 @@ export interface ShouldServeOptions {
*/
config: Config;
}
export interface PackageJson {
name: string;
version: string;
engines?: {
[key: string]: string;
node: string;
npm: string;
};
scripts?: {
[key: string]: string;
};
dependencies?: {
[key: string]: string;
};
devDependencies?: {
[key: string]: string;
};
}
export interface NodeVersion {
major: number;
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

@@ -6,12 +6,22 @@ const execa = require('execa');
const assert = require('assert');
const { glob, download } = require('../');
const { createZip } = require('../dist/lambda');
const {
getSupportedNodeVersion,
defaultSelection,
} = require('../dist/fs/node-version');
const {
packAndDeploy,
testDeployment,
} = require('../../../test/lib/deployment/test-deployment.js');
const {
detectBuilder,
detectApiBuilders,
detectApiRoutes,
} = require('../dist');
jest.setTimeout(4 * 60 * 1000);
const builderUrl = '@canary';
let buildUtilsUrl;
@@ -64,6 +74,52 @@ it('should create zip files with symlinks properly', async () => {
assert(aStat.isFile());
});
it('should only match supported node versions', () => {
expect(getSupportedNodeVersion('10.x')).resolves.toHaveProperty('major', 10);
expect(getSupportedNodeVersion('8.10.x')).resolves.toHaveProperty('major', 8);
expect(getSupportedNodeVersion('8.11.x')).rejects.toThrow();
expect(getSupportedNodeVersion('6.x')).rejects.toThrow();
expect(getSupportedNodeVersion('999.x')).rejects.toThrow();
expect(getSupportedNodeVersion('foo')).rejects.toThrow();
expect(getSupportedNodeVersion('')).resolves.toBe(defaultSelection);
expect(getSupportedNodeVersion(null)).resolves.toBe(defaultSelection);
expect(getSupportedNodeVersion(undefined)).resolves.toBe(defaultSelection);
});
it('should match all semver ranges', () => {
// See https://docs.npmjs.com/files/package.json#engines
expect(getSupportedNodeVersion('10.0.0')).resolves.toHaveProperty(
'major',
10,
);
expect(getSupportedNodeVersion('10.x')).resolves.toHaveProperty('major', 10);
expect(getSupportedNodeVersion('>=10')).resolves.toHaveProperty('major', 10);
expect(getSupportedNodeVersion('>=10.3.0')).resolves.toHaveProperty(
'major',
10,
);
expect(getSupportedNodeVersion('8.5.0 - 10.5.0')).resolves.toHaveProperty(
'major',
10,
);
expect(getSupportedNodeVersion('>=9.0.0')).resolves.toHaveProperty(
'major',
10,
);
expect(getSupportedNodeVersion('>=9.5.0 <=10.5.0')).resolves.toHaveProperty(
'major',
10,
);
expect(getSupportedNodeVersion('~10.5.0')).resolves.toHaveProperty(
'major',
10,
);
expect(getSupportedNodeVersion('^10.5.0')).resolves.toHaveProperty(
'major',
10,
);
});
// own fixtures
const fixturesPath = path.resolve(__dirname, 'fixtures');
@@ -108,3 +164,107 @@ for (const builder of buildersToTestWith) {
}
}
}
it('Test `detectBuilder`', async () => {
{
const pkg = { dependencies: { next: '1.0.0' } };
const builder = await detectBuilder(pkg);
expect(builder.use).toBe('@now/next');
}
{
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.use).toBe('@now/static-build');
}
});
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');
}
{
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')).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'];
const { defaultRoutes } = await detectApiRoutes(files);
expect(defaultRoutes.length).toBe(2);
}
{
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

@@ -61,6 +61,18 @@ export async function build({
await initPrivateGit(process.env.GIT_CREDENTIALS);
}
if (process.env.GO111MODULE) {
console.log(`\nManually assigning 'GO111MODULE' is not recommended.
By default:
- 'GO111MODULE=on' If entrypoint package name is not 'main'
- 'GO111MODULE=off' If entrypoint package name is 'main'
We highly recommend you leverage Go Modules in your project.
Learn more: https://github.com/golang/go/wiki/Modules
`);
}
console.log('Downloading user files...');
const entrypointArr = entrypoint.split(sep);
@@ -108,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',
@@ -128,18 +147,18 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
const entrypointDirname = dirname(downloadedFiles[entrypoint].fsPath);
let isGoModExist = false;
let goModPath = '';
let goModPathArr: string[] = [];
let isGoModInRootDir = false;
for (const file of Object.keys(downloadedFiles)) {
const fileDirname = dirname(downloadedFiles[file].fsPath);
if (file === 'go.mod') {
isGoModExist = true;
isGoModInRootDir = true;
goModPath = fileDirname;
goModPathArr = goModPath.split(sep);
} else if (file.includes('go.mod')) {
isGoModExist = true;
} else if (file.endsWith('go.mod') && !file.endsWith('vendor')) {
if (entrypointDirname === fileDirname) {
isGoModExist = true;
goModPath = fileDirname;
goModPathArr = goModPath.split(sep);
break;
}
}
}
@@ -161,6 +180,10 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
`Found exported function "${handlerFunctionName}" in "${entrypoint}"`
);
if (!isGoModExist && 'vendor' in downloadedFiles) {
throw new Error('`go.mod` is required to use a `vendor` directory.');
}
// check if package name other than main
// using `go.mod` way building the handler
const packageName = parsedAnalyzed.packageName;
@@ -202,14 +225,28 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
if (isGoModExist) {
const goModContents = await readFile(join(goModPath, 'go.mod'), 'utf8');
const usrModName = goModContents.split('\n')[0].split(' ')[1];
goPackageName = `${usrModName}/${packageName}`;
if (entrypointArr.length > 1 && isGoModInRootDir) {
let cleanPackagePath = [...entrypointArr];
cleanPackagePath.pop();
goPackageName = `${usrModName}/${cleanPackagePath.join('/')}`;
} else {
goPackageName = `${usrModName}/${packageName}`;
}
}
const mainModGoContents = modMainGoContents
.replace('__NOW_HANDLER_PACKAGE_NAME', goPackageName)
.replace('__NOW_HANDLER_FUNC_NAME', goFuncName);
if (goModPathArr.length > 1) {
if (meta.isDev && isGoModExist && isGoModInRootDir) {
await writeFile(
join(dirname(downloadedFiles['now.json'].fsPath), mainModGoFileName),
mainModGoContents
);
} else if (isGoModExist && isGoModInRootDir) {
await writeFile(join(srcPath, mainModGoFileName), mainModGoContents);
} else if (isGoModExist && !isGoModInRootDir) {
// using `go.mod` path to write main__mod__.go
await writeFile(join(goModPath, mainModGoFileName), mainModGoContents);
} else {
@@ -252,21 +289,28 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
throw err;
}
let baseGoModPath = '';
if (meta.isDev && isGoModExist && isGoModInRootDir) {
baseGoModPath = dirname(downloadedFiles['now.json'].fsPath);
} else if (isGoModExist && isGoModInRootDir) {
baseGoModPath = srcPath;
} else if (isGoModExist && !isGoModInRootDir) {
baseGoModPath = goModPath;
} else {
baseGoModPath = entrypointDirname;
}
if (meta.isDev) {
let entrypointDir = entrypointDirname;
if (goModPathArr.length > 1) {
entrypointDir = goModPath;
}
const isGoModBk = await pathExists(join(entrypointDir, 'go.mod.bk'));
const isGoModBk = await pathExists(join(baseGoModPath, 'go.mod.bk'));
if (isGoModBk) {
await move(
join(entrypointDir, 'go.mod.bk'),
join(entrypointDir, 'go.mod'),
join(baseGoModPath, 'go.mod.bk'),
join(baseGoModPath, 'go.mod'),
{ overwrite: true }
);
await move(
join(entrypointDir, 'go.sum.bk'),
join(entrypointDir, 'go.sum'),
join(baseGoModPath, 'go.sum.bk'),
join(baseGoModPath, 'go.sum'),
{ overwrite: true }
);
}
@@ -283,8 +327,7 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
console.log('Running `go build`...');
const destPath = join(outDir, 'handler');
const isGoModInRootDir = goModPathArr.length === 1;
const baseGoModPath = isGoModInRootDir ? entrypointDirname : goModPath;
try {
let src = [join(baseGoModPath, mainModGoFileName)];

View File

@@ -1,7 +1,8 @@
{
"name": "@now/go",
"version": "0.5.1",
"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",
@@ -31,6 +32,6 @@
"@types/fs-extra": "^5.0.5",
"@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0",
"typescript": "^3.4.2"
"typescript": "3.5.2"
}
}

View File

@@ -0,0 +1,12 @@
package nested
import (
"fmt"
"net/http"
"with-nested/shared"
)
// Handler func
func Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, shared.Say("lol:RANDOMNESS_PLACEHOLDER"))
}

View File

@@ -0,0 +1,3 @@
module with-nested
go 1.12

View File

@@ -0,0 +1,5 @@
{
"version": 2,
"builds": [{ "src": "api/nested/*.go", "use": "@now/go" }],
"probes": [{ "path": "/api/nested", "mustContain": "RANDOMNESS_PLACEHOLDER" }]
}

View File

@@ -0,0 +1,6 @@
package shared
// Say func
func Say(text string) string {
return text
}

View File

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

View File

@@ -0,0 +1,5 @@
{
"version": 2,
"builds": [{ "src": "api/**/*.go", "use": "@now/go" }],
"probes": [{ "path": "/api", "mustContain": "RANDOMNESS_PLACEHOLDER" }]
}

View File

@@ -0,0 +1,3 @@
module other-folder
go 1.12

View File

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

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.4.2",
"version": "0.5.3",
"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",
@@ -14,7 +15,7 @@
"directory": "packages/now-next"
},
"dependencies": {
"@now/node-bridge": "^1.2.0",
"@now/node-bridge": "1.2.2",
"fs-extra": "^7.0.0",
"get-port": "^5.0.0",
"resolve-from": "^5.0.0",
@@ -28,6 +29,6 @@
"@types/resolve-from": "^5.0.1",
"@types/semver": "^6.0.0",
"jest": "^24.7.1",
"typescript": "^3.4.3"
"typescript": "3.5.2"
}
}

View File

@@ -21,6 +21,8 @@ import {
PrepareCacheOptions,
runNpmInstall,
runPackageJsonScript,
getNodeVersion,
getSpawnOptions,
} from '@now/build-utils';
import nextLegacyVersions from './legacy-versions';
@@ -38,6 +40,7 @@ import {
validateEntrypoint,
normalizePage,
getDynamicRoutes,
isDynamicRoute,
} from './utils';
interface BuildParamsMeta {
@@ -162,6 +165,8 @@ export const build = async ({
watch?: string[];
childProcesses: ChildProcess[];
}> => {
process.env.__NEXT_BUILDER_EXPERIMENTAL_TARGET = 'serverless';
validateEntrypoint(entrypoint);
const entryDirectory = path.dirname(entrypoint);
@@ -171,6 +176,9 @@ 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);
@@ -180,17 +188,13 @@ export const build = async ({
);
}
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
if (!urls[entrypoint]) {
console.log(`${name} Installing dependencies...`);
await runNpmInstall(entryPath, ['--prefer-offline']);
await runNpmInstall(entryPath, ['--prefer-offline'], spawnOpts);
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = 'development';
@@ -277,20 +281,21 @@ export const build = async ({
}
console.log('installing dependencies...');
await runNpmInstall(entryPath, ['--prefer-offline']);
await runNpmInstall(entryPath, ['--prefer-offline'], spawnOpts);
console.log('running user script...');
const memoryToConsume = Math.floor(os.totalmem() / 1024 ** 2) - 128;
await runPackageJsonScript(entryPath, 'now-build', {
env: {
...process.env,
NODE_OPTIONS: `--max_old_space_size=${memoryToConsume}`,
},
} as SpawnOptions);
const env = { ...spawnOpts.env } as any;
env.NODE_OPTIONS = `--max_old_space_size=${memoryToConsume}`;
await runPackageJsonScript(entryPath, 'now-build', { ...spawnOpts, env });
if (isLegacy) {
console.log('running npm install --production...');
await runNpmInstall(entryPath, ['--prefer-offline', '--production']);
await runNpmInstall(
entryPath,
['--prefer-offline', '--production'],
spawnOpts
);
}
if (process.env.NPM_AUTH_TOKEN) {
@@ -381,7 +386,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}"`);
})
@@ -407,7 +412,7 @@ export const build = async ({
const pathname = page.replace(/\.html$/, '');
if (pathname.startsWith('$') || pathname.includes('/$')) {
if (isDynamicRoute(pathname)) {
dynamicPages.push(pathname);
}
@@ -454,7 +459,7 @@ export const build = async ({
const pathname = page.replace(/\.js$/, '');
if (pathname.startsWith('$') || pathname.includes('/$')) {
if (isDynamicRoute(pathname)) {
dynamicPages.push(normalizePage(pathname));
}
@@ -466,7 +471,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

@@ -9,6 +9,14 @@ export interface EnvConfig {
[name: string]: string | undefined;
}
// Identify /[param]/ in route string
const TEST_DYNAMIC_ROUTE = /\/\[[^\/]+?\](?=\/|$)/;
function isDynamicRoute(route: string): boolean {
route = route.startsWith('/') ? route : `/${route}`;
return TEST_DYNAMIC_ROUTE.test(route);
}
/**
* Validate if the entrypoint is allowed to be used
*/
@@ -226,7 +234,7 @@ function getRoutes(
continue;
}
if (pageName.startsWith('$') || pageName.includes('/$')) {
if (isDynamicRoute(pageName)) {
dynamicPages.push(normalizePage(pageName));
}
@@ -351,4 +359,5 @@ export {
stringMap,
syncEnvVars,
normalizePage,
isDynamicRoute,
};

View File

@@ -10,7 +10,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$/));

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

@@ -1,6 +1,6 @@
{
"name": "@now/node-bridge",
"version": "1.2.0",
"version": "1.2.2",
"license": "MIT",
"main": "./index.js",
"repository": {
@@ -21,6 +21,6 @@
"@types/aws-lambda": "8.10.19",
"@types/node": "11.9.4",
"jest": "24.1.0",
"typescript": "3.3.3"
"typescript": "3.5.2"
}
}

View File

@@ -27,6 +27,16 @@ export interface NowProxyResponse {
encoding: string;
}
interface ServerLike {
listen: (
opts: {
host?: string;
port?: number;
},
callback: (this: Server | null) => void
) => Server | void;
}
/**
* If the `http.Server` handler function throws an error asynchronously,
* then it ends up being an unhandled rejection which doesn't kill the node
@@ -92,14 +102,14 @@ function normalizeEvent(
}
export class Bridge {
private server: Server | null;
private server: ServerLike | null;
private listening: Promise<AddressInfo>;
private resolveListening: (info: AddressInfo) => void;
private events: { [key: string]: NowProxyRequest } = {};
private reqIdSeed: number = 1;
private shouldStoreEvents: boolean = false;
constructor(server?: Server, shouldStoreEvents: boolean = false) {
constructor(server?: ServerLike, shouldStoreEvents: boolean = false) {
this.server = null;
this.shouldStoreEvents = shouldStoreEvents;
if (server) {
@@ -116,18 +126,8 @@ export class Bridge {
});
}
setServer(server: Server) {
setServer(server: ServerLike) {
this.server = server;
server.once('listening', () => {
const addr = server.address();
if (typeof addr === 'string') {
throw new Error(`Unexpected string for \`server.address()\`: ${addr}`);
} else if (!addr) {
throw new Error('`server.address()` returned `null`');
} else {
this.resolveListening(addr);
}
});
}
listen() {
@@ -135,10 +135,35 @@ export class Bridge {
throw new Error('Server has not been set!');
}
return this.server.listen({
host: '127.0.0.1',
port: 0,
});
const resolveListening = this.resolveListening;
return this.server.listen(
{
host: '127.0.0.1',
port: 0,
},
function listeningCallback() {
if (!this || typeof this.address !== 'function') {
throw new Error(
'Missing server.address() function on `this` in server.listen()'
);
}
const addr = this.address();
if (!addr) {
throw new Error('`server.address()` returned `null`');
}
if (typeof addr === 'string') {
throw new Error(
`Unexpected string for \`server.address()\`: ${addr}`
);
}
resolveListening(addr);
}
);
}
async launcher(

View File

@@ -1,15 +1,17 @@
const { createLambda } = require('@now/build-utils/lambda.js'); // eslint-disable-line import/no-extraneous-dependencies
const download = require('@now/build-utils/fs/download.js'); // eslint-disable-line import/no-extraneous-dependencies
const FileBlob = require('@now/build-utils/file-blob.js'); // eslint-disable-line import/no-extraneous-dependencies
const FileFsRef = require('@now/build-utils/file-fs-ref.js'); // eslint-disable-line import/no-extraneous-dependencies
const fs = require('fs-extra');
const glob = require('@now/build-utils/fs/glob.js'); // eslint-disable-line import/no-extraneous-dependencies
const path = require('path');
const {
FileBlob,
FileFsRef,
download,
createLambda,
glob,
runNpmInstall,
runPackageJsonScript,
} = require('@now/build-utils/fs/run-user-scripts.js'); // eslint-disable-line import/no-extraneous-dependencies
const { shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
getNodeVersion,
getSpawnOptions,
shouldServe,
} = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
/** @typedef { import('@now/build-utils/file-ref') } FileRef */
/** @typedef {{[filePath: string]: FileRef}} Files */
@@ -38,8 +40,15 @@ async function downloadInstallAndBundle(
console.log("installing dependencies for user's code...");
const entrypointFsDirname = path.join(workPath, path.dirname(entrypoint));
await runNpmInstall(entrypointFsDirname, npmArguments);
return [downloadedFiles, entrypointFsDirname];
const nodeVersion = await getNodeVersion(entrypointFsDirname);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
await runNpmInstall(entrypointFsDirname, npmArguments, spawnOpts);
return {
downloadedFiles,
entrypointFsDirname,
spawnOpts,
nodeVersion,
};
}
async function compile(workPath, downloadedFiles, entrypoint, config) {
@@ -101,9 +110,14 @@ exports.config = {
* @returns {Promise<Files>}
*/
exports.build = async ({
files, entrypoint, config, workPath, meta,
files, entrypoint, config, workPath, meta = {},
}) => {
const [downloadedFiles, entrypointFsDirname] = await downloadInstallAndBundle(
const {
downloadedFiles,
entrypointFsDirname,
spawnOptions,
nodeVersion,
} = await downloadInstallAndBundle(
{
files,
entrypoint,
@@ -114,7 +128,7 @@ exports.build = async ({
);
console.log('running user script...');
await runPackageJsonScript(entrypointFsDirname, 'now-build');
await runPackageJsonScript(entrypointFsDirname, 'now-build', spawnOptions);
console.log('preparing lambda files...');
let preparedFiles;
@@ -143,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: 'nodejs8.10',
runtime,
});
return { [entrypoint]: lambda };

View File

@@ -1,14 +1,15 @@
{
"name": "@now/node-server",
"version": "0.7.5",
"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",
"directory": "packages/now-node-server"
},
"dependencies": {
"@now/node-bridge": "^1.2.0",
"@now/node-bridge": "1.2.2",
"@zeit/ncc": "0.18.5",
"fs-extra": "7.0.1"
},

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,19 +1,19 @@
{
"name": "@now/node",
"version": "0.9.0",
"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",
"directory": "packages/now-node"
},
"dependencies": {
"@now/node-bridge": "^1.2.0",
"@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,11 +26,13 @@
"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.3.3"
"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';
@@ -73,56 +72,127 @@ function getCookieParser(req: NowRequest) {
};
}
function sendStatusCode(res: NowResponse, statusCode: number): NowResponse {
function status(res: NowResponse, statusCode: number): NowResponse {
res.statusCode = statusCode;
return res;
}
function sendData(res: NowResponse, body: any): NowResponse {
if (body === null) {
function setCharset(type: string, charset: string) {
const { parse, format } = require('content-type');
const parsed = parse(type);
parsed.parameters.charset = charset;
return format(parsed);
}
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 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':
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;
}
// 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();
return res;
} else {
// respond
res.end(chunk, encoding);
}
const contentType = res.getHeader('Content-Type');
if (Buffer.isBuffer(body)) {
if (!contentType) {
res.setHeader('Content-Type', 'application/octet-stream');
}
res.setHeader('Content-Length', body.length);
res.end(body);
return res;
}
if (body instanceof Stream) {
if (!contentType) {
res.setHeader('Content-Type', 'application/octet-stream');
}
body.pipe(res);
return res;
}
let str = body;
// Stringify JSON body
if (typeof body === 'object' || typeof body === 'number') {
str = JSON.stringify(body);
res.setHeader('Content-Type', 'application/json; charset=utf-8');
}
res.setHeader('Content-Length', Buffer.byteLength(str));
res.end(str);
return res;
}
function sendJson(res: NowResponse, jsonBody: any): NowResponse {
// Set header to application/json
res.setHeader('Content-Type', 'application/json; charset=utf-8');
function json(req: NowRequest, res: NowResponse, jsonBody: any): NowResponse {
const body = JSON.stringify(jsonBody);
// Use send to handle request
return res.send(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 {
@@ -186,9 +256,9 @@ export function createServerWithHelpers(
setLazyProp<NowRequestQuery>(req, 'query', getQueryParser(req));
setLazyProp<NowRequestBody>(req, 'body', getBodyParser(req, event.body));
res.status = statusCode => sendStatusCode(res, statusCode);
res.send = data => sendData(res, data);
res.json = data => sendJson(res, data);
res.status = statusCode => status(res, statusCode);
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';
@@ -12,11 +11,14 @@ import {
createLambda,
runNpmInstall,
runPackageJsonScript,
getNodeVersion,
getSpawnOptions,
PrepareCacheOptions,
BuildOptions,
shouldServe,
} from '@now/build-utils';
export { NowRequest, NowResponse } from './types';
import { makeLauncher } from './launcher';
interface CompilerConfig {
includeFiles?: string | string[];
@@ -31,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) {
@@ -58,10 +64,12 @@ async function downloadInstallAndBundle({
console.log("installing dependencies for user's code...");
const entrypointFsDirname = join(workPath, dirname(entrypoint));
await runNpmInstall(entrypointFsDirname, ['--prefer-offline']);
const nodeVersion = await getNodeVersion(entrypointFsDirname);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
await runNpmInstall(entrypointFsDirname, ['--prefer-offline'], spawnOpts);
const entrypointPath = downloadedFiles[entrypoint].fsPath;
return { entrypointPath, entrypointFsDirname };
return { entrypointPath, entrypointFsDirname, nodeVersion, spawnOpts };
}
async function compile(
@@ -97,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, {
@@ -178,6 +187,8 @@ export async function build({
const {
entrypointPath,
entrypointFsDirname,
nodeVersion,
spawnOpts,
} = await downloadInstallAndBundle({
files,
entrypoint,
@@ -186,7 +197,7 @@ export async function build({
});
console.log('running user script...');
await runPackageJsonScript(entrypointFsDirname, 'now-build');
await runPackageJsonScript(entrypointFsDirname, 'now-build', spawnOpts);
console.log('compiling entrypoint with ncc...');
const { preparedFiles, watch } = await compile(
@@ -196,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: 'nodejs8.10',
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

@@ -12,6 +12,6 @@ export type NowRequest = IncomingMessage & {
export type NowResponse = ServerResponse & {
send: (body: any) => NowResponse;
json: (body: any) => NowResponse;
json: (jsonBody: any) => NowResponse;
status: (statusCode: number) => NowResponse;
};

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

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end(`RANDOMNESS_PLACEHOLDER:${process.versions.node}`);
};

View File

@@ -0,0 +1,3 @@
{
"name": "missing-engines-key-on-purpose"
}

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end(`RANDOMNESS_PLACEHOLDER:${process.versions.node}`);
};

View File

@@ -0,0 +1,5 @@
{
"engines": {
"node": "10.5.0"
}
}

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end(`RANDOMNESS_PLACEHOLDER:${process.versions.node}`);
};

View File

@@ -0,0 +1,5 @@
{
"engines": {
"node": ">=10.0.0"
}
}

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end(`RANDOMNESS_PLACEHOLDER:${process.versions.node}`);
};

View File

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

View File

@@ -0,0 +1,11 @@
{
"version": 2,
"builds": [{ "src": "**/*.js", "use": "@now/node" }],
"probes": [
{ "path": "/empty", "mustContain": "RANDOMNESS_PLACEHOLDER:8" },
{ "path": "/exact", "mustContain": "RANDOMNESS_PLACEHOLDER:10" },
{ "path": "/greater", "mustContain": "RANDOMNESS_PLACEHOLDER:10" },
{ "path": "/major", "mustContain": "RANDOMNESS_PLACEHOLDER:10" },
{ "path": "/range", "mustContain": "RANDOMNESS_PLACEHOLDER:10" }
]
}

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end(`RANDOMNESS_PLACEHOLDER:${process.versions.node}`);
};

View File

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

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"
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,9 @@
{
"name": "@now/optipng",
"version": "0.6.2",
"version": "0.6.4",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/optipng-now-optipng",
"files": [
"dist"
],
@@ -21,6 +22,6 @@
},
"devDependencies": {
"@types/node": "11.9.4",
"typescript": "3.3.3"
"typescript": "3.5.2"
}
}

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

@@ -2,11 +2,11 @@ from http.server import BaseHTTPRequestHandler
import base64
import json
import inspect
import __NOW_HANDLER_FILENAME
__now_variables = dir(__NOW_HANDLER_FILENAME)
if 'handler' in __now_variables or 'Handler' in __now_variables:
base = __NOW_HANDLER_FILENAME.handler if ('handler' in __now_variables) else __NOW_HANDLER_FILENAME.Handler
if not issubclass(base, BaseHTTPRequestHandler):
@@ -47,76 +47,202 @@ if 'handler' in __now_variables or 'Handler' in __now_variables:
'body': res.text,
}
elif 'app' in __now_variables:
print('using Web Server Gateway Interface (WSGI)')
import sys
from urllib.parse import urlparse, unquote
from werkzeug._compat import BytesIO
from werkzeug._compat import string_types
from werkzeug._compat import to_bytes
from werkzeug._compat import wsgi_encoding_dance
from werkzeug.datastructures import Headers
from werkzeug.wrappers import Response
def now_handler(event, context):
payload = json.loads(event['body'])
if not inspect.iscoroutinefunction(__NOW_HANDLER_FILENAME.app.__call__):
print('using Web Server Gateway Interface (WSGI)')
import sys
from urllib.parse import urlparse, unquote
from werkzeug._compat import BytesIO
from werkzeug._compat import string_types
from werkzeug._compat import to_bytes
from werkzeug._compat import wsgi_encoding_dance
from werkzeug.datastructures import Headers
from werkzeug.wrappers import Response
def now_handler(event, context):
payload = json.loads(event['body'])
headers = Headers(payload.get('headers', {}))
headers = Headers(payload.get('headers', {}))
body = payload.get('body', '')
if body != '':
body = payload.get('body', '')
if body != '':
if payload.get('encoding') == 'base64':
body = base64.b64decode(body)
if isinstance(body, string_types):
body = to_bytes(body, charset='utf-8')
url = urlparse(unquote(payload['path']))
query = url.query
path = url.path
environ = {
'CONTENT_LENGTH': str(len(body)),
'CONTENT_TYPE': headers.get('content-type', ''),
'PATH_INFO': path,
'QUERY_STRING': query,
'REMOTE_ADDR': headers.get(
'x-forwarded-for', headers.get(
'x-real-ip', payload.get(
'true-client-ip', ''))),
'REQUEST_METHOD': payload['method'],
'SERVER_NAME': headers.get('host', 'lambda'),
'SERVER_PORT': headers.get('x-forwarded-port', '80'),
'SERVER_PROTOCOL': 'HTTP/1.1',
'event': event,
'context': context,
'wsgi.errors': sys.stderr,
'wsgi.input': BytesIO(body),
'wsgi.multiprocess': False,
'wsgi.multithread': False,
'wsgi.run_once': False,
'wsgi.url_scheme': headers.get('x-forwarded-proto', 'http'),
'wsgi.version': (1, 0),
}
for key, value in environ.items():
if isinstance(value, string_types) and key != 'QUERY_STRING':
environ[key] = wsgi_encoding_dance(value)
for key, value in headers.items():
key = 'HTTP_' + key.upper().replace('-', '_')
if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
environ[key] = value
response = Response.from_app(__NOW_HANDLER_FILENAME.app, environ)
return_dict = {
'statusCode': response.status_code,
'headers': dict(response.headers)
}
if response.data:
return_dict['body'] = base64.b64encode(response.data).decode('utf-8')
return_dict['encoding'] = 'base64'
return return_dict
else:
print('using Asynchronous Server Gateway Interface (ASGI)')
import asyncio
import enum
from urllib.parse import urlparse, unquote, urlencode
class ASGICycleState(enum.Enum):
REQUEST = enum.auto()
RESPONSE = enum.auto()
class ASGICycle:
def __init__(self, scope):
self.scope = scope
self.body = b''
self.state = ASGICycleState.REQUEST
self.app_queue = None
self.response = {}
def __call__(self, app, body):
"""
Receives the application and any body included in the request, then builds the
ASGI instance using the connection scope.
Runs until the response is completely read from the application.
"""
loop = asyncio.new_event_loop()
self.app_queue = asyncio.Queue(loop=loop)
self.put_message({'type': 'http.request', 'body': body, 'more_body': False})
asgi_instance = app(self.scope, self.receive, self.send)
asgi_task = loop.create_task(asgi_instance)
loop.run_until_complete(asgi_task)
return self.response
def put_message(self, message):
self.app_queue.put_nowait(message)
async def receive(self):
"""
Awaited by the application to receive messages in the queue.
"""
message = await self.app_queue.get()
return message
async def send(self, message):
"""
Awaited by the application to send messages to the current cycle instance.
"""
message_type = message['type']
if self.state is ASGICycleState.REQUEST:
if message_type != 'http.response.start':
raise RuntimeError(
f"Expected 'http.response.start', received: {message_type}"
)
status_code = message['status']
headers = {k: v for k, v in message.get('headers', [])}
self.on_request(headers, status_code)
self.state = ASGICycleState.RESPONSE
elif self.state is ASGICycleState.RESPONSE:
if message_type != 'http.response.body':
raise RuntimeError(
f"Expected 'http.response.body', received: {message_type}"
)
body = message.get('body', b'')
more_body = message.get('more_body', False)
# The body must be completely read before returning the response.
self.body += body
if not more_body:
self.on_response()
self.put_message({'type': 'http.disconnect'})
def on_request(self, headers, status_code):
self.response['statusCode'] = status_code
self.response['headers'] = {k.decode(): v.decode() for k, v in headers.items()}
def on_response(self):
if self.body:
self.response['body'] = base64.b64encode(self.body).decode('utf-8')
self.response['encoding'] = 'base64'
def now_handler(event, context):
payload = json.loads(event['body'])
headers = payload.get('headers', {})
body = payload.get('body', b'')
if payload.get('encoding') == 'base64':
body = base64.b64decode(body)
if isinstance(body, string_types):
body = to_bytes(body, charset='utf-8')
elif not isinstance(body, bytes):
body = body.encode()
url = urlparse(unquote(payload['path']))
query = url.query
path = url.path
url = urlparse(unquote(payload['path']))
query = url.query.encode()
path = url.path
environ = {
'CONTENT_LENGTH': str(len(body)),
'CONTENT_TYPE': headers.get('content-type', ''),
'PATH_INFO': path,
'QUERY_STRING': query,
'REMOTE_ADDR': headers.get(
'x-forwarded-for', headers.get(
'x-real-ip', payload.get(
'true-client-ip', ''))),
'REQUEST_METHOD': payload['method'],
'SERVER_NAME': headers.get('host', 'lambda'),
'SERVER_PORT': headers.get('x-forwarded-port', '80'),
'SERVER_PROTOCOL': 'HTTP/1.1',
'event': event,
'context': context,
'wsgi.errors': sys.stderr,
'wsgi.input': BytesIO(body),
'wsgi.multiprocess': False,
'wsgi.multithread': False,
'wsgi.run_once': False,
'wsgi.url_scheme': headers.get('x-forwarded-proto', 'http'),
'wsgi.version': (1, 0),
}
scope = {
'server': (headers.get('host', 'lambda'), headers.get('x-forwarded-port', 80)),
'client': (headers.get(
'x-forwarded-for', headers.get(
'x-real-ip', payload.get(
'true-client-ip', ''))), 0),
'scheme': headers.get('x-forwarded-proto', 'http'),
'root_path': '',
'query_string': query,
'headers': [[k.lower().encode(), v.encode()] for k, v in headers.items()],
'type': 'http',
'http_version': '1.1',
'method': payload['method'],
'path': path,
'raw_path': path.encode(),
}
for key, value in environ.items():
if isinstance(value, string_types) and key != 'QUERY_STRING':
environ[key] = wsgi_encoding_dance(value)
asgi_cycle = ASGICycle(scope)
response = asgi_cycle(__NOW_HANDLER_FILENAME.app, body)
return response
for key, value in headers.items():
key = 'HTTP_' + key.upper().replace('-', '_')
if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
environ[key] = value
response = Response.from_app(__NOW_HANDLER_FILENAME.app, environ)
return_dict = {
'statusCode': response.status_code,
'headers': dict(response.headers)
}
if response.data:
return_dict['body'] = base64.b64encode(response.data).decode('utf-8')
return_dict['encoding'] = 'base64'
return return_dict
else:
print('Missing variable `handler` or `app` in file __NOW_HANDLER_FILENAME.py')
print('See the docs https://zeit.co/docs/v2/deployments/official-builders/python-now-python')

View File

@@ -1,8 +1,9 @@
{
"name": "@now/python",
"version": "0.2.7",
"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"
@@ -23,6 +24,6 @@
},
"devDependencies": {
"@types/execa": "^0.9.0",
"typescript": "3.3.4000"
"typescript": "3.5.2"
}
}

View File

@@ -92,19 +92,26 @@ 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);
workPath = destNow;
}
const foundLockFile = 'Pipfile.lock' in downloadedFiles;
const pyUserBase = await getWriteableDirectory();
process.env.PYTHONUSERBASE = pyUserBase;
const pipPath = 'pip3';
@@ -129,17 +136,25 @@ export const build = async ({
await pipInstall(pipPath, workPath, 'werkzeug');
await pipInstall(pipPath, workPath, 'requests');
if (foundLockFile) {
let fsFiles = await glob('**', workPath);
const entryDirectory = dirname(entrypoint);
const pipfileLockDir = fsFiles[join(entryDirectory, 'Pipfile.lock')]
? join(workPath, entryDirectory)
: fsFiles['Pipfile.lock']
? workPath
: null;
if (pipfileLockDir) {
console.log('found "Pipfile.lock"');
// Install pipenv.
await pipInstallUser(pipPath, ' pipenv_to_requirements');
await pipenvInstall(pyUserBase, workPath);
await pipenvInstall(pyUserBase, pipfileLockDir);
}
const fsFiles = await glob('**', workPath);
const entryDirectory = dirname(entrypoint);
fsFiles = await glob('**', workPath);
const requirementsTxt = join(entryDirectory, 'requirements.txt');
if (fsFiles[requirementsTxt]) {

View File

@@ -0,0 +1,12 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
cowpy = "*"
[requires]
python_version = "3.7"

View File

@@ -0,0 +1,29 @@
{
"_meta": {
"hash": {
"sha256": "841b49dd1836f7373490faa2a1d6abb4ecd53c0b45e8196df7c6e852e21f458b"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"cowpy": {
"hashes": [
"sha256:1bdc61d107df02fd34a9241f2220d0704a01d8ce16bed8bff3512a34a0efa56a",
"sha256:91a861bfbfa644dfdba5b2250d141b2227a94f61d4dcbeaf7653524d048935a9"
],
"index": "pypi",
"version": "==1.1.0"
}
},
"develop": {}
}

View File

@@ -0,0 +1,13 @@
from http.server import BaseHTTPRequestHandler
from cowpy import cow
class handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
message = cow.Cowacter().milk('pip:RANDOMNESS_PLACEHOLDER')
self.wfile.write(message.encode())
return

View File

@@ -0,0 +1,5 @@
{
"version": 2,
"builds": [{ "src": "**/**.py", "use": "@now/python" }],
"probes": [{ "path": "/", "mustContain": "pip:RANDOMNESS_PLACEHOLDER" }]
}

View File

@@ -0,0 +1,12 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
cowpy = "*"
[requires]
python_version = "3.7"

View File

@@ -0,0 +1,29 @@
{
"_meta": {
"hash": {
"sha256": "841b49dd1836f7373490faa2a1d6abb4ecd53c0b45e8196df7c6e852e21f458b"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"cowpy": {
"hashes": [
"sha256:1bdc61d107df02fd34a9241f2220d0704a01d8ce16bed8bff3512a34a0efa56a",
"sha256:91a861bfbfa644dfdba5b2250d141b2227a94f61d4dcbeaf7653524d048935a9"
],
"index": "pypi",
"version": "==1.1.0"
}
},
"develop": {}
}

View File

@@ -0,0 +1,13 @@
from http.server import BaseHTTPRequestHandler
from cowpy import cow
class handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
message = cow.Cowacter().milk('pip:RANDOMNESS_PLACEHOLDER')
self.wfile.write(message.encode())
return

View File

@@ -0,0 +1,5 @@
{
"version": 2,
"builds": [{ "src": "**/**.py", "use": "@now/python" }],
"probes": [{ "path": "/local", "mustContain": "pip:RANDOMNESS_PLACEHOLDER" }]
}

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