Compare commits

...

112 Commits

Author SHA1 Message Date
Steven
d8a5da6a3e Publish
- @now/bash@0.2.0
 - @now/build-utils@0.5.0
 - @now/cgi@0.1.0
 - @now/go@0.4.0
 - @now/html-minifier@1.1.0
 - @now/lambda@0.5.0
 - @now/md@0.5.0
 - @now/mdx-deck@0.5.0
 - @now/next@0.2.0
 - @now/node-bridge@1.1.0
 - @now/node-server@0.6.0
 - @now/node@0.6.0
 - @now/optipng@0.5.0
 - @now/php-bridge@0.5.0
 - @now/php@0.5.0
 - @now/python@0.1.0
 - @now/rust@0.2.0
 - @now/static-build@0.5.0
 - @now/wordpress@0.5.0
2019-04-25 09:19:19 -04:00
Leo Lamprecht
48f7b72bb2 Added missing workPath type property (#426) 2019-04-25 15:05:41 +02:00
Nathan Rajlich
8e2d5de446 [now-static-build] Add now-dev package.json script (#418)
* [static-build] Add `now-dev` package.json script

This will cause `now dev` to run the `now-dev` script as defined in the
`package.json` file.

`now dev` sets the `$PORT` env variable for use in the `now-dev` script,
which is expected to launch a file watching server (for example `hugo
dev`) on the specified `$PORT`. `now dev` wait for this port to be bound
before returning the `build()` function with the generated assets.

The `watch` array is just "watch everything in the
`path.dirname(entrypoint)`" so any file changed within that directory
will trigger a "re-sync" of the source files that `now dev` is working
with, which the user spawned static build watcher should pick up on and
return the updated assets.

* Use `routes` instead of globbing the output dir

Proxy passing to the development server is a cleaner approach, since it
removes the race condition about the builder not having knowledge of
when a dev watcher server rebuilds, so it's not possible to know how
long to wait to return outputs upon another call to `build()`.

* Make `watch` be an array

Co-Authored-By: TooTallNate <n@n8.io>

* Fix `promise-timeout` require()

* Inherit from `process.env` for `yarn run`

Otherwise ENOENT happens because there is no `PATH` env var set

* Only validate dist dir in production builds

* Fix `routes` and `watch` output

* Remove dev logging, add better timeout error message

* Populate `routes` upon rebuild

* Pass `meta` to the `download()` function

* Revert version change in `package.json`

* Save devport sooner
2019-04-25 08:41:38 -04:00
Steven
2b3efb06be Revert "[now-node] Add initial Builder v2 API (#424)" (#425)
This reverts commit dc3f112d4f.
2019-04-24 22:16:37 -04:00
Connor Davis
13e57bf68f [now/next] Set env variable directly 2019-04-24 16:57:00 -05:00
Nathan Rajlich
dc3f112d4f [now-node] Add initial Builder v2 API (#424)
This is a less complete version of #400, since it's still a WIP.
2019-04-24 14:46:02 -07:00
Connor Davis
da1c423033 Publish
- @now/next@0.2.0-canary.45
2019-04-24 14:57:51 -05:00
Connor Davis
5e1d58f0e8 Set target env variable (#423) 2019-04-25 04:16:08 +09:00
Leo Lamprecht
8940f7fa33 Publish
- @now/next@0.2.0-canary.44
2019-04-24 17:24:39 +00:00
Leo Lamprecht
0aeecd81d3 Pass meta to download() of @now/next (#422) 2019-04-24 19:23:57 +02:00
Joe Haddad
bd4cb3d2a0 Publish
- @now/next@0.2.0-canary.43
2019-04-24 10:50:02 -04:00
Joe Haddad
b3d14f536d Increase heap size for Next.js builds (#419)
* Increase heap size for Next.js builds

* Save some memory for the system
2019-04-24 23:10:27 +09:00
Leo Lamprecht
445d4d84cb Publish
- @now/build-utils@0.4.41-canary.8
 - @now/next@0.2.0-canary.42
2019-04-24 11:06:31 +00:00
Leo Lamprecht
30d4ec8cbd Make download() dedupe (#416)
* Make `download()` dedupe

* Made it work

* Fixed unused var
2019-04-24 13:05:31 +02:00
Nathan Rajlich
51355c263c [now-build-utils] Allow build options for runPackageJsonScript() (#417)
To allow, for example, setting `env` variable.
2019-04-24 10:23:46 +08:00
Steven
ec6d695f24 Add builder v2 tests to @now/next (#415)
* Add builder v2 tests to @now/next

* Add builder v2 tests

* wip

* Return childProcesses for tests
2019-04-23 18:41:26 -04:00
Leo Lamprecht
da910dc097 Publish
- @now/next@0.2.0-canary.41
2019-04-23 19:33:38 +00:00
Leo Lamprecht
00cb55f953 Make @now/next dev work on macOS (#414)
* Make `@now/next` dev work on macOS

* Fixed resolving

* Correctly use cwd

* Removed useless code
2019-04-23 21:32:56 +02:00
Leo Lamprecht
54ee557187 Publish
- @now/next@0.2.0-canary.40
 - @now/node-server@0.5.4-canary.5
 - @now/node@0.5.4-canary.6
2019-04-23 12:01:05 +00:00
Leo Lamprecht
e9a49f658b Develop Next.js app inside workPath (#410)
* Develop Next.js app inside workPath

* Made file watching work

* Define routes the first time

* Added index handling

* Fixed indexing

* Fixed indexing again

* Removed useless code

* Start server in child process
2019-04-23 13:58:50 +02:00
Steven
f3484bc7c9 [now-node] Bump ncc to 0.18.1 (#411)
* [now-node] Bump ncc to 0.18.1

* Fix unit test
2019-04-22 18:20:53 -04:00
Leo Lamprecht
f76d200fd2 Renamed property that has changed its name (#409)
* Define that initial build is needed

* Removed useless code
2019-04-21 20:30:02 +02:00
Leo Lamprecht
24ba90bfeb Wait for Next.js dev server to run before continuing (#408)
* Wait for server better

* Fixed version
2019-04-21 18:48:21 +02:00
Olli Vanhoja
9f0fdada18 Log HTTP status just after fetch returns in deploymentPost() (#406) 2019-04-20 21:40:38 +03:00
Leo Lamprecht
a987c4b298 Publish
- @now/build-utils@0.4.41-canary.7
 - @now/next@0.2.0-canary.39
 - @now/optipng@0.4.9-canary.1
2019-04-20 00:17:07 +00:00
Leo Lamprecht
a746adda93 Only match absolute paths in Next.js builder (#405) 2019-04-20 02:16:20 +02:00
Mark
6a00f02137 @now/optipng using typescript (#397)
Migration `@now/optipng` to typescript

Co-Authored-By: MAPESO <markdrew53@gmail.com>
2019-04-19 19:08:25 -04:00
Leo Lamprecht
65558f8197 Publish
- @now/build-utils@0.4.41-canary.6
 - @now/next@0.2.0-canary.38
2019-04-19 21:58:05 +00:00
Leo Lamprecht
a8bf77091f Made now-next invoke next dev (#398)
* Made `now-next` invoke `next dev`

* Even cleaner

* Fixed some types

* Fixed dirs

* Start server if initial build

* Added remaining types

* Added missing type

* Made property optional

* Fixed types

* Expose that the Builder is continuous

* Added more details

* Removed useless code

* Made it work

* Removed useless crap

* Added new version

* Synced up

* Refined logging

* Fixed routes

* Correctly parse request paths

* Fixed routes

* Fixed GET parameter issues
2019-04-19 23:54:47 +02:00
Steven
ee179b9b52 Add fetchTokenWithRetry for bad json response (#403)
This is a quick fix for our token problem by attempting to retry the fetch.

> FetchError: invalid json response body at NOW_TOKEN_FACTORY_URL reason: Unexpected token A in JSON at position 0

The root cause is in the token factory because it throws `Confirmation incomplete` but that will be harder to track down and fix.
2019-04-19 12:59:22 -04:00
Nathan Rajlich
b480b07cce [now-build-utils] Add meta object type definition for build() (#399)
* [now-build-utils] Add `meta` object type definition for `build()`

* Add `meta.requestPath`
2019-04-18 19:30:01 -07:00
Steven
fc8452abfd Publish
- @now/next@0.2.0-canary.1
 - @now/node-server@0.5.4-canary.4
 - @now/node@0.5.4-canary.5
2019-04-18 09:12:32 -04:00
Steven
48b6d0ebfc [now-node] Enable source maps so errors show proper stack trace (#391)
This will increase the lambda size a little bit but it will make for a much better user experience when debugging uncaught errors.

I also added a test so we don't regress.
2019-04-17 17:53:49 -04:00
Connor Davis
a3d6cea3c6 Migrate @now/next to Builders v2 + TS (#379) 2019-04-16 17:24:26 -05:00
Steven
8a61b1b513 Publish
- @now/build-utils@0.4.41-canary.5
 - @now/next@0.1.3-canary.15
 - @now/node@0.5.4-canary.4
2019-04-16 10:53:49 -04:00
Sophearak Tha
50e648d28a [@now/node] Make sure includeFiles have proper path inside lambda (#387)
Fixes #381 

Demo(public): https://now-node-fs.sophearak.now.sh/
2019-04-16 09:41:04 -04:00
Amio
52994bfe26 [build-utils] add default shouldServe and type ShouldServeOptions (#382)
Added the `ShouldServeParam` type declaration and default implementation for `shouldServe` for builder maker to import.
2019-04-16 19:51:51 +08:00
Connor Davis
1339f17585 Update index.js 2019-04-16 03:30:33 -05:00
Nathan Rajlich
9dd12cf1a7 Publish
- @now/build-utils@0.4.41-canary.4
2019-04-15 18:18:37 -07:00
Nathan Rajlich
6dab09f38e [now-build-utils] Create zip files with symlinks properly (#388)
After #359, creating zip files with symlinks would be corrupted because
the "target" of the symlink was actually the file contents.

This commit fixes the zip file construction logic to handle symlinks by
placing the target of the symlink as the "contents" of the file in the
zip. The unit test relies on GNU `unzip` and checks that it creates the
symlink from the produced zip file as expected.
2019-04-15 12:32:28 -07:00
Amio
c79d7be591 Mute stdio for runNpmInstall by default (#338)
Output logs if error occurs
2019-04-16 00:13:51 +08:00
Joe Haddad
9af3425d6d Publish
- @now/build-utils@0.4.41-canary.3
 - @now/next@0.1.3-canary.14
 - @now/node-bridge@1.0.2-canary.2
 - @now/node-server@0.5.4-canary.3
 - @now/node@0.5.4-canary.3
 - @now/php@0.4.17-canary.1
 - @now/python@0.1.0-canary.2
2019-04-15 08:31:34 +09:00
Igor Klopov
0700c16504 now/node: fix 07-content-length (#392) 2019-04-14 18:55:42 -04:00
Joe Haddad
4e55d9f709 Add a mutable flag to FileRef (#389)
* Add an immutable flag to FileRef

* `immutable` => `mutable`

* Add missing `assert` statement
2019-04-15 07:13:05 +09:00
Joe Haddad
945eb24bdc [now-next] Cache the new Next.js cache location (#386) 2019-04-11 16:10:45 -05:00
Connor Davis
c884102401 [now/next] cache everything in node_modules 2019-04-11 13:42:20 -05:00
Pete Nykänen
36e79efd7f Fix typo in Python builder (#383)
Simple typo fix
2019-04-11 11:29:20 -04:00
Steven
21ee0f3707 Publish
- @now/php@0.4.17-canary.0
2019-04-10 17:48:45 -04:00
Ties
ea5d3b8e80 [@now/php] Added includeFiles config (#326)
This is a retry of https://github.com/zeit/now-builders/pull/37, where the git history is a mess.
2019-04-10 17:44:18 -04:00
Connor Davis
301e0f216b Publish 2019-04-10 16:23:55 -05:00
Sophearak Tha
7a6fbd8c3d [now/python] Add django test (#378)
This PR added 08-django test.
2019-04-10 11:28:02 -04:00
Joe Haddad
77e7a0f502 [now-next] Cache Flying Shuttle directory (#380) 2019-04-09 22:53:41 -05:00
Nathan Rajlich
6bc42bbce9 Publish
- @now/build-utils@0.4.41-canary.2
2019-04-09 18:14:11 -07:00
Nathan Rajlich
de88969c46 [now-build-utils] Set the mode when creating the file, not afterwards (#375)
* [now-build-utils] Set the `mode` when creating the file, not afterwards

This is the more proper fix for #373.

* Use `parseInt()`

For some reason, `@types/node` allows string for `chmod()`
but not for `createWriteStream()`.

* Use a bitmask

See:

 - https://github.com/nodejs/node-v0.x-archive/issues/3045
 - https://www.martin-brennan.com/nodejs-file-permissions-fstat/
2019-04-09 11:17:23 -07:00
Igor Klopov
e86cd38787 Publish
- @now/node@0.5.4-canary.2
 - @now/node-server@0.5.4-canary.2
2019-04-09 20:41:20 +03:00
Igor Klopov
dc1badc931 [now-node] Move @zeit/ncc to npm dependencies (#370)
* now/node: move ncc to deps of package.json

* use require(ncc) instead of import (temporarily)

* do the same for now/node-server
2019-04-09 09:43:18 -07:00
Igor Klopov
ed3c176f5c Publish
- @now/node@0.5.4-canary.1
 - @now/node-server@0.5.4-canary.1
2019-04-09 18:09:37 +03:00
Igor Klopov
749ee5264c Publish
- @now/node-bridge@1.0.2-canary.1
2019-04-09 17:40:56 +03:00
Igor Klopov
9808ea1d8f Publish
- @now/build-utils@0.4.41-canary.1
2019-04-09 12:54:17 +03:00
Nathan Rajlich
a77e7109c7 [now-node-bridge] Exit the Node.js process upon unhandledRejection (#372)
If the `http.Server` handler function throws an error asynchronously,
then it ends up being an unhandled rejection which doesn't kill the node
process which causes the HTTP request to hang indefinitely. So print the
error here and force the process to exit so that the lambda invocation
returns an Unhandled error quickly.
2019-04-09 11:40:08 +03:00
Connor Davis
3b87c7ca83 Fix subscribe pattern 2019-04-08 21:43:28 -05:00
Connor Davis
1887df779a Handle static files in now dev (#369) 2019-04-08 21:41:34 -05:00
Steven
daccd0d8fc Publish
- @now/bash@0.1.5-canary.1
 - @now/build-utils@0.4.41-canary.0
 - @now/go@0.3.1-canary.3
 - @now/mdx-deck@0.4.19-canary.2
 - @now/next@0.1.3-canary.9
 - @now/node-bridge@1.0.2-canary.0
 - @now/node-server@0.5.4-canary.0
 - @now/node@0.5.4-canary.0
 - @now/python@0.1.0-canary.1
 - @now/rust@0.1.2-canary.2
 - @now/static-build@0.4.19-canary.2
2019-04-08 13:33:47 -04:00
Steven
fc9bbd2578 [now-static-build] fix link to docs (#368)
Fix link to documentation in static builder when the `dist` directory is not created.

I also changed the `build-utils` to use top-level imports like our docs specify.
2019-04-08 13:17:47 -04:00
Steven
f23f6ca643 Publish
- @now/python@0.1.0-canary.0
2019-04-08 09:30:06 -04:00
Sophearak Tha
c8d90fbcd1 [now/python] Update python tests (#367)
## What included?
- update python version to 3.6 in tests
- add `content-type` test
2019-04-08 08:45:51 -04:00
Nathan Rajlich
f4247da49a Publish
- @now/build-utils@0.4.40
2019-04-07 21:03:58 -07:00
Nathan Rajlich
9d781403ef [now-build-utils] Add support for symlinks to download() (#359)
* [now-build-utils] Add support for symlinks to `download()`

`now dev` is using this function to extract the `prepareCache()`
results, but upon extraction symlinks are lost and turned into
regular files, which breaks relative requires in Node.js modules.

This commit properly creates the symlinks from the files passed to
the `download()` function so that this problem no longer happens.

* Refactor `glob()` to return symlinks properly and add unit test

* Ensure the symlink target dir exists
2019-04-07 19:27:13 -07:00
Amio
ca188cf8e2 [now-built-utils] Add type declaration for glob (#360) 2019-04-07 19:17:06 -07:00
Connor Davis
207d895c0c Fix version check (#366) 2019-04-07 20:56:39 -05:00
Mike Engel
685821976d [now-rust] Make @now-rust's includeFiles match @now/node-server (#362)
This updates the `includeFiles` implementation to match how `now-node-server` does it, which is an array of globs rather than a single glob. To avoid making a new major version, this is done in a backwards compatible way to support existing projects using the current single glob API.

Added extra files to ensure that all would be included/processed even though the test just tests for one of them.

cc/ @styfle
2019-04-07 19:56:37 -04:00
Steven
fef5638cb9 [now-python] convert python builder to use typescript (#351)
* Add typescript to now-python

* Fix path

* Add gitignore

* Fix type errors

* Fix lint

* Move files back to root, add npmignore

* Final fixes
2019-04-07 11:13:02 -04:00
Nathan Rajlich
073ed247ad Update prepareCache() functions to use existing workPath (#356)
Considering that `prepareCache()` is executed directly after `build()`
is run, it seems that deleting the previous `workPath` and re-installing
the dependencies is just extra work, and it would be better to create
a cache from the `workPath` artifacts that `build()` just created.

From the perspective of `now dev`, this is ideal because:

 1. The `workPath` shouldn't be deleted since static file assets are
    served from the `workPath`, so `prepareCache()` deleting this
    directory is problematic.
 2. Creating the cache quickly becomes an important goal, because it
    reflects how quickly a developer can iterate on a file, and we don't
    want them waiting for `yarn` to finish installing dependencies all
    the time in order to see the change in their project.
2019-04-06 15:58:16 -07:00
Connor Davis
f071788ce6 [now/next] canary is updated 2019-04-05 18:18:11 -05:00
Igor Klopov
16f24bc3c8 [now-build-utils] decrease sema to fix deployments stuck at 'downloading' (#353)
[now-build-utils] decrease sema to fix deployments stuck at 'downloading'
2019-04-05 17:47:26 -04:00
Connor Davis
97fe3d489d [now-next] Strip Carat from version (#350)
* Strip Carat from version

* Get from next package.json

* Fix require

* Use resolveFrom

* Correctly fetch Next.js version

* Update index.js

* Update package.json
2019-04-05 02:12:27 -04:00
Joe Haddad
522d3a530c [now-next] Bump required version of Next.js (#352) 2019-04-05 01:49:19 -04:00
Steven
bafb49c464 Publish
- @now/python@0.0.42-canary.2
2019-04-04 15:21:34 -04:00
Sophearak Tha
7d5bd91e23 [@now/python] migrate @now/python-wsgi to @now/python` (#339)
* migrate `@now/python-wsgi to `@now/python`

* Refactor to use build-utils main

* Update install flag `--upgrade` in the right order.

Co-Authored-By: sophearak <t.sophearak@gmail.com>

* using `cwd` in instead in `execa`

* add 01-cowsay test with `pip` and `pipenv`

* add 01-wsgi test

* Fix typo

* Add test script for now-python

* Add now_init.py to support dynamic imports

* Remove redundant files, no more config.wsgi

* replace all occurrences __NOW_HANDLER_FILENAME

* update tests for `@now/python`
2019-04-04 10:08:20 -04:00
Connor Davis
213614881c Delete .next if it exists (#347) 2019-04-03 23:20:15 -05:00
Nathan Rajlich
a225a4f855 Publish
- @now/build-utils@0.4.39
2019-04-03 17:23:18 -07:00
Connor Davis
ed2fd1dd29 Merge branch 'master' of github.com:zeit/now-builders 2019-04-03 19:00:21 -05:00
Connor Davis
bd33528fc7 Publish 2019-04-03 19:00:02 -05:00
Connor Davis
16969803f8 @now/next hotfixes for now dev optimizations (#345) 2019-04-03 18:58:13 -05:00
Nathan Rajlich
03cc4c0b01 Publish
- @now/build-utils@0.4.39-canary.0
2019-04-03 16:00:21 -07:00
Nathan Rajlich
0b9699da75 [now-build-utils] Remove yarn/npm cache clean after install (#346)
`cache clean` was being invoked for legacy purposes back when we were
dealing with Lambda's 500mb runtime filesystem limit. Now that builds
are not running on lambda, this can be removed. It also makes `now dev`
building unnecessarily slow.
2019-04-03 15:55:39 -07:00
Connor Davis
6737011a63 Publish 2019-04-03 14:55:32 -05:00
Joe Haddad
6d2b0e014c Make @now/next compatible with now dev (#340) 2019-04-03 14:53:22 -05:00
Sigurd Spieckermann
409359bfec Python WSGI builder without server (#95)
This PR adds a builder for Python WSGI web apps. It is heavily based on [`serverless-wsgi`](https://github.com/logandk/serverless-wsgi) and [`requests-wsgi-adapter`](https://github.com/seanbrant/requests-wsgi-adapter), uses [`Werkzeug`](https://github.com/pallets/werkzeug), and works without running an additional web server.

I've tested it manually with a simple Flask app that contains endpoints with `GET` and `POST` methods, nested paths, and JSON and binary response bodies.

Curiously, the `event` parameter in the `now_handler` function does not appear to have the expected structure of AWS Lambda functions. For instance, `event['isBase64Encoded']` does not exist. Instead, this information is encoded in `json.loads(event['body'])['encoding']`. The returned `dict` also expects `encoding='base64'` for a binary response body instead of `isBase64Encoded=True`. I've also noticed that no AWS request headers are included. I assume Now is responsible for these differences to plain AWS Lambda functions.

I'd appreciate code review and feedback to get robust official Python WSGI support into Now as soon as possible.
2019-04-02 10:35:32 -04:00
Steven
2151812596 Publish
- @now/node@0.5.3
 - @now/node-server@0.5.3
2019-04-01 15:38:49 -04:00
Steven
22860be6d0 Publish
- @now/node@0.5.3-canary.1
- @now/node-server@0.5.3-canary.1
2019-04-01 15:27:36 -04:00
Steven
78c3cbd7b4 [now-node] Bump ncc to 0.17.3 (#337)
* [now-node] Bump ncc to 0.17.3
2019-04-01 15:22:31 -04:00
Nathan Rajlich
a458a55e99 Publish
- @now/build-utils@0.4.38
2019-04-01 07:39:09 -07:00
Sophearak Tha
911d85be39 [now-go] fix subdirectory build fail for @now/go (#309)
* fix subdirectory build fail for `@now/go`

* using sep over platform specific forward slash

* Update packages/now-go/index.js

Co-Authored-By: sophearak <t.sophearak@gmail.com>

* Update packages/now-go/index.js

Co-Authored-By: sophearak <t.sophearak@gmail.com>

* add 01-cowsay with subdirectory test case
2019-04-01 13:36:04 +07:00
Nathan Rajlich
98b5a4b0e9 Publish
- @now/node@0.5.2
 - @now/node-server@0.5.2
2019-03-31 15:01:28 -07:00
Nathan Rajlich
5f80e451b8 Update @now/node-bridge to v1.0.1 stable for @now/node and @now/node-server 2019-03-31 15:00:39 -07:00
Nathan Rajlich
0288f2d1a3 Publish
- @now/node-bridge@1.0.1
2019-03-31 14:47:13 -07:00
Sophearak Tha
e39a5eca04 Add Codecov report (#327)
* Add Codecov

* Using flag for coverage instead

* Revert "Using flag for coverage instead"

This reverts commit 9e14c8c8299267b2ff0431d822391e0538839ae7.

* Update codecov

* Add circle ci test coverage step

* try run jest without `node --expose-gc`

* Revert "try run jest without `node --expose-gc`"

This reverts commit 39fbd7995375ac62529d2d55519abc877b526071.

* run jest --coverage without `node --expose-gc`

* run jest with --runInBand flag

* Run tests and coverage at the same time
2019-03-29 09:11:14 -04:00
Steven
d4493f7d39 Fix rate limit for tests (#330)
This will generate a new token for every 10 API call so we stop getting rate limited.
2019-03-28 15:36:24 -04:00
Nathan Rajlich
145e5a10c2 [now-next] Set NODE_ENV to development in now dev (#325) 2019-03-26 12:52:50 -07:00
Nathan Rajlich
bd2d289252 [now-node] Set NODE_ENV to development in now dev (#324) 2019-03-26 12:52:26 -07:00
Nathan Rajlich
a673e5f752 [now-node-server] Set NODE_ENV to development in now dev (#323) 2019-03-26 12:52:04 -07:00
Nathan Rajlich
b2dc31a6b4 Revert "[now-node] Set NODE_ENV to development in now dev"
This reverts commit 62a308bed7.

Accidentally commited to `master` branch
2019-03-26 11:41:31 -07:00
Nathan Rajlich
62a308bed7 [now-node] Set NODE_ENV to development in now dev 2019-03-26 11:39:49 -07:00
Nathan Rajlich
ac08bfd26f [now-bash] Improvements for error handling (#318)
* Add shellcheck ignore comments to dynamic imports

* Add shebang to `runtime.sh`

So that vim applies proper syntax highlighting when bash-specific
features like `<<<` are used.

* Send error responses with `errorMessage` property

So that `now dev` can display the error message properly.
2019-03-26 10:51:59 -07:00
Mike Engel
d7f1371799 [now-rust] Allow extra static files on the lambda fs config.includeFiles (#300)
This add the ability to include other files into the filesystem of the lambda. This works by providing an `includeFiles` option in the builder config, and then moving those into the lambda when it's created. The `includeFiles` option is a glob matcher relative to the entrypoint.

**Note:** This is the first example I know of that allows a user to add more files to the filesystem through the official now builders. This API is something I came up with, and may not be the best or what y'all are thinking.

## Why?
This is helpful for my specific case, where I need extra binaries on the system when the lambda executes (`wget` and `now`). This could also be useful to include large files to parse, or other things one wouldn't want to include in the rust binary itself due to file size, memory allocation, whatever.

## Example
**Local file structure**
```
now_lambda/
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── static
    ├── now
    └── wget
```

**Now config**
```json
{
  "version": 2,
  "name": "now-lambda",
  "builds": [
    {
      "src": "Cargo.toml",
      "use": "@now/rust",
      "config": { "includeFiles": "static/*", "maxLambdaSize": "50mb" }
    }
  ]
}
```

**Lambda file structure**
```
/var/task/
├── bootstrap
└── static
    ├── now
    └── wget
```

## Testing
You can use `https://mike-now-rust-5shu849in.now.sh` as the builder's `"use"` key to test it out. It's a hosted tarball of this PR with a little extra debugging thrown in.
2019-03-26 11:19:25 -04:00
Nathan Rajlich
c97ad02aca [now-node] Enable TypeScript strict mode (#321) 2019-03-25 19:15:08 -07:00
Nathan Rajlich
c0460b734d Publish
- @now/node-server@0.5.2-canary.4
 - @now/node@0.5.2-canary.6
2019-03-25 19:03:21 -07:00
Steven
3b0ed55b57 [now-node] Bump ncc to 0.17.0 (#317)
* Bump ncc to 0.17.0

* Bump ncc to 0.17.0
2019-03-25 18:59:50 -07:00
Nathan Rajlich
402153f076 Publish
- @now/build-utils@0.4.38-canary.1
2019-03-25 18:22:30 -07:00
Nathan Rajlich
6ec823e292 [now-built-utils] Use constants for the type field (#320)
This is important for TypeScript usage with the combined `File` type
declaration. It helps the compiler understand the proper type in
consuming code via `if` checks to avoid casting.
2019-03-25 18:18:38 -07:00
Nathan Rajlich
a9af9ebb5a [now-build-utils] Pin "end-of-stream" dependency to v1.4.1 (#319)
All of the other deps are pinned versions, so this is for consistency.
2019-03-25 17:45:11 -07:00
169 changed files with 4392 additions and 754 deletions

View File

@@ -27,8 +27,8 @@ jobs:
name: Linting name: Linting
command: yarn lint command: yarn lint
- run: - run:
name: Tests name: Tests and Coverage
command: yarn test command: yarn test-coverage
- run: - run:
name: Potentially save npm token name: Potentially save npm token
command: "([[ ! -z $NPM_TOKEN ]] && echo \"//registry.npmjs.org/:_authToken=$NPM_TOKEN\" >> ~/.npmrc) || echo \"Did not write npm token\"" command: "([[ ! -z $NPM_TOKEN ]] && echo \"//registry.npmjs.org/:_authToken=$NPM_TOKEN\" >> ~/.npmrc) || echo \"Did not write npm token\""

View File

@@ -4,4 +4,7 @@
/packages/now-go/go/* /packages/now-go/go/*
/packages/now-build-utils/dist/* /packages/now-build-utils/dist/*
/packages/now-node/dist/* /packages/now-node/dist/*
/packages/now-next/dist/*
/packages/now-node-bridge/* /packages/now-node-bridge/*
/packages/now-python/*
/packages/now-optipng/dist/*

View File

@@ -8,7 +8,7 @@
}, },
"overrides": [ "overrides": [
{ {
"files": ["test/**"], "files": ["**/test/**"],
"rules": { "rules": {
"import/no-extraneous-dependencies": 0 "import/no-extraneous-dependencies": 0
}, },

View File

@@ -1,3 +1,4 @@
{ {
"singleQuote": true "singleQuote": true,
"trailingComma": "es5"
} }

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"eslint.enable": false
}

View File

@@ -1,4 +1,8 @@
module.exports = { module.exports = {
testEnvironment: 'node', testEnvironment: 'node',
collectCoverageFrom: ['packages/**/*.{js,jsx}', '!**/node_modules/**'], collectCoverageFrom: [
'packages/(!test)/**/*.{js,jsx}',
'!**/node_modules/**',
'!**/test/**',
],
}; };

View File

@@ -16,7 +16,9 @@
"publish-canary": "lerna version prerelease --preid canary", "publish-canary": "lerna version prerelease --preid canary",
"build": "./.circleci/build.sh", "build": "./.circleci/build.sh",
"lint": "eslint .", "lint": "eslint .",
"codecov": "codecov",
"test": "jest --runInBand --verbose", "test": "jest --runInBand --verbose",
"test-coverage": "jest --runInBand --verbose --coverage --globals \"{\\\"coverage\\\":true}\" && codecov",
"lint-staged": "lint-staged" "lint-staged": "lint-staged"
}, },
"pre-commit": "lint-staged", "pre-commit": "lint-staged",
@@ -25,6 +27,10 @@
"prettier --write", "prettier --write",
"eslint --fix", "eslint --fix",
"git add" "git add"
],
"*.ts": [
"prettier --write",
"git add"
] ]
}, },
"devDependencies": { "devDependencies": {
@@ -34,6 +40,7 @@
"@types/node": "^10.12.8", "@types/node": "^10.12.8",
"async-retry": "1.2.3", "async-retry": "1.2.3",
"buffer-replace": "^1.0.0", "buffer-replace": "^1.0.0",
"codecov": "^3.2.0",
"eslint": "^5.9.0", "eslint": "^5.9.0",
"eslint-config-airbnb-base": "^13.1.0", "eslint-config-airbnb-base": "^13.1.0",
"eslint-config-prettier": "^3.1.0", "eslint-config-prettier": "^3.1.0",

View File

@@ -7,7 +7,9 @@ export IMPORT_CACHE="$LAMBDA_TASK_ROOT/.import-cache"
export PATH="$IMPORT_CACHE/bin:$PATH" export PATH="$IMPORT_CACHE/bin:$PATH"
# Load `import` and runtime # Load `import` and runtime
# shellcheck disable=SC1090
. "$(which import)" . "$(which import)"
# shellcheck disable=SC1090
. "$IMPORT_CACHE/runtime.sh" . "$IMPORT_CACHE/runtime.sh"
# Load user code and process events in a loop forever # Load user code and process events in a loop forever

View File

@@ -1,6 +1,6 @@
{ {
"name": "@now/bash", "name": "@now/bash",
"version": "0.1.5-canary.0", "version": "0.2.0",
"description": "Now 2.0 builder for HTTP endpoints written in Bash", "description": "Now 2.0 builder for HTTP endpoints written in Bash",
"main": "index.js", "main": "index.js",
"author": "Nathan Rajlich <nate@zeit.co>", "author": "Nathan Rajlich <nate@zeit.co>",

View File

@@ -1,3 +1,4 @@
#!/bin/bash
import "static-binaries@1.0.0" import "static-binaries@1.0.0"
static_binaries jq static_binaries jq
@@ -13,12 +14,14 @@ _lambda_runtime_api() {
_lambda_runtime_init() { _lambda_runtime_init() {
# Initialize user code # Initialize user code
# shellcheck disable=SC1090
. "$SCRIPT_FILENAME" || { . "$SCRIPT_FILENAME" || {
local exit_code="$?" local exit_code="$?"
local error local error_message="Initialization failed for '$SCRIPT_FILENAME' (exit code $exit_code)"
error='{"exitCode":'"$exit_code"'}' echo "$error_message" >&2
local error='{"errorMessage":"'"$error_message"'"}'
_lambda_runtime_api "init/error" -X POST -d "$error" _lambda_runtime_api "init/error" -X POST -d "$error"
exit "$EXIT_CODE" exit "$exit_code"
} }
# Process events # Process events
@@ -45,9 +48,6 @@ _lambda_runtime_next() {
local body local body
body="$(mktemp)" body="$(mktemp)"
local exit_code=0
REQUEST="$event"
# Stdin of the `handler` function is the HTTP request body. # Stdin of the `handler` function is the HTTP request body.
# Need to use a fifo here instead of bash <() because Lambda # Need to use a fifo here instead of bash <() because Lambda
# errors with "/dev/fd/63 not found" for some reason :/ # errors with "/dev/fd/63 not found" for some reason :/
@@ -56,6 +56,7 @@ _lambda_runtime_next() {
mkfifo "$stdin" mkfifo "$stdin"
_lambda_runtime_body < "$event" > "$stdin" & _lambda_runtime_body < "$event" > "$stdin" &
local exit_code=0
handler "$event" < "$stdin" > "$body" || exit_code="$?" handler "$event" < "$stdin" > "$body" || exit_code="$?"
rm -f "$event" "$stdin" rm -f "$event" "$stdin"
@@ -69,8 +70,9 @@ _lambda_runtime_next() {
| _lambda_runtime_api "invocation/$request_id/response" -X POST -d @- > /dev/null | _lambda_runtime_api "invocation/$request_id/response" -X POST -d @- > /dev/null
rm -f "$body" "$_HEADERS" rm -f "$body" "$_HEADERS"
else else
echo "\`handler\` function return code: $exit_code" local error_message="Invocation failed for 'handler' function in '$SCRIPT_FILENAME' (exit code $exit_code)"
_lambda_runtime_api "invocation/$request_id/error" -X POST -d @- > /dev/null <<< '{"exitCode":'"$exit_code"'}' echo "$error_message" >&2
_lambda_runtime_api "invocation/$request_id/error" -X POST -d '{"errorMessage":"'"$error_message"'"}' > /dev/null
fi fi
} }
@@ -100,7 +102,10 @@ http_response_header() {
local value="$2" local value="$2"
local tmp local tmp
tmp="$(mktemp)" tmp="$(mktemp)"
jq --arg name "$name" --arg value "$value" '.[$name] = $value' < "$_HEADERS" > "$tmp" jq \
--arg name "$name" \
--arg value "$value" \
'.[$name] = $value' < "$_HEADERS" > "$tmp"
mv -f "$tmp" "$_HEADERS" mv -f "$tmp" "$_HEADERS"
} }

View File

@@ -1 +1,3 @@
dist dist
test/symlinks-out
test/symlinks.zip

View File

@@ -1,6 +1,6 @@
{ {
"name": "@now/build-utils", "name": "@now/build-utils",
"version": "0.4.38-canary.0", "version": "0.5.0",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.js", "types": "./dist/index.d.js",
@@ -12,7 +12,7 @@
"dependencies": { "dependencies": {
"async-retry": "1.2.3", "async-retry": "1.2.3",
"async-sema": "2.1.4", "async-sema": "2.1.4",
"end-of-stream": "^1.4.1", "end-of-stream": "1.4.1",
"fs-extra": "7.0.0", "fs-extra": "7.0.0",
"glob": "7.1.3", "glob": "7.1.3",
"into-stream": "5.0.0", "into-stream": "5.0.0",
@@ -30,8 +30,10 @@
"@types/async-retry": "^1.2.1", "@types/async-retry": "^1.2.1",
"@types/end-of-stream": "^1.4.0", "@types/end-of-stream": "^1.4.0",
"@types/fs-extra": "^5.0.5", "@types/fs-extra": "^5.0.5",
"@types/glob": "^7.1.1",
"@types/node-fetch": "^2.1.6", "@types/node-fetch": "^2.1.6",
"@types/yazl": "^2.4.1", "@types/yazl": "^2.4.1",
"execa": "^1.0.0",
"typescript": "3.3.4000" "typescript": "3.3.4000"
} }
} }

View File

@@ -1,27 +0,0 @@
import path from 'path';
import FileFsRef from './file-fs-ref';
import { File, Files } from './types';
export interface DownloadedFiles {
[filePath: string]: FileFsRef
}
async function downloadFile(file: File, fsPath: string): Promise<FileFsRef> {
const { mode } = file;
const stream = file.toStream();
return FileFsRef.fromStream({ mode, stream, fsPath });
}
export default async function download(files: Files, basePath: string): Promise<DownloadedFiles> {
const files2: DownloadedFiles = {};
await Promise.all(
Object.keys(files).map(async (name) => {
const file = files[name];
const fsPath = path.join(basePath, name);
files2[name] = await downloadFile(file, fsPath);
}),
);
return files2;
};

View File

@@ -13,7 +13,7 @@ interface FromStreamOptions {
} }
export default class FileBlob implements File { export default class FileBlob implements File {
public type: string; public type: 'FileBlob';
public mode: number; public mode: number;
public data: string | Buffer; public data: string | Buffer;

View File

@@ -5,17 +5,13 @@ import path from 'path';
import Sema from 'async-sema'; import Sema from 'async-sema';
import { File } from './types'; import { File } from './types';
const semaToPreventEMFILE = new Sema(30); const semaToPreventEMFILE = new Sema(20);
interface FileFsRefOptions { interface FileFsRefOptions {
mode?: number; mode?: number;
fsPath: string; fsPath: string;
} }
interface FromOptions {
fsPath: string;
}
interface FromStreamOptions { interface FromStreamOptions {
mode: number; mode: number;
stream: NodeJS.ReadableStream; stream: NodeJS.ReadableStream;
@@ -23,7 +19,7 @@ interface FromStreamOptions {
} }
class FileFsRef implements File { class FileFsRef implements File {
public type: string; public type: 'FileFsRef';
public mode: number; public mode: number;
public fsPath: string; public fsPath: string;
@@ -35,9 +31,13 @@ class FileFsRef implements File {
this.fsPath = fsPath; this.fsPath = fsPath;
} }
static async fromFsPath({ fsPath }: FromOptions): Promise<FileFsRef> { static async fromFsPath({ mode, fsPath }: FileFsRefOptions): Promise<FileFsRef> {
const { mode } = await fs.lstat(fsPath); let m = mode;
return new FileFsRef({ mode, fsPath }); if (!m) {
const stat = await fs.lstat(fsPath);
m = stat.mode;
}
return new FileFsRef({ mode: m, fsPath });
} }
static async fromStream({ mode = 0o100644, stream, fsPath }: FromStreamOptions): Promise<FileFsRef> { static async fromStream({ mode = 0o100644, stream, fsPath }: FromStreamOptions): Promise<FileFsRef> {
@@ -47,14 +47,15 @@ class FileFsRef implements File {
await fs.mkdirp(path.dirname(fsPath)); await fs.mkdirp(path.dirname(fsPath));
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const dest = fs.createWriteStream(fsPath); const dest = fs.createWriteStream(fsPath, {
mode: mode & 0o777
});
stream.pipe(dest); stream.pipe(dest);
stream.on('error', reject); stream.on('error', reject);
dest.on('finish', resolve); dest.on('finish', resolve);
dest.on('error', reject); dest.on('error', reject);
}); });
await fs.chmod(fsPath, mode.toString(8).slice(-3));
return new FileFsRef({ mode, fsPath }); return new FileFsRef({ mode, fsPath });
} }

View File

@@ -8,9 +8,10 @@ import { File } from './types';
interface FileRefOptions { interface FileRefOptions {
mode?: number; mode?: number;
digest: string; digest: string;
mutable?: boolean;
} }
const semaToDownloadFromS3 = new Sema(10); const semaToDownloadFromS3 = new Sema(5);
class BailableError extends Error { class BailableError extends Error {
public bail: boolean; public bail: boolean;
@@ -22,16 +23,19 @@ class BailableError extends Error {
} }
export default class FileRef implements File { export default class FileRef implements File {
public type: string; public type: 'FileRef';
public mode: number; public mode: number;
public digest: string; public digest: string;
public mutable: boolean;
constructor({ mode = 0o100644, digest }: FileRefOptions) { constructor({ mode = 0o100644, digest, mutable = false }: FileRefOptions) {
assert(typeof mode === 'number'); assert(typeof mode === 'number');
assert(typeof digest === 'string'); assert(typeof digest === 'string');
assert(typeof mutable === 'boolean');
this.type = 'FileRef'; this.type = 'FileRef';
this.mode = mode; this.mode = mode;
this.digest = digest; this.digest = digest;
this.mutable = mutable;
} }
async toStreamAsync(): Promise<NodeJS.ReadableStream> { async toStreamAsync(): Promise<NodeJS.ReadableStream> {
@@ -39,8 +43,9 @@ export default class FileRef implements File {
// sha:24be087eef9fac01d61b30a725c1a10d7b45a256 // sha:24be087eef9fac01d61b30a725c1a10d7b45a256
const digestParts = this.digest.split(':'); const digestParts = this.digest.split(':');
if (digestParts[0] === 'sha') { if (digestParts[0] === 'sha') {
// url = `https://s3.amazonaws.com/now-files/${digestParts[1]}`; url = this.mutable
url = `https://dmmcy0pwk6bqi.cloudfront.net/${digestParts[1]}`; ? `https://s3.amazonaws.com/now-files/${digestParts[1]}`
: `https://dmmcy0pwk6bqi.cloudfront.net/${digestParts[1]}`;
} else { } else {
throw new Error('Expected digest to be sha'); throw new Error('Expected digest to be sha');
} }

View File

@@ -1,27 +1,62 @@
import path from 'path'; import path from 'path';
import FileFsRef from '../file-fs-ref'; import FileFsRef from '../file-fs-ref';
import { File, Files } from '../types'; import { File, Files, Meta } from '../types';
import { remove, mkdirp, readlink, symlink } from 'fs-extra';
export interface DownloadedFiles { export interface DownloadedFiles {
[filePath: string]: FileFsRef [filePath: string]: FileFsRef
} }
const S_IFMT = 61440; /* 0170000 type of file */
const S_IFLNK = 40960; /* 0120000 symbolic link */
export function isSymbolicLink(mode: number): boolean {
return (mode & S_IFMT) === S_IFLNK;
}
async function downloadFile(file: File, fsPath: string): Promise<FileFsRef> { async function downloadFile(file: File, fsPath: string): Promise<FileFsRef> {
const { mode } = file; const { mode } = file;
if (mode && isSymbolicLink(mode) && file.type === 'FileFsRef') {
const [ target ] = await Promise.all([
readlink((file as FileFsRef).fsPath),
mkdirp(path.dirname(fsPath))
]);
await symlink(target, fsPath);
return FileFsRef.fromFsPath({ mode, fsPath });
} else {
const stream = file.toStream(); const stream = file.toStream();
return FileFsRef.fromStream({ mode, stream, fsPath }); return FileFsRef.fromStream({ mode, stream, fsPath });
} }
}
export default async function download(files: Files, basePath: string): Promise<DownloadedFiles> { async function removeFile(basePath: string, fileMatched: string) {
const file = path.join(basePath, fileMatched);
await remove(file);
}
export default async function download(files: Files, basePath: string, meta?: Meta): Promise<DownloadedFiles> {
const files2: DownloadedFiles = {}; const files2: DownloadedFiles = {};
const { filesChanged = null, filesRemoved = null } = meta || {};
await Promise.all( await Promise.all(
Object.keys(files).map(async (name) => { Object.keys(files).map(async (name) => {
// If the file does not exist anymore, remove it.
if (Array.isArray(filesRemoved) && filesRemoved.includes(name)) {
await removeFile(basePath, name);
return;
}
// If a file didn't change, do not re-download it.
if (Array.isArray(filesChanged) && !filesChanged.includes(name)) {
return;
}
const file = files[name]; const file = files[name];
const fsPath = path.join(basePath, name); const fsPath = path.join(basePath, name);
files2[name] = await downloadFile(file, fsPath); files2[name] = await downloadFile(file, fsPath);
}), }),
); );
return files2; return files2;
}; }

View File

@@ -1,16 +1,19 @@
import assert from 'assert';
import path from 'path'; import path from 'path';
import vanillaGlob from 'glob'; import assert from 'assert';
import vanillaGlob_ from 'glob';
import { promisify } from 'util';
import { lstat, Stats } from 'fs-extra';
import FileFsRef from '../file-fs-ref'; import FileFsRef from '../file-fs-ref';
type GlobOptions = import('glob').IOptions; type GlobOptions = vanillaGlob_.IOptions;
interface FsFiles { interface FsFiles {
[filePath: string]: FileFsRef [filePath: string]: FileFsRef
} }
export default function glob(pattern: string, opts: GlobOptions | string, mountpoint?: string): Promise<FsFiles> { const vanillaGlob = promisify(vanillaGlob_);
return new Promise<FsFiles>((resolve, reject) => {
export default async function glob(pattern: string, opts: GlobOptions | string, mountpoint?: string): Promise<FsFiles> {
let options: GlobOptions; let options: GlobOptions;
if (typeof opts === 'string') { if (typeof opts === 'string') {
options = { cwd: opts }; options = { cwd: opts };
@@ -28,34 +31,36 @@ export default function glob(pattern: string, opts: GlobOptions | string, mountp
throw new Error(`basePath/cwd must be an absolute path (${options.cwd})`); throw new Error(`basePath/cwd must be an absolute path (${options.cwd})`);
} }
const results: FsFiles = {};
options.symlinks = {};
options.statCache = {}; options.statCache = {};
options.stat = true; options.stat = true;
options.dot = true; options.dot = true;
// eslint-disable-next-line consistent-return const files = await vanillaGlob(pattern, options);
vanillaGlob(pattern, options, (error, files) => {
if (error) return reject(error);
resolve( for (const relativePath of files) {
files.reduce<FsFiles>((files2, relativePath) => {
const fsPath = path.join(options.cwd!, relativePath); const fsPath = path.join(options.cwd!, relativePath);
const stat = options.statCache![fsPath] as import('fs').Stats; let stat: Stats = options.statCache![fsPath] as Stats;
assert( assert(
stat, stat,
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`, `statCache does not contain value for ${relativePath} (resolved to ${fsPath})`,
); );
if (stat && stat.isFile()) { if (stat.isFile()) {
let finalPath = relativePath; const isSymlink = options.symlinks![fsPath];
if (mountpoint) finalPath = path.join(mountpoint, finalPath); if (isSymlink) {
return { stat = await lstat(fsPath);
...files2,
[finalPath]: new FileFsRef({ mode: stat.mode, fsPath }),
};
} }
return files2; let finalPath = relativePath;
}, {}), if (mountpoint) {
); finalPath = path.join(mountpoint, finalPath);
}); }
});
}; results[finalPath] = new FileFsRef({ mode: stat.mode, fsPath });
}
}
return results;
}

View File

@@ -5,11 +5,27 @@ import { spawn, SpawnOptions } from 'child_process';
function spawnAsync(command: string, args: string[], cwd: string, opts: SpawnOptions = {}) { function spawnAsync(command: string, args: string[], cwd: string, opts: SpawnOptions = {}) {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const child = spawn(command, args, { stdio: 'inherit', cwd, ...opts }); const stderrLogs: Buffer[] = []
opts = { stdio: 'inherit', cwd, ...opts };
const child = spawn(command, args, opts);
if (opts.stdio === 'pipe'){
child.stderr.on('data', data => stderrLogs.push(data));
}
child.on('error', reject); child.on('error', reject);
child.on('close', (code, signal) => (code !== 0 child.on('close', (code, signal) => {
? reject(new Error(`Exited with ${code || signal}`)) if (code === 0) {
: resolve())); return resolve()
}
const errorLogs = stderrLogs.map(line => line.toString()).join('');
if (opts.stdio !== 'inherit') {
reject(new Error(`Exited with ${code || signal}\n${errorLogs}`));
} else {
reject(new Error(`Exited with ${code || signal}`));
}
});
}); });
} }
@@ -75,24 +91,32 @@ export async function installDependencies(destPath: string, args: string[] = [])
// Node.js version that `@now/node` and `@now/node-server` use // Node.js version that `@now/node` and `@now/node-server` use
npm_config_target: '8.10.0', npm_config_target: '8.10.0',
}, },
stdio: 'pipe'
}; };
if (hasPackageLockJson) { if (hasPackageLockJson) {
commandArgs = args.filter(a => a !== '--prefer-offline'); commandArgs = args.filter(a => a !== '--prefer-offline');
await spawnAsync('npm', ['install'].concat(commandArgs), destPath, opts); await spawnAsync(
await spawnAsync('npm', ['cache', 'clean', '--force'], destPath, opts); 'npm',
['install'].concat(commandArgs),
destPath,
opts as SpawnOptions
);
} else { } else {
await spawnAsync( await spawnAsync(
'yarn', 'yarn',
['--cwd', destPath].concat(commandArgs), ['--cwd', destPath].concat(commandArgs),
destPath, destPath,
opts, opts as SpawnOptions,
); );
await spawnAsync('yarn', ['cache', 'clean'], destPath, opts);
} }
} }
export async function runPackageJsonScript(destPath: string, scriptName: string) { export async function runPackageJsonScript(
destPath: string,
scriptName: string,
opts?: SpawnOptions
) {
assert(path.isAbsolute(destPath)); assert(path.isAbsolute(destPath));
const { hasScript, hasPackageLockJson } = await scanParentDirs( const { hasScript, hasPackageLockJson } = await scanParentDirs(
destPath, destPath,
@@ -102,10 +126,10 @@ export async function runPackageJsonScript(destPath: string, scriptName: string)
if (hasPackageLockJson) { if (hasPackageLockJson) {
console.log(`running "npm run ${scriptName}"`); console.log(`running "npm run ${scriptName}"`);
await spawnAsync('npm', ['run', scriptName], destPath); await spawnAsync('npm', ['run', scriptName], destPath, opts);
} else { } else {
console.log(`running "yarn run ${scriptName}"`); console.log(`running "yarn run ${scriptName}"`);
await spawnAsync('yarn', ['--cwd', destPath, 'run', scriptName], destPath); await spawnAsync('yarn', ['--cwd', destPath, 'run', scriptName], destPath, opts);
} }
return true; return true;

View File

@@ -1,7 +1,7 @@
import FileBlob from './file-blob'; import FileBlob from './file-blob';
import FileFsRef from './file-fs-ref'; import FileFsRef from './file-fs-ref';
import FileRef from './file-ref'; import FileRef from './file-ref';
import { File, Files, AnalyzeOptions, BuildOptions, PrepareCacheOptions } from './types'; import { File, Files, AnalyzeOptions, BuildOptions, PrepareCacheOptions, ShouldServeOptions, Meta } from './types';
import { Lambda, createLambda } from './lambda'; import { Lambda, createLambda } from './lambda';
import download from './fs/download'; import download from './fs/download';
import getWriteableDirectory from './fs/get-writable-directory' import getWriteableDirectory from './fs/get-writable-directory'
@@ -9,6 +9,7 @@ import glob from './fs/glob';
import rename from './fs/rename'; import rename from './fs/rename';
import { installDependencies, runPackageJsonScript, runNpmInstall, runShellScript } from './fs/run-user-scripts'; import { installDependencies, runPackageJsonScript, runNpmInstall, runShellScript } from './fs/run-user-scripts';
import streamToBuffer from './fs/stream-to-buffer'; import streamToBuffer from './fs/stream-to-buffer';
import shouldServe from './should-serve';
export { export {
FileBlob, FileBlob,
@@ -16,6 +17,7 @@ export {
FileRef, FileRef,
Files, Files,
File, File,
Meta,
Lambda, Lambda,
createLambda, createLambda,
download, download,
@@ -27,5 +29,6 @@ export {
AnalyzeOptions, AnalyzeOptions,
BuildOptions, BuildOptions,
PrepareCacheOptions, PrepareCacheOptions,
ShouldServeOptions,
shouldServe,
}; };

View File

@@ -1,8 +1,11 @@
import assert from 'assert'; import assert from 'assert';
import Sema from 'async-sema'; import Sema from 'async-sema';
import { ZipFile } from 'yazl'; import { ZipFile } from 'yazl';
import streamToBuffer from './fs/stream-to-buffer'; import { readlink } from 'fs-extra';
import { Files } from './types'; import { Files } from './types';
import FileFsRef from './file-fs-ref';
import { isSymbolicLink } from './fs/download';
import streamToBuffer from './fs/stream-to-buffer';
interface Environment { interface Environment {
[key: string]: string; [key: string]: string;
@@ -23,7 +26,7 @@ interface CreateLambdaOptions {
} }
export class Lambda { export class Lambda {
public type: string; public type: 'Lambda';
public zipBuffer: Buffer; public zipBuffer: Buffer;
public handler: string; public handler: string;
public runtime: string; public runtime: string;
@@ -52,22 +55,9 @@ export async function createLambda({
assert(typeof environment === 'object', '"environment" is not an object'); assert(typeof environment === 'object', '"environment" is not an object');
await sema.acquire(); await sema.acquire();
try { try {
const zipFile = new ZipFile(); const zipBuffer = await createZip(files);
const zipBuffer = await new Promise<Buffer>((resolve, reject) => {
Object.keys(files)
.sort()
.forEach((name) => {
const file = files[name];
const stream = file.toStream() as import('stream').Readable;
stream.on('error', reject);
zipFile.addReadStream(stream, name, { mode: file.mode, mtime });
});
zipFile.end();
streamToBuffer(zipFile.outputStream).then(resolve).catch(reject);
});
return new Lambda({ return new Lambda({
zipBuffer, zipBuffer,
handler, handler,
@@ -78,3 +68,37 @@ export async function createLambda({
sema.release(); sema.release();
} }
} }
export async function createZip(files: Files): Promise<Buffer> {
const names = Object.keys(files).sort();
const symlinkTargets = new Map<string, string>();
for (const name of names) {
const file = files[name];
if (file.mode && isSymbolicLink(file.mode) && file.type === 'FileFsRef') {
const symlinkTarget = await readlink((file as FileFsRef).fsPath);
symlinkTargets.set(name, symlinkTarget);
}
}
const zipFile = new ZipFile();
const zipBuffer = await new Promise<Buffer>((resolve, reject) => {
for (const name of names) {
const file = files[name];
const opts = { mode: file.mode, mtime };
const symlinkTarget = symlinkTargets.get(name);
if (typeof symlinkTarget === 'string') {
zipFile.addBuffer(Buffer.from(symlinkTarget, 'utf8'), name, opts);
} else {
const stream = file.toStream() as import('stream').Readable;
stream.on('error', reject);
zipFile.addReadStream(stream, name, opts);
}
}
zipFile.end();
streamToBuffer(zipFile.outputStream).then(resolve).catch(reject);
});
return zipBuffer;
}

View File

@@ -0,0 +1,27 @@
import { parse } from 'path';
import { ShouldServeOptions } from './types';
import FileFsRef from './file-fs-ref';
export default function shouldServe({
entrypoint,
files,
requestPath
}: ShouldServeOptions): boolean {
requestPath = requestPath.replace(/\/$/, ''); // sanitize trailing '/'
entrypoint = entrypoint.replace(/\\/, '/'); // windows compatibility
if (entrypoint === requestPath && hasProp(files, entrypoint)) {
return true;
}
const { dir, name } = parse(entrypoint);
if (name === 'index' && dir === requestPath && hasProp(files, entrypoint)) {
return true;
}
return false;
}
function hasProp(obj: { [path: string]: FileFsRef }, key: string): boolean {
return Object.hasOwnProperty.call(obj, key)
}

View File

@@ -1,22 +1,35 @@
import FileRef from './file-ref';
import FileFsRef from './file-fs-ref';
export interface File { export interface File {
type: string; type: string;
mode: number; mode: number;
toStream: () => NodeJS.ReadableStream; toStream: () => NodeJS.ReadableStream;
fsPath?: string;
} }
export interface Files { export interface Files {
[filePath: string]: File [filePath: string]: File;
} }
export interface Config { export interface Config {
[key: string]: string [key: string]: string;
}
export interface Meta {
isDev?: boolean;
requestPath?: string;
filesChanged?: string[];
filesRemoved?: string[];
} }
export interface AnalyzeOptions { export interface AnalyzeOptions {
/** /**
* All source files of the project * All source files of the project
*/ */
files: Files; files: {
[filePath: string]: FileRef;
};
/** /**
* Name of entrypoint file for this particular build job. Value * Name of entrypoint file for this particular build job. Value
@@ -39,7 +52,6 @@ export interface AnalyzeOptions {
config: Config; config: Config;
} }
export interface BuildOptions { export interface BuildOptions {
/** /**
* All source files of the project * All source files of the project
@@ -65,6 +77,13 @@ export interface BuildOptions {
* in `now.json`. * in `now.json`.
*/ */
config: Config; config: Config;
/**
* Metadata related to the invoker of the builder, used by `now dev`.
* Builders may use the properties on this object to change behavior based
* on the build environment.
*/
meta?: Meta;
} }
export interface PrepareCacheOptions { export interface PrepareCacheOptions {
@@ -99,3 +118,37 @@ export interface PrepareCacheOptions {
*/ */
config: Config; config: Config;
} }
export interface ShouldServeOptions {
/**
* A path string from a request.
*/
requestPath: string;
/**
* Name of entrypoint file for this particular build job. Value
* `files[entrypoint]` is guaranteed to exist and be a valid File reference.
* `entrypoint` is always a discrete file and never a glob, since globs are
* expanded into separate builds at deployment time.
*/
entrypoint: string;
/**
* All source files of the project
*/
files: {
[path: string]: FileFsRef;
};
/**
* A writable temporary directory where you are encouraged to perform your
* build process. This directory will be populated with the restored cache.
*/
workPath: string;
/**
* An arbitrary object passed by the user in the build definition defined
* in `now.json`.
*/
config: Config;
}

View File

@@ -0,0 +1 @@
contents

View File

@@ -0,0 +1 @@
./a.txt

View File

@@ -1,6 +1,11 @@
/* global beforeAll, expect, it, jest */ /* global beforeAll, expect, it, jest */
const fs = require('fs');
const path = require('path'); const path = require('path');
const fs = require('fs-extra');
// eslint-disable-next-line import/no-extraneous-dependencies
const execa = require('execa');
const assert = require('assert');
const { glob, download } = require('../');
const { createZip } = require('../dist/lambda');
const { const {
packAndDeploy, packAndDeploy,
@@ -17,6 +22,48 @@ beforeAll(async () => {
console.log('buildUtilsUrl', buildUtilsUrl); console.log('buildUtilsUrl', buildUtilsUrl);
}); });
// unit tests
it('should re-create symlinks properly', async () => {
const files = await glob('**', path.join(__dirname, 'symlinks'));
assert.equal(Object.keys(files).length, 2);
const outDir = path.join(__dirname, 'symlinks-out');
await fs.remove(outDir);
const files2 = await download(files, outDir);
assert.equal(Object.keys(files2).length, 2);
const [linkStat, aStat] = await Promise.all([
fs.lstat(path.join(outDir, 'link.txt')),
fs.lstat(path.join(outDir, 'a.txt')),
]);
assert(linkStat.isSymbolicLink());
assert(aStat.isFile());
});
it('should create zip files with symlinks properly', async () => {
const files = await glob('**', path.join(__dirname, 'symlinks'));
assert.equal(Object.keys(files).length, 2);
const outFile = path.join(__dirname, 'symlinks.zip');
await fs.remove(outFile);
const outDir = path.join(__dirname, 'symlinks-out');
await fs.remove(outDir);
await fs.mkdirp(outDir);
await fs.writeFile(outFile, await createZip(files));
await execa('unzip', [outFile], { cwd: outDir });
const [linkStat, aStat] = await Promise.all([
fs.lstat(path.join(outDir, 'link.txt')),
fs.lstat(path.join(outDir, 'a.txt')),
]);
assert(linkStat.isSymbolicLink());
assert(aStat.isFile());
});
// own fixtures // own fixtures
const fixturesPath = path.resolve(__dirname, 'fixtures'); const fixturesPath = path.resolve(__dirname, 'fixtures');

View File

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

View File

@@ -1,4 +1,4 @@
const { join, dirname } = require('path'); const { join, sep, dirname } = require('path');
const { const {
readFile, writeFile, pathExists, move, readFile, writeFile, pathExists, move,
} = require('fs-extra'); } = require('fs-extra');
@@ -55,7 +55,7 @@ async function build({ files, entrypoint }) {
// check if package name other than main // check if package name other than main
const packageName = parseFunctionName.split(',')[1]; const packageName = parseFunctionName.split(',')[1];
const isGoModExist = await pathExists(`${entrypointDirname}/go.mod`); const isGoModExist = await pathExists(join(entrypointDirname, 'go.mod'));
if (packageName !== 'main') { if (packageName !== 'main') {
const go = await createGo( const go = await createGo(
goPath, goPath,
@@ -88,7 +88,7 @@ async function build({ files, entrypoint }) {
if (isGoModExist) { if (isGoModExist) {
const goModContents = await readFile( const goModContents = await readFile(
`${entrypointDirname}/go.mod`, join(entrypointDirname, 'go.mod'),
'utf8', 'utf8',
); );
goPackageName = `${ goPackageName = `${
@@ -108,10 +108,20 @@ async function build({ files, entrypoint }) {
// move user go file to folder // move user go file to folder
try { try {
await move( // default path
downloadedFiles[entrypoint].fsPath, let finalDestination = join(entrypointDirname, packageName, entrypoint);
`${join(entrypointDirname, packageName, entrypoint)}`, const entrypointArr = entrypoint.split(sep);
// if `entrypoint` include folder, only use filename
if (entrypointArr.length > 1) {
finalDestination = join(
entrypointDirname,
packageName,
entrypointArr.pop(),
); );
}
await move(downloadedFiles[entrypoint].fsPath, finalDestination);
} catch (err) { } catch (err) {
console.log('failed to move entry to package folder'); console.log('failed to move entry to package folder');
throw err; throw err;

View File

@@ -1,6 +1,6 @@
{ {
"name": "@now/go", "name": "@now/go",
"version": "0.3.1-canary.2", "version": "0.4.0",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -0,0 +1,13 @@
package cowsay
import (
"fmt"
"net/http"
say "github.com/dhruvbird/go-cowsay"
)
// Handler function
func Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, say.Format("cow:RANDOMNESS_PLACEHOLDER"))
}

View File

@@ -0,0 +1,11 @@
{
"version": 2,
"builds": [
{ "src": "index.go", "use": "@now/go" },
{ "src": "subdirectory/index.go", "use": "@now/go" }
],
"probes": [
{ "path": "/", "mustContain": "cow:RANDOMNESS_PLACEHOLDER" },
{ "path": "/subdirectory", "mustContain": "subcow:RANDOMNESS_PLACEHOLDER" }
]
}

View File

@@ -0,0 +1,13 @@
package subcow
import (
"fmt"
"net/http"
say "github.com/dhruvbird/go-cowsay"
)
// Handler function
func Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, say.Format("subcow:RANDOMNESS_PLACEHOLDER"))
}

View File

@@ -0,0 +1,33 @@
/* global beforeAll, expect, it, jest */
const fs = require('fs');
const path = require('path');
const {
packAndDeploy,
testDeployment,
} = require('../../../test/lib/deployment/test-deployment.js');
jest.setTimeout(4 * 60 * 1000);
const buildUtilsUrl = '@canary';
let builderUrl;
beforeAll(async () => {
const builderPath = path.resolve(__dirname, '..');
builderUrl = await packAndDeploy(builderPath);
console.log('builderUrl', builderUrl);
});
const fixturesPath = path.resolve(__dirname, 'fixtures');
// eslint-disable-next-line no-restricted-syntax
for (const fixture of fs.readdirSync(fixturesPath)) {
// eslint-disable-next-line no-loop-func
it(`should build ${fixture}`, async () => {
await expect(
testDeployment(
{ builderUrl, buildUtilsUrl },
path.join(fixturesPath, fixture),
),
).resolves.toBeDefined();
});
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@now/html-minifier", "name": "@now/html-minifier",
"version": "1.0.8-canary.1", "version": "1.1.0",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@now/lambda", "name": "@now/lambda",
"version": "0.4.10-canary.1", "version": "0.5.0",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@now/md", "name": "@now/md",
"version": "0.4.10-canary.2", "version": "0.5.0",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -43,17 +43,8 @@ exports.build = async ({ files, entrypoint, workPath }) => {
return glob('**', outDir, mountpoint); return glob('**', outDir, mountpoint);
}; };
exports.prepareCache = async ({ cachePath }) => { exports.prepareCache = async ({ workPath }) => ({
console.log('writing package.json...'); ...(await glob('node_modules/**', workPath)),
const packageJson = { dependencies: { 'mdx-deck': '1.7.15' } }; ...(await glob('package-lock.json', workPath)),
const packageJsonPath = path.join(cachePath, 'package.json'); ...(await glob('yarn.lock', workPath)),
await writeFile(packageJsonPath, JSON.stringify(packageJson)); });
console.log('running npm install...');
await runNpmInstall(path.dirname(packageJsonPath), ['--prod']);
return {
...(await glob('node_modules/**', cachePath)),
...(await glob('package-lock.json', cachePath)),
...(await glob('yarn.lock', cachePath)),
};
};

View File

@@ -1,6 +1,6 @@
{ {
"name": "@now/mdx-deck", "name": "@now/mdx-deck",
"version": "0.4.19-canary.1", "version": "0.5.0",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",

2
packages/now-next/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/dist
/src/now__bridge.d.ts

View File

@@ -0,0 +1,11 @@
#!/bin/bash
set -euo pipefail
bridge_entrypoint="$(node -p 'require.resolve("@now/node-bridge")')"
bridge_defs="$(dirname "$bridge_entrypoint")/bridge.d.ts"
if [ ! -e "$bridge_defs" ]; then
yarn install --cwd "$bridge_entrypoint"
fi
cp -v "$bridge_defs" src/now__bridge.d.ts

View File

@@ -1,18 +0,0 @@
const { Server } = require('http');
const next = require('next-server');
const url = require('url');
const { Bridge } = require('./now__bridge');
process.env.NODE_ENV = 'production';
const app = next({});
const server = new Server((req, res) => {
const parsedUrl = url.parse(req.url, true);
app.render(req, res, 'PATHNAME_PLACEHOLDER', parsedUrl.query, parsedUrl);
});
const bridge = new Bridge(server);
bridge.listen();
exports.launcher = bridge.launcher;

View File

@@ -1,9 +1,12 @@
{ {
"name": "@now/next", "name": "@now/next",
"version": "0.1.3-canary.2", "version": "0.2.0",
"license": "MIT", "license": "MIT",
"main": "./dist/index",
"scripts": { "scripts": {
"build": "tsc" "build": "./getBridgeTypes.sh && tsc",
"test": "npm run build && jest",
"prepublish": "yarn run build"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -11,9 +14,20 @@
"directory": "packages/now-next" "directory": "packages/now-next"
}, },
"dependencies": { "dependencies": {
"@now/node-bridge": "^1.0.0", "@now/node-bridge": "^1.1.0",
"execa": "^1.0.0",
"fs-extra": "^7.0.0", "fs-extra": "^7.0.0",
"get-port": "^5.0.0",
"resolve-from": "^5.0.0",
"semver": "^5.6.0" "semver": "^5.6.0"
},
"files": [
"dist"
],
"devDependencies": {
"@types/next-server": "^8.0.0",
"@types/resolve-from": "^5.0.1",
"@types/semver": "^6.0.0",
"jest": "^24.7.1",
"typescript": "^3.4.3"
} }
} }

View File

@@ -0,0 +1,41 @@
import resolveFrom from 'resolve-from';
import { parse } from 'url';
import getPort from 'get-port';
import { createServer } from 'http';
export interface ProcessEnv {
[key: string]: string;
}
async function main(env: ProcessEnv, cwd: string) {
const next = require(resolveFrom(cwd, 'next'));
const app = next({ dev: true, dir: cwd });
const handler = app.getRequestHandler();
const openPort = await getPort({
port: [ 5000, 4000 ]
});
const url = `http://localhost:${openPort}`;
// Prepare for incoming requests
await app.prepare();
createServer((req, res) => {
const parsedUrl = parse(req.url || '', true);
handler(req, res, parsedUrl);
}).listen(openPort, (error: NodeJS.ErrnoException) => {
if (error) {
console.error(error);
process.exit(1);
return;
}
if (process.send) {
process.send(url);
}
});
}
main(process.env as ProcessEnv, process.cwd());

View File

@@ -1,48 +1,58 @@
const { createLambda } = require('@now/build-utils/lambda'); // eslint-disable-line import/no-extraneous-dependencies import { ChildProcess, fork, SpawnOptions } from 'child_process';
const download = require('@now/build-utils/fs/download'); // eslint-disable-line import/no-extraneous-dependencies import {
const FileFsRef = require('@now/build-utils/file-fs-ref'); // eslint-disable-line import/no-extraneous-dependencies pathExists,
const FileBlob = require('@now/build-utils/file-blob'); // eslint-disable-line import/no-extraneous-dependencies readFile,
const path = require('path'); unlink as unlinkFile,
const { writeFile,
} from 'fs-extra';
import os from 'os';
import path from 'path';
import semver from 'semver';
import {
BuildOptions,
createLambda,
download,
FileBlob,
FileFsRef,
Files,
glob,
Lambda,
PrepareCacheOptions,
runNpmInstall, runNpmInstall,
runPackageJsonScript, runPackageJsonScript,
} = require('@now/build-utils/fs/run-user-scripts'); // eslint-disable-line import/no-extraneous-dependencies } from '@now/build-utils';
const glob = require('@now/build-utils/fs/glob'); // eslint-disable-line import/no-extraneous-dependencies
const { import nextLegacyVersions from './legacy-versions';
readFile, import {
writeFile,
unlink: unlinkFile,
remove: removePath,
mkdirp,
rename: renamePath,
pathExists,
} = require('fs-extra');
const semver = require('semver');
const nextLegacyVersions = require('./legacy-versions');
const {
excludeFiles, excludeFiles,
validateEntrypoint, getNextConfig,
getPathsInside,
getRoutes,
includeOnlyEntryDirectory, includeOnlyEntryDirectory,
normalizePackageJson, normalizePackageJson,
onlyStaticDirectory, onlyStaticDirectory,
getNextConfig, stringMap,
} = require('./utils'); validateEntrypoint,
} from './utils';
/** @typedef { import('@now/build-utils/file-ref').Files } Files */ interface BuildParamsMeta {
/** @typedef { import('@now/build-utils/fs/download').DownloadedFiles } DownloadedFiles */ isDev: boolean | undefined;
}
/** interface BuildParamsType extends BuildOptions {
* @typedef {Object} BuildParamsType files: Files;
* @property {Files} files - Files object entrypoint: string;
* @property {string} entrypoint - Entrypoint specified for the builder workPath: string;
* @property {string} workPath - Working directory for this build meta: BuildParamsMeta;
*/ }
export const version = 2;
/** /**
* Read package.json from files * Read package.json from files
* @param {string} entryPath
*/ */
async function readPackageJson(entryPath) { async function readPackageJson(entryPath: string) {
const packagePath = path.join(entryPath, 'package.json'); const packagePath = path.join(entryPath, 'package.json');
try { try {
@@ -55,29 +65,28 @@ async function readPackageJson(entryPath) {
/** /**
* Write package.json * Write package.json
* @param {string} workPath
* @param {Object} packageJson
*/ */
async function writePackageJson(workPath, packageJson) { async function writePackageJson(workPath: string, packageJson: Object) {
await writeFile( await writeFile(
path.join(workPath, 'package.json'), path.join(workPath, 'package.json'),
JSON.stringify(packageJson, null, 2), JSON.stringify(packageJson, null, 2)
); );
} }
/** /**
* Write .npmrc with npm auth token * Write .npmrc with npm auth token
* @param {string} workPath
* @param {string} token
*/ */
async function writeNpmRc(workPath, token) { async function writeNpmRc(workPath: string, token: string) {
await writeFile( await writeFile(
path.join(workPath, '.npmrc'), path.join(workPath, '.npmrc'),
`//registry.npmjs.org/:_authToken=${token}`, `//registry.npmjs.org/:_authToken=${token}`
); );
} }
function getNextVersion(packageJson) { function getNextVersion(packageJson: {
dependencies?: { [key: string]: string };
devDependencies?: { [key: string]: string };
}) {
let nextVersion; let nextVersion;
if (packageJson.dependencies && packageJson.dependencies.next) { if (packageJson.dependencies && packageJson.dependencies.next) {
nextVersion = packageJson.dependencies.next; nextVersion = packageJson.dependencies.next;
@@ -87,7 +96,7 @@ function getNextVersion(packageJson) {
return nextVersion; return nextVersion;
} }
function isLegacyNext(nextVersion) { function isLegacyNext(nextVersion: string) {
// If version is using the dist-tag instead of a version range // If version is using the dist-tag instead of a version range
if (nextVersion === 'canary' || nextVersion === 'latest') { if (nextVersion === 'canary' || nextVersion === 'latest') {
return false; return false;
@@ -107,34 +116,95 @@ function isLegacyNext(nextVersion) {
return true; return true;
} }
exports.config = { const name = '[@now/next]';
const urls: stringMap = {};
function startDevServer(entryPath: string) {
const forked = fork(path.join(__dirname, 'dev-server.js'), [], {
cwd: entryPath,
execArgv: [],
env: {
NOW_REGION: 'dev1',
},
});
const getUrl = () =>
new Promise<string>((resolve, reject) => {
forked.on('message', resolve);
forked.on('error', reject);
});
return { forked, getUrl };
}
export const config = {
maxLambdaSize: '5mb', maxLambdaSize: '5mb',
}; };
/** export const build = async ({
* @param {BuildParamsType} buildParams files,
* @returns {Promise<Files>} workPath,
*/ entrypoint,
exports.build = async ({ files, workPath, entrypoint }) => { meta = {} as BuildParamsMeta,
}: BuildParamsType): Promise<{
routes?: any[];
output: Files;
watch?: string[];
childProcesses: ChildProcess[];
}> => {
validateEntrypoint(entrypoint); validateEntrypoint(entrypoint);
console.log('downloading user files...'); const routes: any[] = [];
const entryDirectory = path.dirname(entrypoint); const entryDirectory = path.dirname(entrypoint);
await download(files, workPath);
const entryPath = path.join(workPath, entryDirectory); const entryPath = path.join(workPath, entryDirectory);
const dotNext = path.join(entryPath, '.next');
if (await pathExists(path.join(entryPath, '.next'))) { console.log(`${name} Downloading user files...`);
console.warn( await download(files, workPath, meta);
'WARNING: You should probably not upload the `.next` directory. See https://zeit.co/docs/v2/deployments/official-builders/next-js-now-next/ for more information.',
const pkg = await readPackageJson(entryPath);
const nextVersion = getNextVersion(pkg);
if (!nextVersion) {
throw new Error(
'No Next.js version could be detected in "package.json". Make sure `"next"` is installed in "dependencies" or "devDependencies"'
); );
} }
const pkg = await readPackageJson(entryPath); process.env.__NEXT_BUILDER_EXPERIMENTAL_TARGET = 'serverless';
const nextVersion = getNextVersion(pkg); if (meta.isDev) {
if (!nextVersion) { // eslint-disable-next-line no-underscore-dangle
throw new Error( process.env.__NEXT_BUILDER_EXPERIMENTAL_DEBUG = 'true';
'No Next.js version could be detected in "package.json". Make sure `"next"` is installed in "dependencies" or "devDependencies"', 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']);
const { forked, getUrl } = startDevServer(entryPath);
urls[entrypoint] = await getUrl();
childProcess = forked;
console.log(
`${name} Development server for ${entrypoint} running at ${
urls[entrypoint]
}`
);
}
const pathsInside = getPathsInside(entryDirectory, files);
return {
output: {},
routes: getRoutes(entryDirectory, pathsInside, files, urls[entrypoint]),
watch: pathsInside,
childProcesses: childProcess ? [childProcess] : [],
};
}
if (await pathExists(dotNext)) {
console.warn(
'WARNING: You should not upload the `.next` directory. See https://zeit.co/docs/v2/deployments/official-builders/next-js-now-next/ for more details.'
); );
} }
@@ -156,7 +226,7 @@ exports.build = async ({ files, workPath, entrypoint }) => {
} }
console.warn( console.warn(
"WARNING: your application is being deployed in @now/next's legacy mode. http://err.sh/zeit/now-builders/now-next-legacy-mode", "WARNING: your application is being deployed in @now/next's legacy mode. http://err.sh/zeit/now-builders/now-next-legacy-mode"
); );
console.log('normalizing package.json'); console.log('normalizing package.json');
@@ -165,7 +235,7 @@ exports.build = async ({ files, workPath, entrypoint }) => {
await writePackageJson(entryPath, packageJson); await writePackageJson(entryPath, packageJson);
} else if (!pkg.scripts || !pkg.scripts['now-build']) { } else if (!pkg.scripts || !pkg.scripts['now-build']) {
console.warn( console.warn(
'WARNING: "now-build" script not found. Adding \'"now-build": "next build"\' to "package.json" automatically', 'WARNING: "now-build" script not found. Adding \'"now-build": "next build"\' to "package.json" automatically'
); );
pkg.scripts = { pkg.scripts = {
'now-build': 'next build', 'now-build': 'next build',
@@ -182,8 +252,15 @@ exports.build = async ({ files, workPath, entrypoint }) => {
console.log('installing dependencies...'); console.log('installing dependencies...');
await runNpmInstall(entryPath, ['--prefer-offline']); await runNpmInstall(entryPath, ['--prefer-offline']);
console.log('running user script...'); console.log('running user script...');
await runPackageJsonScript(entryPath, 'now-build'); 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);
if (isLegacy) { if (isLegacy) {
console.log('running npm install --production...'); console.log('running npm install --production...');
@@ -194,21 +271,21 @@ exports.build = async ({ files, workPath, entrypoint }) => {
await unlinkFile(path.join(entryPath, '.npmrc')); await unlinkFile(path.join(entryPath, '.npmrc'));
} }
const lambdas = {}; const lambdas: { [key: string]: Lambda } = {};
if (isLegacy) { if (isLegacy) {
const filesAfterBuild = await glob('**', entryPath); const filesAfterBuild = await glob('**', entryPath);
console.log('preparing lambda files...'); console.log('preparing lambda files...');
let buildId; let buildId: string;
try { try {
buildId = await readFile( buildId = await readFile(
path.join(entryPath, '.next', 'BUILD_ID'), path.join(entryPath, '.next', 'BUILD_ID'),
'utf8', 'utf8'
); );
} catch (err) { } catch (err) {
console.error( console.error(
'BUILD_ID not found in ".next". The "package.json" "build" script did not run "next build"', 'BUILD_ID not found in ".next". The "package.json" "build" script did not run "next build"'
); );
throw new Error('Missing BUILD_ID'); throw new Error('Missing BUILD_ID');
} }
@@ -216,12 +293,12 @@ exports.build = async ({ files, workPath, entrypoint }) => {
const dotNextServerRootFiles = await glob('.next/server/*', entryPath); const dotNextServerRootFiles = await glob('.next/server/*', entryPath);
const nodeModules = excludeFiles( const nodeModules = excludeFiles(
await glob('node_modules/**', entryPath), await glob('node_modules/**', entryPath),
file => file.startsWith('node_modules/.cache'), file => file.startsWith('node_modules/.cache')
); );
const launcherFiles = { const launcherFiles = {
'now__bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }), 'now__bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
}; };
const nextFiles = { const nextFiles: { [key: string]: FileFsRef } = {
...nodeModules, ...nodeModules,
...dotNextRootFiles, ...dotNextRootFiles,
...dotNextServerRootFiles, ...dotNextServerRootFiles,
@@ -232,13 +309,13 @@ exports.build = async ({ files, workPath, entrypoint }) => {
} }
const pages = await glob( const pages = await glob(
'**/*.js', '**/*.js',
path.join(entryPath, '.next', 'server', 'static', buildId, 'pages'), path.join(entryPath, '.next', 'server', 'static', buildId, 'pages')
); );
const launcherPath = path.join(__dirname, 'legacy-launcher.js'); const launcherPath = path.join(__dirname, 'legacy-launcher.js');
const launcherData = await readFile(launcherPath, 'utf8'); const launcherData = await readFile(launcherPath, 'utf8');
await Promise.all( await Promise.all(
Object.keys(pages).map(async (page) => { Object.keys(pages).map(async page => {
// These default pages don't have to be handled as they'd always 404 // These default pages don't have to be handled as they'd always 404
if (['_app.js', '_error.js', '_document.js'].includes(page)) { if (['_app.js', '_error.js', '_document.js'].includes(page)) {
return; return;
@@ -247,7 +324,7 @@ exports.build = async ({ files, workPath, entrypoint }) => {
const pathname = page.replace(/\.js$/, ''); const pathname = page.replace(/\.js$/, '');
const launcher = launcherData.replace( const launcher = launcherData.replace(
'PATHNAME_PLACEHOLDER', 'PATHNAME_PLACEHOLDER',
`/${pathname.replace(/(^|\/)index$/, '')}`, `/${pathname.replace(/(^|\/)index$/, '')}`
); );
const pageFiles = { const pageFiles = {
@@ -276,7 +353,7 @@ exports.build = async ({ files, workPath, entrypoint }) => {
runtime: 'nodejs8.10', runtime: 'nodejs8.10',
}); });
console.log(`Created lambda for page: "${page}"`); console.log(`Created lambda for page: "${page}"`);
}), })
); );
} else { } else {
console.log('preparing lambda files...'); console.log('preparing lambda files...');
@@ -288,7 +365,7 @@ exports.build = async ({ files, workPath, entrypoint }) => {
}; };
const pages = await glob( const pages = await glob(
'**/*.js', '**/*.js',
path.join(entryPath, '.next', 'serverless', 'pages'), path.join(entryPath, '.next', 'serverless', 'pages')
); );
const pageKeys = Object.keys(pages); const pageKeys = Object.keys(pages);
@@ -303,14 +380,14 @@ exports.build = async ({ files, workPath, entrypoint }) => {
} }
throw new Error( throw new Error(
'No serverless pages were built. https://err.sh/zeit/now-builders/now-next-no-serverless-pages-built', 'No serverless pages were built. https://err.sh/zeit/now-builders/now-next-no-serverless-pages-built'
); );
} }
// An optional assets folder that is placed alongside every page entrypoint // An optional assets folder that is placed alongside every page entrypoint
const assets = await glob( const assets = await glob(
'assets/**', 'assets/**',
path.join(entryPath, '.next', 'serverless'), path.join(entryPath, '.next', 'serverless')
); );
const assetKeys = Object.keys(assets); const assetKeys = Object.keys(assets);
@@ -320,7 +397,7 @@ exports.build = async ({ files, workPath, entrypoint }) => {
} }
await Promise.all( await Promise.all(
pageKeys.map(async (page) => { pageKeys.map(async page => {
// These default pages don't have to be handled as they'd always 404 // These default pages don't have to be handled as they'd always 404
if (['_app.js', '_error.js', '_document.js'].includes(page)) { if (['_app.js', '_error.js', '_document.js'].includes(page)) {
return; return;
@@ -339,39 +416,48 @@ exports.build = async ({ files, workPath, entrypoint }) => {
runtime: 'nodejs8.10', runtime: 'nodejs8.10',
}); });
console.log(`Created lambda for page: "${page}"`); console.log(`Created lambda for page: "${page}"`);
}), })
); );
} }
const nextStaticFiles = await glob( const nextStaticFiles = await glob(
'**', '**',
path.join(entryPath, '.next', 'static'), path.join(entryPath, '.next', 'static')
); );
const staticFiles = Object.keys(nextStaticFiles).reduce( const staticFiles = Object.keys(nextStaticFiles).reduce(
(mappedFiles, file) => ({ (mappedFiles, file) => ({
...mappedFiles, ...mappedFiles,
[path.join(entryDirectory, `_next/static/${file}`)]: nextStaticFiles[file], [path.join(entryDirectory, `_next/static/${file}`)]: nextStaticFiles[
file
],
}), }),
{}, {}
); );
const staticDirectoryFiles = onlyStaticDirectory( const staticDirectoryFiles = onlyStaticDirectory(
includeOnlyEntryDirectory(files, entryDirectory), includeOnlyEntryDirectory(files, entryDirectory),
entryDirectory, entryDirectory
); );
return { ...lambdas, ...staticFiles, ...staticDirectoryFiles }; return {
output: { ...lambdas, ...staticFiles, ...staticDirectoryFiles },
routes: [],
watch: [],
childProcesses: [],
};
}; };
exports.prepareCache = async ({ cachePath, workPath, entrypoint }) => { export const prepareCache = async ({
workPath,
entrypoint,
}: PrepareCacheOptions) => {
console.log('preparing cache ...'); console.log('preparing cache ...');
const entryDirectory = path.dirname(entrypoint); const entryDirectory = path.dirname(entrypoint);
const entryPath = path.join(workPath, entryDirectory); const entryPath = path.join(workPath, entryDirectory);
const cacheEntryPath = path.join(cachePath, entryDirectory);
const pkg = await readPackageJson(entryPath); const pkg = await readPackageJson(entryPath);
const nextVersion = getNextVersion(pkg); const nextVersion = getNextVersion(pkg);
if (!nextVersion) throw new Error('Could not parse Next.js version');
const isLegacy = isLegacyNext(nextVersion); const isLegacy = isLegacyNext(nextVersion);
if (isLegacy) { if (isLegacy) {
@@ -379,25 +465,14 @@ exports.prepareCache = async ({ cachePath, workPath, entrypoint }) => {
return {}; return {};
} }
console.log('clearing old cache ...');
await removePath(cacheEntryPath);
await mkdirp(cacheEntryPath);
console.log('copying build files for cache ...');
await renamePath(entryPath, cacheEntryPath);
console.log('producing cache file manifest ...'); console.log('producing cache file manifest ...');
const cacheEntrypoint = path.relative(workPath, entryPath);
const cacheEntrypoint = path.relative(cachePath, cacheEntryPath); const cache = {
return { ...(await glob(path.join(cacheEntrypoint, 'node_modules/**'), workPath)),
...(await glob( ...(await glob(path.join(cacheEntrypoint, '.next/cache/**'), workPath)),
path.join( ...(await glob(path.join(cacheEntrypoint, 'package-lock.json'), workPath)),
cacheEntrypoint, ...(await glob(path.join(cacheEntrypoint, 'yarn.lock'), workPath)),
'node_modules/{**,!.*,.yarn*,.cache/next-minifier/**}',
),
cachePath,
)),
...(await glob(path.join(cacheEntrypoint, 'package-lock.json'), cachePath)),
...(await glob(path.join(cacheEntrypoint, 'yarn.lock'), cachePath)),
}; };
console.log('cache file manifest produced');
return cache;
}; };

View File

@@ -1,4 +1,6 @@
process.env.NODE_ENV = 'production'; if (!process.env.NODE_ENV) {
process.env.NODE_ENV = process.env.NOW_REGION === 'dev1' ? 'development' : 'production';
}
const { Server } = require('http'); const { Server } = require('http');
const { Bridge } = require('./now__bridge'); const { Bridge } = require('./now__bridge');

View File

@@ -0,0 +1,20 @@
import { Server } from 'http';
import next from 'next-server';
import url from 'url';
import { Bridge } from './now__bridge';
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = process.env.NOW_REGION === 'dev1' ? 'development' : 'production';
}
const app = next({});
const server = new Server((req, res) => {
const parsedUrl = url.parse(req.url || '', true);
app.render(req, res, 'PATHNAME_PLACEHOLDER', parsedUrl.query, parsedUrl);
});
const bridge = new Bridge(server);
bridge.listen();
exports.launcher = bridge.launcher;

View File

@@ -1,4 +1,4 @@
module.exports = [ export default [
'0.1.0', '0.1.0',
'0.1.1', '0.1.1',
'0.2.0', '0.2.0',

View File

@@ -1,16 +1,13 @@
const fs = require('fs-extra'); import fs from 'fs-extra';
const path = require('path'); import path from 'path';
import { Files } from '@now/build-utils';
/** @typedef { import('@now/build-utils/file-ref') } FileRef */ type stringMap = {[key: string]: string};
/** @typedef { import('@now/build-utils/file-fs-ref') } FileFsRef */
/** @typedef {{[filePath: string]: FileRef|FileFsRef}} Files */
/** /**
* Validate if the entrypoint is allowed to be used * Validate if the entrypoint is allowed to be used
* @param {string} entrypoint
* @throws {Error}
*/ */
function validateEntrypoint(entrypoint) { function validateEntrypoint(entrypoint: string) {
if ( if (
!/package\.json$/.exec(entrypoint) !/package\.json$/.exec(entrypoint)
&& !/next\.config\.js$/.exec(entrypoint) && !/next\.config\.js$/.exec(entrypoint)
@@ -21,21 +18,10 @@ function validateEntrypoint(entrypoint) {
} }
} }
/**
* This callback type is called `requestCallback` and is displayed as a global symbol.
*
* @callback matcher
* @param {string} filePath
* @returns {boolean}
*/
/** /**
* Exclude certain files from the files object * Exclude certain files from the files object
* @param {Files} files
* @param {matcher} matcher
* @returns {Files}
*/ */
function excludeFiles(files, matcher) { function excludeFiles(files: Files, matcher: (filePath: string) => boolean): Files {
return Object.keys(files).reduce((newFiles, filePath) => { return Object.keys(files).reduce((newFiles, filePath) => {
if (matcher(filePath)) { if (matcher(filePath)) {
return newFiles; return newFiles;
@@ -49,16 +35,13 @@ function excludeFiles(files, matcher) {
/** /**
* Creates a new Files object holding only the entrypoint files * Creates a new Files object holding only the entrypoint files
* @param {Files} files
* @param {string} entryDirectory
* @returns {Files}
*/ */
function includeOnlyEntryDirectory(files, entryDirectory) { function includeOnlyEntryDirectory(files: Files, entryDirectory: string): Files {
if (entryDirectory === '.') { if (entryDirectory === '.') {
return files; return files;
} }
function matcher(filePath) { function matcher(filePath: string) {
return !filePath.startsWith(entryDirectory); return !filePath.startsWith(entryDirectory);
} }
@@ -67,10 +50,8 @@ function includeOnlyEntryDirectory(files, entryDirectory) {
/** /**
* Exclude package manager lockfiles from files * Exclude package manager lockfiles from files
* @param {Files} files
* @returns {Files}
*/ */
function excludeLockFiles(files) { function excludeLockFiles(files: Files): Files {
const newFiles = files; const newFiles = files;
if (newFiles['package-lock.json']) { if (newFiles['package-lock.json']) {
delete newFiles['package-lock.json']; delete newFiles['package-lock.json'];
@@ -83,11 +64,9 @@ function excludeLockFiles(files) {
/** /**
* Include the static directory from files * Include the static directory from files
* @param {Files} files
* @returns {Files}
*/ */
function onlyStaticDirectory(files, entryDir) { function onlyStaticDirectory(files: Files, entryDir: string): Files {
function matcher(filePath) { function matcher(filePath: string) {
return !filePath.startsWith(path.join(entryDir, 'static')); return !filePath.startsWith(path.join(entryDir, 'static'));
} }
@@ -96,11 +75,10 @@ function onlyStaticDirectory(files, entryDir) {
/** /**
* Enforce specific package.json configuration for smallest possible lambda * Enforce specific package.json configuration for smallest possible lambda
* @param {{dependencies?: any, devDependencies?: any, scripts?: any}} defaultPackageJson
*/ */
function normalizePackageJson(defaultPackageJson = {}) { function normalizePackageJson(defaultPackageJson: {dependencies?: stringMap, devDependencies?: stringMap, scripts?: stringMap} = {}) {
const dependencies = {}; const dependencies: stringMap = {};
const devDependencies = { const devDependencies: stringMap = {
...defaultPackageJson.dependencies, ...defaultPackageJson.dependencies,
...defaultPackageJson.devDependencies, ...defaultPackageJson.devDependencies,
}; };
@@ -139,7 +117,7 @@ function normalizePackageJson(defaultPackageJson = {}) {
}; };
} }
async function getNextConfig(workPath, entryPath) { async function getNextConfig(workPath: string, entryPath: string) {
const entryConfig = path.join(entryPath, './next.config.js'); const entryConfig = path.join(entryPath, './next.config.js');
if (await fs.pathExists(entryConfig)) { if (await fs.pathExists(entryConfig)) {
return fs.readFile(entryConfig, 'utf8'); return fs.readFile(entryConfig, 'utf8');
@@ -153,7 +131,84 @@ async function getNextConfig(workPath, entryPath) {
return null; return null;
} }
module.exports = { function pathIsInside(firstPath: string, secondPath: string) {
return !path.relative(firstPath, secondPath).startsWith('..');
}
function getPathsInside(entryDirectory: string, files: Files) {
const watch: string[] = [];
for (const file of Object.keys(files)) {
// If the file is outside of the entrypoint directory, we do
// not want to monitor it for changes.
if (!pathIsInside(entryDirectory, file)) {
continue;
}
watch.push(file);
}
return watch;
}
function getRoutes(entryDirectory: string, pathsInside: string[], files: Files, url: string): any[] {
const filesInside: Files = {};
const prefix = entryDirectory === `.` ? `/` : `/${entryDirectory}/`;
for (const file of Object.keys(files)) {
if (!pathsInside.includes(file)) {
continue;
}
filesInside[file] = files[file];
}
const routes: any[] = [
{
src: `${prefix}_next/(.*)`,
dest: `${url}/_next/$1`
},
{
src: `${prefix}static/(.*)`,
dest: `${url}/static/$1`
}
];
for (const file of Object.keys(filesInside)) {
const relativePath = path.relative(entryDirectory, file);
const isPage = pathIsInside('pages', relativePath);
if (!isPage) {
continue;
}
const relativeToPages = path.relative('pages', relativePath);
const extension = path.extname(relativeToPages);
const pageName = relativeToPages.replace(extension, '');
if (pageName.startsWith('_')) {
continue;
}
routes.push({
src: `${prefix}${pageName}`,
dest: `${url}/${pageName}`
});
if (pageName.endsWith('index')) {
const resolvedIndex = pageName.replace('/index', '').replace('index', '');
routes.push({
src: `${prefix}${resolvedIndex}`,
dest: `${url}/${resolvedIndex}`
});
}
}
return routes;
}
export {
excludeFiles, excludeFiles,
validateEntrypoint, validateEntrypoint,
includeOnlyEntryDirectory, includeOnlyEntryDirectory,
@@ -161,4 +216,7 @@ module.exports = {
normalizePackageJson, normalizePackageJson,
onlyStaticDirectory, onlyStaticDirectory,
getNextConfig, getNextConfig,
getPathsInside,
getRoutes,
stringMap,
}; };

View File

@@ -1,17 +1,17 @@
/* global it, expect */ /* global it, expect */
const path = require('path'); const path = require('path');
const runBuildLambda = require('../../lib/run-build-lambda'); const runBuildLambda = require('../../../../test/lib/run-build-lambda');
const FOUR_MINUTES = 240000; const FOUR_MINUTES = 240000;
it( it(
'Should build the standard example', 'Should build the standard example',
async () => { async () => {
const { buildResult } = await runBuildLambda( const {
path.join(__dirname, 'standard'), buildResult: { output },
); } = await runBuildLambda(path.join(__dirname, 'standard'));
expect(buildResult.index).toBeDefined(); expect(output.index).toBeDefined();
const filePaths = Object.keys(buildResult); const filePaths = Object.keys(output);
const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/)); const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/));
const hasUnderScoreErrorStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_error\.js$/)); const hasUnderScoreErrorStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_error\.js$/));
expect(hasUnderScoreAppStaticFile).toBeTruthy(); expect(hasUnderScoreAppStaticFile).toBeTruthy();
@@ -23,12 +23,12 @@ it(
it( it(
'Should build the monorepo example', 'Should build the monorepo example',
async () => { async () => {
const { buildResult } = await runBuildLambda( const {
path.join(__dirname, 'monorepo'), buildResult: { output },
); } = await runBuildLambda(path.join(__dirname, 'monorepo'));
expect(buildResult['www/index']).toBeDefined(); expect(output['www/index']).toBeDefined();
expect(buildResult['www/static/test.txt']).toBeDefined(); expect(output['www/static/test.txt']).toBeDefined();
const filePaths = Object.keys(buildResult); const filePaths = Object.keys(output);
const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/)); const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/));
const hasUnderScoreErrorStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_error\.js$/)); const hasUnderScoreErrorStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_error\.js$/));
expect(hasUnderScoreAppStaticFile).toBeTruthy(); expect(hasUnderScoreAppStaticFile).toBeTruthy();
@@ -40,11 +40,11 @@ it(
it( it(
'Should build the legacy standard example', 'Should build the legacy standard example',
async () => { async () => {
const { buildResult } = await runBuildLambda( const {
path.join(__dirname, 'legacy-standard'), buildResult: { output },
); } = await runBuildLambda(path.join(__dirname, 'legacy-standard'));
expect(buildResult.index).toBeDefined(); expect(output.index).toBeDefined();
const filePaths = Object.keys(buildResult); const filePaths = Object.keys(output);
const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/)); const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/));
const hasUnderScoreErrorStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_error\.js$/)); const hasUnderScoreErrorStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_error\.js$/));
expect(hasUnderScoreAppStaticFile).toBeTruthy(); expect(hasUnderScoreAppStaticFile).toBeTruthy();
@@ -56,10 +56,10 @@ it(
it( it(
'Should build the legacy custom dependency test', 'Should build the legacy custom dependency test',
async () => { async () => {
const { buildResult } = await runBuildLambda( const {
path.join(__dirname, 'legacy-custom-dependency'), buildResult: { output },
); } = await runBuildLambda(path.join(__dirname, 'legacy-custom-dependency'));
expect(buildResult.index).toBeDefined(); expect(output.index).toBeDefined();
}, },
FOUR_MINUTES, FOUR_MINUTES,
); );
@@ -78,10 +78,10 @@ it('Should throw when package.json or next.config.js is not the "src"', async ()
it( it(
'Should build the static-files test on legacy', 'Should build the static-files test on legacy',
async () => { async () => {
const { buildResult } = await runBuildLambda( const {
path.join(__dirname, 'legacy-static-files'), buildResult: { output },
); } = await runBuildLambda(path.join(__dirname, 'legacy-static-files'));
expect(buildResult['static/test.txt']).toBeDefined(); expect(output['static/test.txt']).toBeDefined();
}, },
FOUR_MINUTES, FOUR_MINUTES,
); );
@@ -89,10 +89,10 @@ it(
it( it(
'Should build the static-files test', 'Should build the static-files test',
async () => { async () => {
const { buildResult } = await runBuildLambda( const {
path.join(__dirname, 'static-files'), buildResult: { output },
); } = await runBuildLambda(path.join(__dirname, 'static-files'));
expect(buildResult['static/test.txt']).toBeDefined(); expect(output['static/test.txt']).toBeDefined();
}, },
FOUR_MINUTES, FOUR_MINUTES,
); );

View File

@@ -0,0 +1,90 @@
/* global expect, it, jest */
const path = require('path');
const os = require('os');
const { build } = require('@now/next');
const { FileBlob } = require('@now/build-utils');
jest.setTimeout(45000);
describe('build meta dev', () => {
const files = {
'next.config.js': new FileBlob({
mode: 0o777,
data: `
module.exports = {
target: 'serverless'
}
`,
}),
'pages/index.js': new FileBlob({
mode: 0o777,
data: `
export default () => 'Index page'
`,
}),
'package.json': new FileBlob({
mode: 0o777,
data: `
{
"scripts": {
"now-build": "next build"
},
"dependencies": {
"next": "8",
"react": "16",
"react-dom": "16"
}
}
`,
}),
};
const entrypoint = 'next.config.js';
const workPath = path.join(
os.tmpdir(),
Math.random()
.toString()
.slice(3),
);
console.log('workPath directory: ', workPath);
/*
it('should have builder v2 response isDev=false', async () => {
const meta = { isDev: false, requestPath: null };
const { output, routes, watch } = await build({
files,
workPath,
entrypoint,
meta,
});
//console.log('output: ', Object.keys(output));
expect(Object.keys(output).length).toBe(7);
expect(output.index.type).toBe('Lambda');
expect(routes.length).toBe(0);
expect(watch.length).toBe(0);
});
*/
it('should have builder v2 response isDev=true', async () => {
const meta = { isDev: true, requestPath: null };
const {
output, routes, watch, childProcesses,
} = await build({
files,
workPath,
entrypoint,
meta,
});
routes.forEach((route) => {
// eslint-disable-next-line no-param-reassign
route.dest = route.dest.replace(':4000', ':5000');
});
expect(output).toEqual({});
expect(routes).toEqual([
{ src: '/_next/(.*)', dest: 'http://localhost:5000/_next/$1' },
{ src: '/static/(.*)', dest: 'http://localhost:5000/static/$1' },
{ src: '/index', dest: 'http://localhost:5000/index' },
{ src: '/', dest: 'http://localhost:5000/' },
]);
expect(watch).toEqual(['next.config.js', 'pages/index.js', 'package.json']);
childProcesses.forEach(cp => cp.kill());
});
});

View File

@@ -5,7 +5,7 @@ const {
includeOnlyEntryDirectory, includeOnlyEntryDirectory,
normalizePackageJson, normalizePackageJson,
getNextConfig, getNextConfig,
} = require('@now/next/utils'); } = require('@now/next/dist/utils');
const FileRef = require('@now/build-utils/file-ref'); // eslint-disable-line import/no-extraneous-dependencies const FileRef = require('@now/build-utils/file-ref'); // eslint-disable-line import/no-extraneous-dependencies
describe('getNextConfig', () => { describe('getNextConfig', () => {

View File

@@ -1,20 +1,18 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2017", "strict": true,
"esModuleInterop": true,
"lib": ["esnext"],
"target": "esnext",
"module": "commonjs", "module": "commonjs",
"lib": ["es2017"], "outDir": "dist",
"allowJs": true, "sourceMap": false,
"checkJs": true, "declaration": false
"noEmit": true,
"strict": false,
"types": ["node"],
"esModuleInterop": true
}, },
"include": [ "include": [
"./" "src/**/*"
], ],
"exclude": [ "exclude": [
"./launcher.js", "node_modules"
"./legacy-launcher.js"
] ]
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@now/node-bridge", "name": "@now/node-bridge",
"version": "1.0.1-canary.1", "version": "1.1.0",
"license": "MIT", "license": "MIT",
"main": "./index.js", "main": "./index.js",
"repository": { "repository": {

View File

@@ -27,6 +27,18 @@ export interface NowProxyResponse {
encoding: string; encoding: string;
} }
/**
* If the `http.Server` handler function throws an error asynchronously,
* then it ends up being an unhandled rejection which doesn't kill the node
* process which causes the HTTP request to hang indefinitely. So print the
* error here and force the process to exit so that the lambda invocation
* returns an Unhandled error quickly.
*/
process.on('unhandledRejection', (err: Error) => {
console.error('Unhandled rejection:', err);
process.exit(1);
});
function normalizeNowProxyEvent(event: NowProxyEvent): NowProxyRequest { function normalizeNowProxyEvent(event: NowProxyEvent): NowProxyRequest {
let bodyBuffer: Buffer | null; let bodyBuffer: Buffer | null;
const { method, path, headers, encoding, body } = JSON.parse(event.body); const { method, path, headers, encoding, body } = JSON.parse(event.body);

View File

@@ -5,7 +5,6 @@ const FileFsRef = require('@now/build-utils/file-fs-ref.js'); // eslint-disable-
const fs = require('fs-extra'); const fs = require('fs-extra');
const glob = require('@now/build-utils/fs/glob.js'); // eslint-disable-line import/no-extraneous-dependencies const glob = require('@now/build-utils/fs/glob.js'); // eslint-disable-line import/no-extraneous-dependencies
const path = require('path'); const path = require('path');
const rename = require('@now/build-utils/fs/rename.js'); // eslint-disable-line import/no-extraneous-dependencies
const { const {
runNpmInstall, runNpmInstall,
runPackageJsonScript, runPackageJsonScript,
@@ -31,41 +30,23 @@ async function downloadInstallAndBundle(
{ files, entrypoint, workPath }, { files, entrypoint, workPath },
{ npmArguments = [] } = {}, { npmArguments = [] } = {},
) { ) {
const userPath = path.join(workPath, 'user');
const nccPath = path.join(workPath, 'ncc');
console.log('downloading user files...'); console.log('downloading user files...');
const downloadedFiles = await download(files, userPath); const downloadedFiles = await download(files, workPath);
console.log("installing dependencies for user's code..."); console.log("installing dependencies for user's code...");
const entrypointFsDirname = path.join(userPath, path.dirname(entrypoint)); const entrypointFsDirname = path.join(workPath, path.dirname(entrypoint));
await runNpmInstall(entrypointFsDirname, npmArguments); await runNpmInstall(entrypointFsDirname, npmArguments);
return [downloadedFiles, entrypointFsDirname];
console.log('writing ncc package.json...');
await download(
{
'package.json': new FileBlob({
data: JSON.stringify({
license: 'UNLICENSED',
dependencies: {
'@zeit/ncc': '0.16.1',
},
}),
}),
},
nccPath,
);
console.log('installing dependencies for ncc...');
await runNpmInstall(nccPath, npmArguments);
return [downloadedFiles, userPath, nccPath, entrypointFsDirname];
} }
async function compile(workNccPath, downloadedFiles, entrypoint, config) { async function compile(workPath, downloadedFiles, entrypoint, config) {
const input = downloadedFiles[entrypoint].fsPath; const input = downloadedFiles[entrypoint].fsPath;
const inputDir = path.dirname(input); const inputDir = path.dirname(input);
const ncc = require(path.join(workNccPath, 'node_modules/@zeit/ncc')); const ncc = require('@zeit/ncc');
const { code, assets } = await ncc(input, { sourceMap: true }); const { code, map, assets } = await ncc(input, {
sourceMap: true,
sourceMapRegister: true,
});
if (config && config.includeFiles) { if (config && config.includeFiles) {
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax
@@ -89,16 +70,16 @@ async function compile(workNccPath, downloadedFiles, entrypoint, config) {
} }
const preparedFiles = {}; const preparedFiles = {};
const blob = new FileBlob({ data: code });
// move all user code to 'user' subdirectory // move all user code to 'user' subdirectory
preparedFiles[path.join('user', entrypoint)] = blob; preparedFiles[entrypoint] = new FileBlob({ data: code });
preparedFiles[`${entrypoint.replace('.ts', '.js')}.map`] = new FileBlob({
data: map,
});
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax
for (const assetName of Object.keys(assets)) { for (const assetName of Object.keys(assets)) {
const { source: data, permissions: mode } = assets[assetName]; const { source: data, permissions: mode } = assets[assetName];
const blob2 = new FileBlob({ data, mode }); const blob2 = new FileBlob({ data, mode });
preparedFiles[ preparedFiles[path.join(path.dirname(entrypoint), assetName)] = blob2;
path.join('user', path.dirname(entrypoint), assetName)
] = blob2;
} }
return preparedFiles; return preparedFiles;
@@ -115,12 +96,7 @@ exports.config = {
exports.build = async ({ exports.build = async ({
files, entrypoint, config, workPath, files, entrypoint, config, workPath,
}) => { }) => {
const [ const [downloadedFiles, entrypointFsDirname] = await downloadInstallAndBundle(
downloadedFiles,
workUserPath,
workNccPath,
entrypointFsDirname,
] = await downloadInstallAndBundle(
{ files, entrypoint, workPath }, { files, entrypoint, workPath },
{ npmArguments: ['--prefer-offline'] }, { npmArguments: ['--prefer-offline'] },
); );
@@ -132,13 +108,11 @@ exports.build = async ({
let preparedFiles; let preparedFiles;
if (config && config.bundle === false) { if (config && config.bundle === false) {
// move all user code to 'user' subdirectory preparedFiles = await glob('**', workPath);
preparedFiles = await glob('**', workUserPath);
preparedFiles = rename(preparedFiles, name => path.join('user', name));
} else { } else {
console.log('compiling entrypoint with ncc...'); console.log('compiling entrypoint with ncc...');
preparedFiles = await compile( preparedFiles = await compile(
workNccPath, workPath,
downloadedFiles, downloadedFiles,
entrypoint, entrypoint,
config, config,
@@ -149,10 +123,7 @@ exports.build = async ({
let launcherData = await fs.readFile(launcherPath, 'utf8'); let launcherData = await fs.readFile(launcherPath, 'utf8');
launcherData = launcherData.replace( launcherData = launcherData.replace(
'// PLACEHOLDER', '// PLACEHOLDER',
[ [`require("./${entrypoint}");`].join(' '),
'process.chdir("./user");',
`require("./${path.join('user', entrypoint)}");`,
].join(' '),
); );
const launcherFiles = { const launcherFiles = {
@@ -169,18 +140,8 @@ exports.build = async ({
return { [entrypoint]: lambda }; return { [entrypoint]: lambda };
}; };
exports.prepareCache = async ({ exports.prepareCache = async ({ workPath }) => ({
files, entrypoint, workPath, cachePath, ...(await glob('node_modules/**', workPath)),
}) => { ...(await glob('package-lock.json', workPath)),
await fs.remove(workPath); ...(await glob('yarn.lock', workPath)),
await downloadInstallAndBundle({ files, entrypoint, workPath: cachePath }); });
return {
...(await glob('user/node_modules/**', cachePath)),
...(await glob('user/package-lock.json', cachePath)),
...(await glob('user/yarn.lock', cachePath)),
...(await glob('ncc/node_modules/**', cachePath)),
...(await glob('ncc/package-lock.json', cachePath)),
...(await glob('ncc/yarn.lock', cachePath)),
};
};

View File

@@ -11,7 +11,7 @@ Server.prototype.listen = function listen() {
}; };
if (!process.env.NODE_ENV) { if (!process.env.NODE_ENV) {
process.env.NODE_ENV = 'production'; process.env.NODE_ENV = process.env.NOW_REGION === 'dev1' ? 'development' : 'production';
} }
try { try {
@@ -24,7 +24,8 @@ try {
); );
process.exit(1); process.exit(1);
} else { } else {
throw err; console.error(err);
process.exit(1);
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@now/node-server", "name": "@now/node-server",
"version": "0.5.2-canary.3", "version": "0.6.0",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -8,7 +8,8 @@
"directory": "packages/now-node-server" "directory": "packages/now-node-server"
}, },
"dependencies": { "dependencies": {
"@now/node-bridge": "^1.0.1-canary.1", "@now/node-bridge": "^1.1.0",
"@zeit/ncc": "0.18.1",
"fs-extra": "7.0.1" "fs-extra": "7.0.1"
}, },
"scripts": { "scripts": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@now/node", "name": "@now/node",
"version": "0.5.2-canary.5", "version": "0.6.0",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"repository": { "repository": {
@@ -9,7 +9,8 @@
"directory": "packages/now-node" "directory": "packages/now-node"
}, },
"dependencies": { "dependencies": {
"@now/node-bridge": "^1.0.1-canary.1", "@now/node-bridge": "^1.1.0",
"@zeit/ncc": "0.18.1",
"fs-extra": "7.0.1" "fs-extra": "7.0.1"
}, },
"scripts": { "scripts": {

View File

@@ -1,5 +1,5 @@
import { join, dirname } from 'path'; import { join, dirname, sep } from 'path';
import { remove, readFile } from 'fs-extra'; import { readFile } from 'fs-extra';
import { import {
glob, glob,
download, download,
@@ -30,42 +30,23 @@ async function downloadInstallAndBundle({
workPath, workPath,
npmArguments = [] npmArguments = []
}: DownloadOptions) { }: DownloadOptions) {
const userPath = join(workPath, 'user');
const nccPath = join(workPath, 'ncc');
console.log('downloading user files...'); console.log('downloading user files...');
const downloadedFiles = await download(files, userPath); const downloadedFiles = await download(files, workPath);
console.log("installing dependencies for user's code..."); console.log("installing dependencies for user's code...");
const entrypointFsDirname = join(userPath, dirname(entrypoint)); const entrypointFsDirname = join(workPath, dirname(entrypoint));
await runNpmInstall(entrypointFsDirname, npmArguments); await runNpmInstall(entrypointFsDirname, npmArguments);
console.log('writing ncc package.json...');
await download(
{
'package.json': new FileBlob({
data: JSON.stringify({
license: 'UNLICENSED',
dependencies: {
'@zeit/ncc': '0.16.1',
}
})
})
},
nccPath
);
console.log('installing dependencies for ncc...');
await runNpmInstall(nccPath, npmArguments);
const entrypointPath = downloadedFiles[entrypoint].fsPath; const entrypointPath = downloadedFiles[entrypoint].fsPath;
return { entrypointPath, workNccPath: nccPath, entrypointFsDirname }; return { entrypointPath, entrypointFsDirname };
} }
async function compile(workNccPath: string, entrypointPath: string, entrypoint: string, config: CompilerConfig) { async function compile(entrypointPath: string, entrypoint: string, config: CompilerConfig): Promise<Files> {
const input = entrypointPath; const input = entrypointPath;
const inputDir = dirname(input); const inputDir = dirname(input);
const ncc = require(join(workNccPath, 'node_modules/@zeit/ncc')); const rootIncludeFiles = inputDir.split(sep).pop() || '';
const { code, assets } = await ncc(input); const ncc = require('@zeit/ncc');
const { code, map, assets } = await ncc(input, { sourceMap: true, sourceMapRegister: true });
if (config && config.includeFiles) { if (config && config.includeFiles) {
for (const pattern of config.includeFiles) { for (const pattern of config.includeFiles) {
@@ -75,8 +56,15 @@ async function compile(workNccPath: string, entrypointPath: string, entrypoint:
const stream = files[assetName].toStream(); const stream = files[assetName].toStream();
const { mode } = files[assetName]; const { mode } = files[assetName];
const { data } = await FileBlob.fromStream({ stream }); const { data } = await FileBlob.fromStream({ stream });
let fullPath = join(rootIncludeFiles, assetName);
assets[assetName] = { // if asset contain directory
// no need to use `rootIncludeFiles`
if (assetName.includes(sep)) {
fullPath = assetName
}
assets[fullPath] = {
'source': data, 'source': data,
'permissions': mode 'permissions': mode
}; };
@@ -84,15 +72,15 @@ async function compile(workNccPath: string, entrypointPath: string, entrypoint:
} }
} }
const preparedFiles = {}; const preparedFiles: Files = {};
const blob = new FileBlob({ data: code });
// move all user code to 'user' subdirectory // move all user code to 'user' subdirectory
preparedFiles[join('user', entrypoint)] = blob; preparedFiles[entrypoint] = new FileBlob({ data: code });
preparedFiles[`${entrypoint.replace('.ts', '.js')}.map`] = new FileBlob({ data: map });
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax
for (const assetName of Object.keys(assets)) { for (const assetName of Object.keys(assets)) {
const { source: data, permissions: mode } = assets[assetName]; const { source: data, permissions: mode } = assets[assetName];
const blob2 = new FileBlob({ data, mode }); const blob2 = new FileBlob({ data, mode });
preparedFiles[join('user', dirname(entrypoint), assetName)] = blob2; preparedFiles[join(dirname(entrypoint), assetName)] = blob2;
} }
return preparedFiles; return preparedFiles;
@@ -105,7 +93,6 @@ export const config = {
export async function build({ files, entrypoint, workPath, config }: BuildOptions) { export async function build({ files, entrypoint, workPath, config }: BuildOptions) {
const { const {
entrypointPath, entrypointPath,
workNccPath,
entrypointFsDirname entrypointFsDirname
} = await downloadInstallAndBundle( } = await downloadInstallAndBundle(
{ files, entrypoint, workPath, npmArguments: ['--prefer-offline'] } { files, entrypoint, workPath, npmArguments: ['--prefer-offline'] }
@@ -115,15 +102,14 @@ export async function build({ files, entrypoint, workPath, config }: BuildOption
await runPackageJsonScript(entrypointFsDirname, 'now-build'); await runPackageJsonScript(entrypointFsDirname, 'now-build');
console.log('compiling entrypoint with ncc...'); console.log('compiling entrypoint with ncc...');
const preparedFiles = await compile(workNccPath, entrypointPath, entrypoint, config); const preparedFiles = await compile(entrypointPath, entrypoint, config);
const launcherPath = join(__dirname, 'launcher.js'); const launcherPath = join(__dirname, 'launcher.js');
let launcherData = await readFile(launcherPath, 'utf8'); let launcherData = await readFile(launcherPath, 'utf8');
launcherData = launcherData.replace( launcherData = launcherData.replace(
'// PLACEHOLDER', '// PLACEHOLDER',
[ [
'process.chdir("./user");', `listener = require("./${entrypoint}");`,
`listener = require("./${join('user', entrypoint)}");`,
'if (listener.default) listener = listener.default;' 'if (listener.default) listener = listener.default;'
].join(' ') ].join(' ')
); );
@@ -142,16 +128,10 @@ export async function build({ files, entrypoint, workPath, config }: BuildOption
return { [entrypoint]: lambda }; return { [entrypoint]: lambda };
} }
export async function prepareCache({ files, entrypoint, workPath, cachePath }: PrepareCacheOptions) { export async function prepareCache({ workPath }: PrepareCacheOptions) {
await remove(workPath);
await downloadInstallAndBundle({ files, entrypoint, workPath: cachePath });
return { return {
...(await glob('user/node_modules/**', cachePath)), ...(await glob('node_modules/**', workPath)),
...(await glob('user/package-lock.json', cachePath)), ...(await glob('package-lock.json', workPath)),
...(await glob('user/yarn.lock', cachePath)), ...(await glob('yarn.lock', workPath))
...(await glob('ncc/node_modules/**', cachePath)),
...(await glob('ncc/package-lock.json', cachePath)),
...(await glob('ncc/yarn.lock', cachePath))
}; };
} }

View File

@@ -4,7 +4,7 @@ import { Bridge } from './bridge';
let listener; let listener;
if (!process.env.NODE_ENV) { if (!process.env.NODE_ENV) {
process.env.NODE_ENV = 'production'; process.env.NODE_ENV = process.env.NOW_REGION === 'dev1' ? 'development' : 'production';
} }
try { try {
@@ -15,7 +15,8 @@ try {
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); process.exit(1);
} else { } else {
throw err; console.error(err);
process.exit(1);
} }
} }

View File

@@ -21,7 +21,7 @@ async function test3({ deploymentUrl, fetch, randomness }) {
const resp = await fetch(`https://${deploymentUrl}/test3.js`); const resp = await fetch(`https://${deploymentUrl}/test3.js`);
assert.equal(resp.status, 401); assert.equal(resp.status, 401);
assert.equal(await resp.text(), bodyMustBe); assert.equal(await resp.text(), bodyMustBe);
assert.equal(resp.headers.get('content-length'), null); assert.equal(resp.headers.get('content-length'), bodyMustBe.length);
} }
module.exports = async ({ deploymentUrl, fetch, randomness }) => { module.exports = async ({ deploymentUrl, fetch, randomness }) => {

View File

@@ -9,12 +9,25 @@
"templates/**" "templates/**"
] ]
} }
},
{
"src": "root.js",
"use": "@now/node",
"config": {
"includeFiles": [
"root.edge"
]
}
} }
], ],
"probes": [ "probes": [
{ {
"path": "/", "path": "/",
"mustContain": "hello Now!" "mustContain": "hello Now!"
},
{
"path": "/root.js",
"mustContain": "hello Root!"
} }
] ]
} }

View File

@@ -0,0 +1 @@
hello {{ text }}

View File

@@ -0,0 +1,7 @@
const edge = require('edge.js');
module.exports = (req, resp) => {
edge.registerViews(__dirname);
resp.end(edge.render('root', { text: 'Root!' }));
};

View File

@@ -0,0 +1,10 @@
module.exports = (req, res) => {
try {
if (req) {
throw new Error(`Should throw ${process.env.RANDOMNESS_ENV_VAR}`);
}
res.end(`Should not print ${process.env.RANDOMNESS_ENV_VAR}`);
} catch (error) {
res.end(error.stack);
}
};

View File

@@ -0,0 +1,12 @@
{
"version": 2,
"builds": [
{ "src": "index.js", "use": "@now/node" }
],
"probes": [
{
"path": "/",
"mustContain": "index.js:4"
}
]
}

View File

@@ -0,0 +1,10 @@
export default function handler (req: any, res: any) {
try {
if (req) {
throw new Error(`Should throw`);
}
res.end(`Should not print`);
} catch (error) {
res.end(error.stack);
}
}

View File

@@ -0,0 +1,12 @@
{
"version": 2,
"builds": [
{ "src": "index.ts", "use": "@now/node" }
],
"probes": [
{
"path": "/",
"mustContain": "index.ts:4"
}
]
}

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