Compare commits

...

130 Commits

Author SHA1 Message Date
Nathan Rajlich
0b89d30d6c Publish
- @now/build-utils@0.5.5
 - @now/cgi@0.1.4
 - @now/go@0.4.6
 - @now/mdx-deck@0.5.4
 - @now/next@0.3.2
 - @now/node-server@0.7.2
 - @now/node@0.7.2
 - @now/php@0.5.4
 - @now/python@0.2.2
 - @now/rust@0.2.4
2019-05-11 08:13:57 -07:00
Tim Neutkens
8a021c9417 [now-next] Add support for /api routes (#494) 2019-05-11 16:18:23 +02:00
Nathan Rajlich
f218771382 Publish
- @now/cgi@0.1.4-canary.0
 - @now/go@0.4.6-canary.0
 - @now/mdx-deck@0.5.4-canary.0
 - @now/node-server@0.7.2-canary.1
 - @now/node@0.7.2-canary.2
 - @now/php@0.5.4-canary.0
 - @now/python@0.2.2-canary.0
 - @now/rust@0.2.4-canary.1
2019-05-10 19:19:15 -07:00
Nathan Rajlich
17309291ed [now-node-server] Pass meta to download() function (#489) 2019-05-10 19:18:31 -07:00
Nathan Rajlich
86300577ae [now-rust] Pass meta to download() function 2019-05-10 17:14:22 -07:00
Nathan Rajlich
f9594e0d61 [now-python] Pass meta to download() function (#491) 2019-05-11 01:13:48 +02:00
Nathan Rajlich
20fd4b2e12 [now-php] Download files to workPath and pass meta (#490)
It's not clear to me why we were installing to `userfiles` directory,
so let me know if this breaks something.
2019-05-11 01:13:14 +02:00
Nathan Rajlich
718e4d0e0c [now-mdx-deck] Pass meta to download() function (#488) 2019-05-11 01:12:46 +02:00
Nathan Rajlich
dc3584cd08 [now-cgi] Download files to workPath and pass meta (#487) 2019-05-11 01:12:12 +02:00
Nathan Rajlich
b41788b241 Update yarn.lock 2019-05-10 13:17:05 -07:00
Nathan Rajlich
af9a2f9792 [now-node-server] Update @zeit/ncc to v0.18.5 2019-05-10 13:17:05 -07:00
Nathan Rajlich
f8b8e760de [now-node] Update @zeit/ncc to v0.18.5 2019-05-10 13:17:05 -07:00
Sophearak Tha
93d6ec8024 [now-go] Only use valid exported function with net/http interface (#477)
* Only use valid exported function with `net/http` interface

* Improve log, show link to docs if we coudn't parsed the entrypoint
2019-05-10 21:13:44 +07:00
Nathan Rajlich
7ed6b84056 Remove packages/now-rust/now-rust-0.2.3.tgz (#480)
It appears to have been accidentally committed in
bd2d05344e.
2019-05-08 14:50:57 -07:00
Nathan Rajlich
31da488365 Publish
- @now/build-utils@0.5.5-canary.1
 - @now/next@0.3.2-canary.1
 - @now/node@0.7.2-canary.1
2019-05-08 12:22:17 -07:00
Nathan Rajlich
8eaf05f782 Add initial CODEOWNERS file (#479)
* Add initial `CODEOWNERS` file

* Add `@now/go` to `CODEOWNERS` file
2019-05-08 12:07:32 -07:00
Nathan Rajlich
9311e90f27 [now-next] Fix failing unit test when isDev: true (#478) 2019-05-08 12:00:35 -07:00
Steven
c0de970de2 Add coverage to .gitignore (#473)
* Add coverage to `.gitignore`

* Add .tgz to `.gitignore`

* *.tgz
2019-05-07 17:57:09 -07:00
Nathan Rajlich
465ac2093d [now-node] Pass in the meta object to download() (#475)
Depends on https://github.com/zeit/now-builders/pull/474.
2019-05-07 17:56:39 -07:00
Nathan Rajlich
19ab0e8698 [now-build-utils] Make download() a no-op in now dev (#474)
This will be necessary for the update in `now dev` to have the builder
`workPath` be equal to the `cwd` source directory of the dev server.

Otherwise, unnecessary file modifications are made (copying the source
file to itself) and file corruption often occurs.
2019-05-07 17:56:16 -07:00
Steven
02fa98e5e3 Publish
- @now/build-utils@0.5.5-canary.0
 - @now/next@0.3.2-canary.0
 - @now/node-server@0.7.2-canary.0
 - @now/node@0.7.2-canary.0
 - @now/rust@0.2.4-canary.0
2019-05-07 17:08:55 -04:00
Steven
4aef9d48b0 [now-node] Bump ncc to 0.18.3 (#472) 2019-05-07 15:40:14 -04:00
Luis Fernando Alvarez D
bd2d05344e [now-next] Add public files to the output (#468) 2019-05-07 14:06:05 -05:00
Steven
edc7696623 Add newline to .gitignore 2019-05-07 09:28:59 -04:00
Steven
e2f91094bc Publish
- @now/bash@0.2.3
 - @now/build-utils@0.5.4
 - @now/cgi@0.1.3
 - @now/go@0.4.5
 - @now/html-minifier@1.1.3
 - @now/lambda@0.5.3
 - @now/md@0.5.3
 - @now/mdx-deck@0.5.3
 - @now/next@0.3.1
 - @now/node-bridge@1.1.2
 - @now/node-server@0.7.1
 - @now/node@0.7.1
 - @now/optipng@0.6.1
 - @now/php-bridge@0.5.2
 - @now/php@0.5.3
 - @now/python@0.2.1
 - @now/rust@0.2.3
 - @now/static-build@0.5.5
 - @now/wordpress@0.5.2
2019-05-07 07:09:50 -04:00
Steven
38dba57378 Bump stable version 2019-05-07 07:00:57 -04:00
Nathan Rajlich
be6a6ba1d7 Publish
- @now/build-utils@0.5.2-canary.2
 - @now/next@0.2.1-canary.1
2019-05-06 17:07:12 -07:00
Nathan Rajlich
31fb5d9ec8 [now-next] Sync runtime env vars in dev server after app.prepare() (#467)
* [now-next] Sync runtime env vars in dev server after `app.prepare()`

* Update packages/now-next/src/index.ts

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

* Update packages/now-next/src/index.ts

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

* Add `syncEnvVars()` helper function for common logic
2019-05-06 16:43:20 -07:00
Joe Haddad
6c8f946a48 [now-build-utils] Remove mutable option and add sha+ephemeral scheme (#466)
Co-Authored-By: Steven <steven@ceriously.com>

* Add an ephemeral option for files

* Use array destructuring

Co-Authored-By: Timer <joe.haddad@zeit.co>

* Remove mutable option

* Remove code all together

* Introduce `sha+ephemeral` handling

* http => https

* Elaborate more on the cloudfront url

* Update comment a bit more

* Add comment explaining other url
2019-05-06 19:12:39 -04:00
Steven
d59e1b9789 Publish
- @now/build-utils@0.5.2-canary.1
 - @now/go@0.4.3-canary.1
2019-05-06 08:47:35 -04:00
Steven
2852d3fbc3 [now-build-utils] Add yarn --ignore-engines during install (#463) 2019-05-05 22:57:37 -04:00
Sophearak Tha
d0292eb751 Improve go checking in user dev machine (#460) 2019-05-06 09:11:24 +07:00
Steven
17bbf69346 Publish
- @now/node-server@0.6.1-canary.2
 - @now/python@0.1.1-canary.2
2019-05-03 19:48:26 -04:00
Steven
4fb4229c90 Remove python/pip installer (#459)
* Remove python/pip installer

* Add back PYTHONUSERBASE env var
2019-05-03 19:46:47 -04:00
Steven
03b7586b50 [now-node-server] Fix unit tests 2019-05-03 14:09:45 -04:00
Mickaël Allonneau
a1427866ca [now-node-server] 'config.includeFiles' accepts a string (#442)
* 1st try (fails)

* 2nd try (fails)

* 3rd try (fails)

* 4th try (fail)
2019-05-03 13:54:33 -04:00
Nathan Rajlich
5f787b8146 Publish
- @now/bash@0.2.1-canary.0
 - @now/cgi@0.1.1-canary.0
 - @now/html-minifier@1.1.1-canary.0
 - @now/lambda@0.5.1-canary.0
 - @now/md@0.5.1-canary.0
 - @now/node-server@0.6.1-canary.1
 - @now/node@0.6.1-canary.2
 - @now/optipng@0.5.1-canary.0
 - @now/php@0.5.1-canary.0
 - @now/python@0.1.1-canary.1
 - @now/rust@0.2.1-canary.0
2019-05-02 15:21:06 -07:00
Nathan Rajlich
b03405a665 Add default shouldServe() to appropriate builders (#456)
This adds the default `shouldServe()` implementation to the following
builders:

 * `@now/bash`
 * `@now/cgi`
 * `@now/html-minifier`
 * `@now/lambda`
 * `@now/md`
 * `@now/node-server`
 * `@now/node`
 * `@now/optipng`
 * `@now/php`
 * `@now/python`
 * `@now/rust`

The default implementation may be used for these builders because
they map 1-1 with their entrypoint file -> output file/lambda.
2019-05-02 15:09:58 -07:00
Steven
4393dad15a Publish
- @now/node@0.6.1-canary.1
 - @now/static-build@0.5.3-canary.1
2019-05-02 16:07:38 -04:00
Marios Antonoudiou
b4d604b2e9 [now-static-build] Fix @now/static-build nested build (#452) 2019-05-02 15:15:40 -04:00
Mickaël Allonneau
5fb6e5c0ba [now-node] 'config.includeFiles' accepts a string (#441) 2019-05-02 14:26:35 -04:00
Steven
9d7dd3a713 Publish
- @now/python@0.1.1-canary.0
2019-05-02 11:32:59 -04:00
Steven
4f867b320d [now-python] Upgrade build env to python3.6 (#450)
* [now-python] Upgarde build env to python3.6

* Add support for isDev
2019-05-02 11:00:39 -04:00
Nathan Rajlich
c153690104 Publish
- @now/build-utils@0.5.2-canary.0
 - @now/go@0.4.3-canary.0
 - @now/next@0.2.1-canary.0
 - @now/static-build@0.5.3-canary.0
2019-05-01 23:34:18 -07:00
Nathan Rajlich
8c1b96edf7 [now-build-utils] Use cross-spawn for Windows support (#449) 2019-05-01 23:26:20 -07:00
Carlos Alexandro Becker
15c83a69f7 [now-go] Add support for config.includeFiles (#447) 2019-05-01 17:39:53 -04:00
Steven
0986de85ee [now-static-build] Warn when now-dev script is missing (#443)
* Warn when now-dev script is missing

* Add link to local dev docs

* Update packages/now-static-build/index.js

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

* Fix url
2019-05-01 17:32:50 -04:00
Leo Lamprecht
94c5d83ccc Copy the entire process environment to child (#448)
* Copy the entire process environment to child

* Removed lockfile

* Removed useless code

* Add comment
2019-05-01 22:26:35 +02:00
Steven
ff49b9d32d Publish
- @now/node-server@0.6.1-canary.0
 - @now/node@0.6.1-canary.0
2019-05-01 14:20:43 -04:00
Steven
ec5290dab1 [node-node] Bump ncc to 0.18.2 (#446) 2019-05-01 12:19:43 -04:00
Steven
4f758ec84e Create CODE_OF_CONDUCT.md (#444) 2019-05-01 09:04:34 -04:00
Steven
7951be156a Publish
- @now/build-utils@0.5.1
 - @now/go@0.4.2
 - @now/static-build@0.5.2
2019-04-30 10:33:52 -04:00
Nathan Rajlich
1bafc1d7b7 Publish
- @now/go@0.4.1
2019-04-29 18:03:38 -07:00
Sophearak Tha
1493101325 [now-go] Improve speed for now dev (#438)
* Improve speed for build() `@now/go` `now dev`

* Remove duplicate line

* Improve code flow

* Improve logging information

* no need to download Go if it available
2019-04-29 18:01:09 -07:00
Nathan Rajlich
824b044a96 Publish
- @now/static-build@0.5.1
2019-04-29 14:57:30 -07:00
Nathan Rajlich
0978be4c3d Publish
- @now/static-build@0.5.1-canary.0
2019-04-29 14:00:45 -07:00
Nathan Rajlich
dc832aa6c3 Regenerate yarn.lock file 2019-04-29 14:00:26 -07:00
Nathan Rajlich
8df77fe4fa [now-build-utils] Wait for the dev server to print the URL with correct port number (#439)
This is an alternative approach to detecting when the dev server is
ready to receive HTTP traffic, also bumps the timeout to 5 minutes.
2019-04-29 13:58:44 -07:00
Sophearak Tha
ff413b45fa Publish
- @now/go@0.4.1-canary.3
2019-04-29 16:46:58 +07:00
Sophearak Tha
e7befb5dc1 Make install @now/go faster (#435) 2019-04-29 16:46:08 +07:00
Steven
b898f82771 [now-go] Use optimized build flags (#428)
* Add optimized build output

* Add `config.ldsflags`
2019-04-29 09:41:50 +07:00
Leo Lamprecht
e6b22cb0df Publish
- @now/go@0.4.1-canary.2
2019-04-28 19:54:24 +00:00
Sophearak Tha
cbfe4a133d Using full path for checking against entrypoint (#433) 2019-04-29 00:44:31 +07:00
Sophearak Tha
823b78c626 Publish
- @now/go@0.4.1-canary.1
2019-04-28 21:22:30 +07:00
Sophearak Tha
03e1255043 Improve rebuild speed for now dev (#432) 2019-04-28 20:56:07 +07:00
Steven
3373cbca4e Publish
- @now/build-utils@0.5.1-canary.0
 - @now/go@0.4.1-canary.0
2019-04-25 17:11:31 -04:00
Sophearak Tha
4fba4b5f67 [now-go] Migrated to TypeScript and support Builder v2 API (#412)
* Migrated `@now/go` to TypeScript

* Add support for Builder v2 API

* Add now-go to .eslintignore

* Update go bin path

* Update packages/now-go/.gitignore

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

* Update packages/now-go/go-helpers.ts

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

* Update packages/now-go/go-helpers.ts

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

* Using American English for consistency

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

* Rename analyse.go to analyze.go

* Update packages/now-go/go-helpers.ts

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

* Remove `mkdirp-promise` from now-go

* Ensure `watch` directory-aware given `entrypoint` in subdirectory

* Support export struct type declaration

* Improve log

* Add type to `analyzed`

* Update packages/now-go/index.ts

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

* Migrate test fixtures from `add/now-go-tests`
2019-04-25 17:07:56 -04:00
Steven
9fcf6da3c1 Add comment to fsPath about absolute path (#427) 2019-04-25 11:27:36 -04:00
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
175 changed files with 3581 additions and 988 deletions

View File

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

View File

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

7
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,7 @@
# Documentation
# https://help.github.com/en/articles/about-code-owners
* @styfle
/packages/now-node @styfle @tootallnate
/packages/now-next @styfle @dav-is
/packages/now-go @styfle @sophearak

4
.gitignore vendored
View File

@@ -1,4 +1,6 @@
node_modules
tmp
target/
.next
.next
coverage
*.tgz

View File

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

74
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,74 @@
## Code of Conduct
### Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
### Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
### Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
### Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
### Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [abuse@zeit.co](mailto:abuse@zeit.co). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
### Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

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

View File

@@ -27,6 +27,10 @@
"prettier --write",
"eslint --fix",
"git add"
],
"*.ts": [
"prettier --write",
"git add"
]
},
"devDependencies": {

View File

@@ -1,10 +1,13 @@
const execa = require('execa');
const { join } = require('path');
const snakeCase = require('snake-case');
const glob = require('@now/build-utils/fs/glob'); // eslint-disable-line import/no-extraneous-dependencies
const download = require('@now/build-utils/fs/download'); // eslint-disable-line import/no-extraneous-dependencies
const { createLambda } = require('@now/build-utils/lambda'); // eslint-disable-line import/no-extraneous-dependencies
const getWritableDirectory = require('@now/build-utils/fs/get-writable-directory'); // eslint-disable-line import/no-extraneous-dependencies
const {
glob,
download,
createLambda,
getWriteableDirectory,
shouldServe,
} = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
exports.config = {
maxLambdaSize: '10mb',
@@ -15,7 +18,7 @@ exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
exports.build = async ({
workPath, files, entrypoint, config,
}) => {
const srcDir = await getWritableDirectory();
const srcDir = await getWriteableDirectory();
console.log('downloading files...');
await download(files, srcDir);
@@ -55,3 +58,5 @@ exports.build = async ({
[entrypoint]: lambda,
};
};
exports.shouldServe = shouldServe;

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@now/build-utils",
"version": "0.4.41-canary.0",
"version": "0.5.5",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -10,8 +10,10 @@
"directory": "packages/now-build-utils"
},
"dependencies": {
"@types/cross-spawn": "6.0.0",
"async-retry": "1.2.3",
"async-sema": "2.1.4",
"cross-spawn": "6.0.5",
"end-of-stream": "1.4.1",
"fs-extra": "7.0.0",
"glob": "7.1.3",
@@ -33,6 +35,7 @@
"@types/glob": "^7.1.1",
"@types/node-fetch": "^2.1.6",
"@types/yazl": "^2.4.1",
"execa": "^1.0.0",
"typescript": "3.3.4000"
}
}

View File

@@ -47,14 +47,15 @@ class FileFsRef implements File {
await fs.mkdirp(path.dirname(fsPath));
await new Promise<void>((resolve, reject) => {
const dest = fs.createWriteStream(fsPath);
const dest = fs.createWriteStream(fsPath, {
mode: mode & 0o777
});
stream.pipe(dest);
stream.on('error', reject);
dest.on('finish', resolve);
dest.on('error', reject);
});
await fs.chmod(fsPath, mode.toString(8).slice(-3));
return new FileFsRef({ mode, fsPath });
}

View File

@@ -37,10 +37,17 @@ export default class FileRef implements File {
async toStreamAsync(): Promise<NodeJS.ReadableStream> {
let url = '';
// sha:24be087eef9fac01d61b30a725c1a10d7b45a256
const digestParts = this.digest.split(':');
if (digestParts[0] === 'sha') {
// url = `https://s3.amazonaws.com/now-files/${digestParts[1]}`;
url = `https://dmmcy0pwk6bqi.cloudfront.net/${digestParts[1]}`;
const [digestType, digestHash] = this.digest.split(':');
if (digestType === 'sha') {
// This CloudFront URL edge caches the `now-files` S3 bucket to prevent
// overloading it
// `https://now-files.s3.amazonaws.com/${digestHash}`
url = `https://dmmcy0pwk6bqi.cloudfront.net/${digestHash}`;
} else if (digestType === 'sha+ephemeral') {
// This URL is currently only used for cache files that constantly
// change. We shouldn't cache it on CloudFront because it'd always be a
// MISS.
url = `https://now-ephemeral-files.s3.amazonaws.com/${digestHash}`;
} else {
throw new Error('Expected digest to be sha');
}
@@ -53,14 +60,14 @@ export default class FileRef implements File {
const resp = await fetch(url);
if (!resp.ok) {
const error = new BailableError(
`download: ${resp.status} ${resp.statusText} for ${url}`,
`download: ${resp.status} ${resp.statusText} for ${url}`
);
if (resp.status === 403) error.bail = true;
throw error;
}
return resp.body;
},
{ factor: 1, retries: 3 },
{ factor: 1, retries: 3 }
);
} finally {
// console.timeEnd(`downloading ${url}`);
@@ -72,15 +79,15 @@ export default class FileRef implements File {
let flag = false;
// eslint-disable-next-line consistent-return
return multiStream((cb) => {
return multiStream(cb => {
if (flag) return cb(null, null);
flag = true;
this.toStreamAsync()
.then((stream) => {
.then(stream => {
cb(null, stream);
})
.catch((error) => {
.catch(error => {
cb(error, null);
});
});

View File

@@ -1,14 +1,14 @@
import path from 'path';
import FileFsRef from '../file-fs-ref';
import { File, Files } from '../types';
import { mkdirp, readlink, symlink } from 'fs-extra';
import { File, Files, Meta } from '../types';
import { remove, mkdirp, readlink, symlink } from 'fs-extra';
export interface DownloadedFiles {
[filePath: string]: FileFsRef
[filePath: string]: FileFsRef;
}
const S_IFMT = 61440; /* 0170000 type of file */
const S_IFLNK = 40960; /* 0120000 symbolic link */
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;
@@ -17,9 +17,9 @@ export function isSymbolicLink(mode: number): boolean {
async function downloadFile(file: File, fsPath: string): Promise<FileFsRef> {
const { mode } = file;
if (mode && isSymbolicLink(mode) && file.type === 'FileFsRef') {
const [ target ] = await Promise.all([
const [target] = await Promise.all([
readlink((file as FileFsRef).fsPath),
mkdirp(path.dirname(fsPath))
mkdirp(path.dirname(fsPath)),
]);
await symlink(target, fsPath);
return FileFsRef.fromFsPath({ mode, fsPath });
@@ -29,15 +29,46 @@ async function downloadFile(file: File, fsPath: string): Promise<FileFsRef> {
}
}
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 { isDev = false, filesChanged = null, filesRemoved = null } =
meta || {};
if (isDev) {
// In `now dev`, the `download()` function is a no-op because
// the `basePath` matches the `cwd` of the dev server, so the
// source files are already available.
return files as DownloadedFiles;
}
const files2: DownloadedFiles = {};
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 fsPath = path.join(basePath, name);
files2[name] = await downloadFile(file, fsPath);
}),
})
);
return files2;

View File

@@ -8,12 +8,16 @@ import FileFsRef from '../file-fs-ref';
type GlobOptions = vanillaGlob_.IOptions;
interface FsFiles {
[filePath: string]: FileFsRef
[filePath: string]: FileFsRef;
}
const vanillaGlob = promisify(vanillaGlob_);
export default async function glob(pattern: string, opts: GlobOptions | string, mountpoint?: string): Promise<FsFiles> {
export default async function glob(
pattern: string,
opts: GlobOptions | string,
mountpoint?: string
): Promise<FsFiles> {
let options: GlobOptions;
if (typeof opts === 'string') {
options = { cwd: opts };
@@ -23,7 +27,7 @@ export default async function glob(pattern: string, opts: GlobOptions | string,
if (!options.cwd) {
throw new Error(
'Second argument (basePath) must be specified for names of resulting files',
'Second argument (basePath) must be specified for names of resulting files'
);
}
@@ -41,11 +45,11 @@ export default async function glob(pattern: string, opts: GlobOptions | string,
const files = await vanillaGlob(pattern, options);
for (const relativePath of files) {
const fsPath = path.join(options.cwd!, relativePath);
const fsPath = path.join(options.cwd!, relativePath).replace(/\\/g, '/');
let stat: Stats = options.statCache![fsPath] as Stats;
assert(
stat,
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`,
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`
);
if (stat.isFile()) {
const isSymlink = options.symlinks![fsPath];

View File

@@ -1,15 +1,37 @@
import assert from 'assert';
import fs from 'fs-extra';
import path from 'path';
import { spawn, SpawnOptions } from 'child_process';
import spawn from 'cross-spawn';
import { 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) => {
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('close', (code, signal) => (code !== 0
? reject(new Error(`Exited with ${code || signal}`))
: resolve()));
child.on('close', (code, signal) => {
if (code === 0) {
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}`));
}
});
});
}
@@ -42,13 +64,15 @@ async function scanParentDirs(destPath: string, scriptName?: string) {
// eslint-disable-next-line no-await-in-loop
if (await fs.pathExists(packageJsonPath)) {
// eslint-disable-next-line no-await-in-loop
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
const packageJson = JSON.parse(
await fs.readFile(packageJsonPath, 'utf8')
);
hasScript = Boolean(
packageJson.scripts && scriptName && packageJson.scripts[scriptName],
packageJson.scripts && scriptName && packageJson.scripts[scriptName]
);
// eslint-disable-next-line no-await-in-loop
hasPackageLockJson = await fs.pathExists(
path.join(currentDestPath, 'package-lock.json'),
path.join(currentDestPath, 'package-lock.json')
);
break;
}
@@ -61,7 +85,10 @@ async function scanParentDirs(destPath: string, scriptName?: string) {
return { hasScript, hasPackageLockJson };
}
export async function installDependencies(destPath: string, args: string[] = []) {
export async function installDependencies(
destPath: string,
args: string[] = []
) {
assert(path.isAbsolute(destPath));
let commandArgs = args;
@@ -75,35 +102,50 @@ export async function installDependencies(destPath: string, args: string[] = [])
// Node.js version that `@now/node` and `@now/node-server` use
npm_config_target: '8.10.0',
},
stdio: 'pipe',
};
if (hasPackageLockJson) {
commandArgs = args.filter(a => a !== '--prefer-offline');
await spawnAsync('npm', ['install'].concat(commandArgs), destPath, opts);
await spawnAsync(
'npm',
['install'].concat(commandArgs),
destPath,
opts as SpawnOptions
);
} else {
await spawnAsync(
'yarn',
['--cwd', destPath].concat(commandArgs),
['--ignore-engines', '--cwd', destPath].concat(commandArgs),
destPath,
opts,
opts as SpawnOptions
);
}
}
export async function runPackageJsonScript(destPath: string, scriptName: string) {
export async function runPackageJsonScript(
destPath: string,
scriptName: string,
opts?: SpawnOptions
) {
assert(path.isAbsolute(destPath));
const { hasScript, hasPackageLockJson } = await scanParentDirs(
destPath,
scriptName,
scriptName
);
if (!hasScript) return false;
if (hasPackageLockJson) {
console.log(`running "npm run ${scriptName}"`);
await spawnAsync('npm', ['run', scriptName], destPath);
await spawnAsync('npm', ['run', scriptName], destPath, opts);
} else {
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;

View File

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

View File

@@ -1,8 +1,11 @@
import assert from 'assert';
import Sema from 'async-sema';
import { ZipFile } from 'yazl';
import streamToBuffer from './fs/stream-to-buffer';
import { readlink } from 'fs-extra';
import { Files } from './types';
import FileFsRef from './file-fs-ref';
import { isSymbolicLink } from './fs/download';
import streamToBuffer from './fs/stream-to-buffer';
interface Environment {
[key: string]: string;
@@ -52,22 +55,9 @@ export async function createLambda({
assert(typeof environment === 'object', '"environment" is not an object');
await sema.acquire();
try {
const zipFile = new ZipFile();
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);
});
const zipBuffer = await createZip(files);
return new Lambda({
zipBuffer,
handler,
@@ -78,3 +68,37 @@ export async function createLambda({
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,38 @@
import FileRef from './file-ref';
import FileFsRef from './file-fs-ref';
export interface File {
type: string;
mode: number;
toStream: () => NodeJS.ReadableStream;
/**
* The absolute path to the file in the filesystem
*/
fsPath?: string;
}
export interface Files {
[filePath: string]: File
[filePath: string]: File;
}
export interface Config {
[key: string]: string
[key: string]: string;
}
export interface Meta {
isDev?: boolean;
requestPath?: string;
filesChanged?: string[];
filesRemoved?: string[];
}
export interface AnalyzeOptions {
/**
* All source files of the project
*/
files: Files;
files: {
[filePath: string]: FileRef;
};
/**
* Name of entrypoint file for this particular build job. Value
@@ -39,7 +55,6 @@ export interface AnalyzeOptions {
config: Config;
}
export interface BuildOptions {
/**
* All source files of the project
@@ -65,6 +80,13 @@ export interface BuildOptions {
* in `now.json`.
*/
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 {
@@ -99,3 +121,37 @@ export interface PrepareCacheOptions {
*/
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,9 @@
const scheduler = require('@google-cloud/scheduler');
module.exports = (_, res) => {
if (scheduler) {
res.end('found:RANDOMNESS_PLACEHOLDER');
} else {
res.end('nope:RANDOMNESS_PLACEHOLDER');
}
};

View File

@@ -0,0 +1,10 @@
{
"version": 2,
"builds": [
{ "src": "index.js", "use": "@now/node", "config": { "maxLambdaSize": "15mb" } }
],
"probes": [
{ "path": "/", "mustContain": "found:RANDOMNESS_PLACEHOLDER" }
]
}

View File

@@ -0,0 +1,8 @@
{
"name": "15-yarn-ignore-engines",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"@google-cloud/scheduler": "0.3.0"
}
}

View File

@@ -1,8 +1,11 @@
/* global beforeAll, expect, it, jest */
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 {
packAndDeploy,
@@ -39,6 +42,28 @@ it('should re-create symlinks properly', async () => {
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
const fixturesPath = path.resolve(__dirname, 'fixtures');

View File

@@ -5,15 +5,17 @@ const glob = require('@now/build-utils/fs/glob'); // eslint-disable-line import/
const download = require('@now/build-utils/fs/download'); // eslint-disable-line import/no-extraneous-dependencies
const { createLambda } = require('@now/build-utils/lambda'); // eslint-disable-line import/no-extraneous-dependencies
const getWritableDirectory = require('@now/build-utils/fs/get-writable-directory'); // eslint-disable-line import/no-extraneous-dependencies
const { shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
exports.build = async ({ files, entrypoint }) => {
exports.build = async ({
workPath, files, entrypoint, meta,
}) => {
console.log('downloading files...');
const srcDir = await getWritableDirectory();
const outDir = await getWritableDirectory();
await download(files, srcDir);
await download(files, workPath, meta);
const handlerPath = path.join(__dirname, 'handler');
await copyFile(handlerPath, path.join(outDir, 'handler'));
@@ -23,7 +25,7 @@ exports.build = async ({ files, entrypoint }) => {
// For now only the entrypoint file is copied into the lambda
await copyFile(
path.join(srcDir, entrypoint),
path.join(workPath, entrypoint),
path.join(outDir, entrypoint),
);
@@ -40,3 +42,5 @@ exports.build = async ({ files, entrypoint }) => {
[entrypoint]: lambda,
};
};
exports.shouldServe = shouldServe;

View File

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

View File

@@ -1,5 +1,6 @@
node_modules
*.log
/?.js
/go
/get-exported-function-name
/analyze
*.js
!util/install.js

View File

@@ -0,0 +1,5 @@
*.ts
test
tsconfig.json
package-lock.json
yarn.lock

View File

@@ -1,129 +0,0 @@
const tar = require('tar');
const execa = require('execa');
const fetch = require('node-fetch');
const { mkdirp } = require('fs-extra');
const { dirname, join } = require('path');
const debug = require('debug')('@now/go:go-helpers');
const archMap = new Map([['x64', 'amd64'], ['x86', '386']]);
const platformMap = new Map([['win32', 'windows']]);
// Location where the `go` binary will be installed after `postinstall`
const GO_DIR = join(__dirname, 'go');
const GO_BIN = join(GO_DIR, 'bin/go');
const getPlatform = p => platformMap.get(p) || p;
const getArch = a => archMap.get(a) || a;
const getGoUrl = (version, platform, arch) => {
const goArch = getArch(arch);
const goPlatform = getPlatform(platform);
const ext = platform === 'win32' ? 'zip' : 'tar.gz';
return `https://dl.google.com/go/go${version}.${goPlatform}-${goArch}.${ext}`;
};
async function getExportedFunctionName(filePath) {
debug('Detecting handler name for %o', filePath);
const bin = join(__dirname, 'get-exported-function-name');
const args = [filePath];
const name = await execa.stdout(bin, args);
debug('Detected exported name %o', name);
return name;
}
// Creates a `$GOPATH` directory tree, as per `go help gopath` instructions.
// Without this, `go` won't recognize the `$GOPATH`.
function createGoPathTree(goPath, platform, arch) {
const tuple = `${getPlatform(platform)}_${getArch(arch)}`;
debug('Creating GOPATH directory structure for %o (%s)', goPath, tuple);
return Promise.all([
mkdirp(join(goPath, 'bin')),
mkdirp(join(goPath, 'pkg', tuple)),
]);
}
async function get({ src } = {}) {
const args = ['get'];
if (src) {
debug('Fetching `go` dependencies for file %o', src);
args.push(src);
} else {
debug('Fetching `go` dependencies for cwd %o', this.cwd);
}
await this(...args);
}
async function build({ src, dest }) {
debug('Building `go` binary %o -> %o', src, dest);
let sources;
if (Array.isArray(src)) {
sources = src;
} else {
sources = [src];
}
await this('build', '-o', dest, ...sources);
}
async function createGo(
goPath,
platform = process.platform,
arch = process.arch,
opts = {},
goMod = false,
) {
const env = {
...process.env,
PATH: `${dirname(GO_BIN)}:${process.env.PATH}`,
GOPATH: goPath,
...opts.env,
};
if (goMod) {
env.GO111MODULE = 'on';
}
function go(...args) {
debug('Exec %o', `go ${args.join(' ')}`);
return execa('go', args, { stdio: 'inherit', ...opts, env });
}
go.cwd = opts.cwd || process.cwd();
go.get = get;
go.build = build;
go.goPath = goPath;
await createGoPathTree(goPath, platform, arch);
return go;
}
async function downloadGo(
dir = GO_DIR,
version = '1.12',
platform = process.platform,
arch = process.arch,
) {
debug('Installing `go` v%s to %o for %s %s', version, dir, platform, arch);
const url = getGoUrl(version, platform, arch);
debug('Downloading `go` URL: %o', url);
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Failed to download: ${url} (${res.status})`);
}
// TODO: use a zip extractor when `ext === "zip"`
await mkdirp(dir);
await new Promise((resolve, reject) => {
res.body
.on('error', reject)
.pipe(tar.extract({ cwd: dir, strip: 1 }))
.on('error', reject)
.on('finish', resolve);
});
return createGo(dir, platform, arch);
}
module.exports = {
createGo,
downloadGo,
getExportedFunctionName,
};

View File

@@ -0,0 +1,161 @@
import tar from 'tar';
import execa from 'execa';
import fetch from 'node-fetch';
import { mkdirp, pathExists } from 'fs-extra';
import { dirname, join } from 'path';
import { homedir } from 'os';
import Debug from 'debug';
const debug = Debug('@now/go:go-helpers');
const archMap = new Map([['x64', 'amd64'], ['x86', '386']]);
const platformMap = new Map([['win32', 'windows']]);
// Location where the `go` binary will be installed after `postinstall`
const GO_DIR = join(__dirname, 'go');
const GO_BIN = join(GO_DIR, 'bin/go');
const getPlatform = (p: string) => platformMap.get(p) || p;
const getArch = (a: string) => archMap.get(a) || a;
const getGoUrl = (version: string, platform: string, arch: string) => {
const goArch = getArch(arch);
const goPlatform = getPlatform(platform);
const ext = platform === 'win32' ? 'zip' : 'tar.gz';
return `https://dl.google.com/go/go${version}.${goPlatform}-${goArch}.${ext}`;
};
export async function getAnalyzedEntrypoint(filePath: string) {
debug('Analyzing entrypoint %o', filePath);
const bin = join(__dirname, 'analyze');
const isAnalyzeExist = await pathExists(bin);
if (!isAnalyzeExist) {
const src = join(__dirname, 'util', 'analyze.go');
const dest = join(__dirname, 'analyze');
const go = await downloadGo();
await go.build(src, dest);
}
const args = [filePath];
const analyzed = await execa.stdout(bin, args);
debug('Analyzed entrypoint %o', analyzed);
return analyzed;
}
// Creates a `$GOPATH` directory tree, as per `go help gopath` instructions.
// Without this, `go` won't recognize the `$GOPATH`.
function createGoPathTree(goPath: string, platform: string, arch: string) {
const tuple = `${getPlatform(platform)}_${getArch(arch)}`;
debug('Creating GOPATH directory structure for %o (%s)', goPath, tuple);
return Promise.all([
mkdirp(join(goPath, 'bin')),
mkdirp(join(goPath, 'pkg', tuple)),
]);
}
class GoWrapper {
private env: { [key: string]: string };
private opts: execa.Options;
constructor(env: { [key: string]: string }, opts: execa.Options = {}) {
if (!opts.cwd) {
opts.cwd = process.cwd();
}
this.env = env;
this.opts = opts;
}
private execute(...args: string[]) {
const { opts, env } = this;
debug('Exec %o', `go ${args.join(' ')}`);
return execa('go', args, { stdio: 'inherit', ...opts, env });
}
mod() {
return this.execute('mod', 'tidy');
}
get(src?: string) {
const args = ['get'];
if (src) {
debug('Fetching `go` dependencies for file %o', src);
args.push(src);
} else {
debug('Fetching `go` dependencies for cwd %o', this.opts.cwd);
}
return this.execute(...args);
}
build(src: string | string[], dest: string, ldsflags = '-s -w') {
debug('Building optimized `go` binary %o -> %o', src, dest);
const sources = Array.isArray(src) ? src : [src];
return this.execute('build', '-ldflags', ldsflags, '-o', dest, ...sources);
}
}
export async function createGo(
goPath: string,
platform = process.platform,
arch = process.arch,
opts: execa.Options = {},
goMod = false
) {
const path = `${dirname(GO_BIN)}:${process.env.PATH}`;
const env: { [key: string]: string } = {
...process.env,
PATH: path,
GOPATH: goPath,
...opts.env,
};
if (goMod) {
env.GO111MODULE = 'on';
}
await createGoPathTree(goPath, platform, arch);
return new GoWrapper(env, opts);
}
export async function downloadGo(
dir = GO_DIR,
version = '1.12',
platform = process.platform,
arch = process.arch
) {
// Check default `Go` in user machine
const isUserGo = await pathExists(join(homedir(), 'go'));
// If we found GOPATH in ENV, or default `Go` path exists
// asssume that user have `Go` installed
if (isUserGo || process.env.GOPATH !== undefined) {
return createGo(dir, platform, arch);
} else {
// Check `Go` bin in builder CWD
const isGoExist = await pathExists(join(dir, 'bin'));
if (!isGoExist) {
debug(
'Installing `go` v%s to %o for %s %s',
version,
dir,
platform,
arch
);
const url = getGoUrl(version, platform, arch);
debug('Downloading `go` URL: %o', url);
console.log('Downloading Go ...');
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Failed to download: ${url} (${res.status})`);
}
// TODO: use a zip extractor when `ext === "zip"`
await mkdirp(dir);
await new Promise((resolve, reject) => {
res.body
.on('error', reject)
.pipe(tar.extract({ cwd: dir, strip: 1 }))
.on('error', reject)
.on('finish', resolve);
});
}
return createGo(dir, platform, arch);
}
}

View File

@@ -1,52 +1,100 @@
const { join, sep, dirname } = require('path');
const {
readFile, writeFile, pathExists, move,
} = require('fs-extra');
import { join, sep, dirname } from 'path';
import { readFile, writeFile, pathExists, move } from 'fs-extra';
const glob = require('@now/build-utils/fs/glob.js'); // eslint-disable-line import/no-extraneous-dependencies
const download = require('@now/build-utils/fs/download.js'); // eslint-disable-line import/no-extraneous-dependencies
const { createLambda } = require('@now/build-utils/lambda.js'); // eslint-disable-line import/no-extraneous-dependencies
const getWritableDirectory = require('@now/build-utils/fs/get-writable-directory.js'); // eslint-disable-line import/no-extraneous-dependencies
const { createGo, getExportedFunctionName } = require('./go-helpers');
import {
glob,
download,
createLambda,
getWriteableDirectory,
BuildOptions,
shouldServe,
Files,
} from '@now/build-utils';
const config = {
import { createGo, getAnalyzedEntrypoint } from './go-helpers';
interface Analyzed {
found?: boolean;
packageName: string;
functionName: string;
watch: string[];
}
interface BuildParamsMeta {
isDev: boolean | undefined;
}
interface BuildParamsType extends BuildOptions {
files: Files;
entrypoint: string;
workPath: string;
meta: BuildParamsMeta;
}
export const version = 2;
export const config = {
maxLambdaSize: '10mb',
};
async function build({ files, entrypoint }) {
export async function build({
files,
entrypoint,
config,
meta = {} as BuildParamsMeta,
}: BuildParamsType) {
console.log('Downloading user files...');
const entrypointArr = entrypoint.split(sep);
const [goPath, outDir] = await Promise.all([
getWritableDirectory(),
getWritableDirectory(),
let [goPath, outDir] = await Promise.all([
getWriteableDirectory(),
getWriteableDirectory(),
]);
if (meta.isDev) {
const devGoPath = `dev${entrypointArr[entrypointArr.length - 1]}`;
const goPathArr = goPath.split(sep);
goPathArr.pop();
goPathArr.push(devGoPath);
goPath = goPathArr.join(sep);
}
const srcPath = join(goPath, 'src', 'lambda');
const downloadedFiles = await download(files, srcPath);
const input = dirname(downloadedFiles[entrypoint].fsPath);
var includedFiles: Files = {};
if (config && config.includeFiles) {
for (const pattern of config.includeFiles) {
const files = await glob(pattern, input);
for (const assetName of Object.keys(files)) {
includedFiles[assetName] = files[assetName];
}
}
}
console.log(`Parsing AST for "${entrypoint}"`);
let parseFunctionName;
let analyzed: string;
try {
parseFunctionName = await getExportedFunctionName(
downloadedFiles[entrypoint].fsPath,
);
analyzed = await getAnalyzedEntrypoint(downloadedFiles[entrypoint].fsPath);
} catch (err) {
console.log(`Failed to parse AST for "${entrypoint}"`);
throw err;
}
if (!parseFunctionName) {
if (!analyzed) {
const err = new Error(
`Could not find an exported function in "${entrypoint}"`,
`Could not find an exported function in "${entrypoint}"
Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#entrypoint
`
);
console.log(err.message);
throw err;
}
const handlerFunctionName = parseFunctionName.split(',')[0];
const parsedAnalyzed = JSON.parse(analyzed) as Analyzed;
const handlerFunctionName = parsedAnalyzed.functionName;
console.log(
`Found exported function "${handlerFunctionName}" in "${entrypoint}"`,
`Found exported function "${handlerFunctionName}" in "${entrypoint}"`
);
// we need `main.go` in the same dir as the entrypoint,
@@ -54,7 +102,7 @@ async function build({ files, entrypoint }) {
const entrypointDirname = dirname(downloadedFiles[entrypoint].fsPath);
// check if package name other than main
const packageName = parseFunctionName.split(',')[1];
const packageName = parsedAnalyzed.packageName;
const isGoModExist = await pathExists(join(entrypointDirname, 'go.mod'));
if (packageName !== 'main') {
const go = await createGo(
@@ -64,7 +112,7 @@ async function build({ files, entrypoint }) {
{
cwd: entrypointDirname,
},
true,
true
);
if (!isGoModExist) {
try {
@@ -80,7 +128,7 @@ async function build({ files, entrypoint }) {
const mainModGoFileName = 'main__mod__.go';
const modMainGoContents = await readFile(
join(__dirname, mainModGoFileName),
'utf8',
'utf8'
);
let goPackageName = `${packageName}/${packageName}`;
@@ -89,11 +137,10 @@ async function build({ files, entrypoint }) {
if (isGoModExist) {
const goModContents = await readFile(
join(entrypointDirname, 'go.mod'),
'utf8',
'utf8'
);
goPackageName = `${
goModContents.split('\n')[0].split(' ')[1]
}/${packageName}`;
const usrModName = goModContents.split('\n')[0].split(' ')[1];
goPackageName = `${usrModName}/${packageName}`;
}
const mainModGoContents = modMainGoContents
@@ -103,34 +150,56 @@ async function build({ files, entrypoint }) {
// write main__mod__.go
await writeFile(
join(entrypointDirname, mainModGoFileName),
mainModGoContents,
mainModGoContents
);
// move user go file to folder
try {
// default path
let finalDestination = join(entrypointDirname, packageName, entrypoint);
const entrypointArr = entrypoint.split(sep);
let forceMove = false;
if (meta.isDev) {
forceMove = true;
}
// if `entrypoint` include folder, only use filename
if (entrypointArr.length > 1) {
finalDestination = join(
entrypointDirname,
packageName,
entrypointArr.pop(),
entrypointArr[entrypointArr.length - 1]
);
}
await move(downloadedFiles[entrypoint].fsPath, finalDestination);
await move(downloadedFiles[entrypoint].fsPath, finalDestination, {
overwrite: forceMove,
});
} catch (err) {
console.log('failed to move entry to package folder');
throw err;
}
console.log('tidy go.mod file');
if (meta.isDev) {
const isGoModBk = await pathExists(join(entrypointDirname, 'go.mod.bk'));
if (isGoModBk) {
await move(
join(entrypointDirname, 'go.mod.bk'),
join(entrypointDirname, 'go.mod'),
{ overwrite: true }
);
await move(
join(entrypointDirname, 'go.sum.bk'),
join(entrypointDirname, 'go.sum'),
{ overwrite: true }
);
}
}
console.log('Tidy `go.mod` file...');
try {
// ensure go.mod up-to-date
await go('mod', 'tidy');
await go.mod();
} catch (err) {
console.log('failed to `go mod tidy`');
throw err;
@@ -140,11 +209,24 @@ async function build({ files, entrypoint }) {
const destPath = join(outDir, 'handler');
try {
const src = [join(entrypointDirname, mainModGoFileName)];
await go.build({ src, dest: destPath });
await go.build(src, destPath, config.ldsflags);
} catch (err) {
console.log('failed to `go build`');
throw err;
}
if (meta.isDev) {
// caching for `now dev`
await move(
join(entrypointDirname, 'go.mod'),
join(entrypointDirname, 'go.mod.bk'),
{ overwrite: true }
);
await move(
join(entrypointDirname, 'go.sum'),
join(entrypointDirname, 'go.sum.bk'),
{ overwrite: true }
);
}
} else {
const go = await createGo(
goPath,
@@ -153,15 +235,15 @@ async function build({ files, entrypoint }) {
{
cwd: entrypointDirname,
},
false,
false
);
const origianlMainGoContents = await readFile(
join(__dirname, 'main.go'),
'utf8',
'utf8'
);
const mainGoContents = origianlMainGoContents.replace(
'__NOW_HANDLER_FUNC_NAME',
handlerFunctionName,
handlerFunctionName
);
// in order to allow the user to have `main.go`,
@@ -174,6 +256,7 @@ async function build({ files, entrypoint }) {
// `go get` will look at `*.go` (note we set `cwd`), parse the `import`s
// and download any packages that aren't part of the stdlib
console.log('Running `go get`...');
try {
await go.get();
} catch (err) {
@@ -188,7 +271,7 @@ async function build({ files, entrypoint }) {
join(entrypointDirname, mainGoFileName),
downloadedFiles[entrypoint].fsPath,
];
await go.build({ src, dest: destPath });
await go.build(src, destPath);
} catch (err) {
console.log('failed to `go build`');
throw err;
@@ -196,15 +279,27 @@ async function build({ files, entrypoint }) {
}
const lambda = await createLambda({
files: await glob('**', outDir),
files: { ...(await glob('**', outDir)), ...includedFiles },
handler: 'handler',
runtime: 'go1.x',
environment: {},
});
const output = {
[entrypoint]: lambda,
};
let watch = parsedAnalyzed.watch;
// if `entrypoint` located in subdirectory
// we will need to concat it with return watch array
if (entrypointArr.length > 1) {
entrypointArr.pop();
watch = parsedAnalyzed.watch.map(file => join(...entrypointArr, file));
}
return {
[entrypoint]: lambda,
output,
watch,
};
}
module.exports = { config, build };
export { shouldServe };

View File

@@ -1,6 +1,6 @@
{
"name": "@now/go",
"version": "0.3.1-canary.3",
"version": "0.4.6",
"license": "MIT",
"repository": {
"type": "git",
@@ -8,7 +8,9 @@
"directory": "packages/now-go"
},
"scripts": {
"postinstall": "node ./util/install"
"build": "tsc",
"test": "tsc && jest",
"prepublish": "tsc"
},
"files": [
"*.js",
@@ -20,8 +22,15 @@
"debug": "^4.1.1",
"execa": "^1.0.0",
"fs-extra": "^7.0.0",
"mkdirp-promise": "5.0.1",
"node-fetch": "^2.2.1",
"tar": "4.4.6"
},
"devDependencies": {
"@types/debug": "^4.1.3",
"@types/execa": "^0.9.0",
"@types/fs-extra": "^5.0.5",
"@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0",
"typescript": "^3.4.2"
}
}

View File

@@ -0,0 +1 @@
module build-env

View File

@@ -0,0 +1,17 @@
package buildenv
import (
"fmt"
"net/http"
"os"
)
// Handler function
func Handler(w http.ResponseWriter, r *http.Request) {
rdm := os.Getenv("RANDOMNESS_BUILD_ENV")
if rdm == "" {
fmt.Println("No build env received")
}
fmt.Fprintf(w, rdm+":build-env")
}

View File

@@ -0,0 +1 @@
module env

View File

@@ -0,0 +1,17 @@
package env
import (
"fmt"
"net/http"
"os"
)
// Handler function
func Handler(w http.ResponseWriter, r *http.Request) {
rdm := os.Getenv("RANDOMNESS_ENV")
if rdm == "" {
fmt.Println("No env received")
}
fmt.Fprintf(w, rdm)
}

View File

@@ -0,0 +1,18 @@
{
"version": 2,
"builds": [
{
"src": "env/index.go",
"use": "@now/go"
}
],
"env": {
"RANDOMNESS_ENV": "RANDOMNESS_PLACEHOLDER"
},
"probes": [
{
"path": "/env",
"mustContain": "RANDOMNESS_PLACEHOLDER"
}
]
}

View File

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

View File

@@ -0,0 +1,6 @@
{
"version": 2,
"builds": [
{ "src": "index.go", "use": "@now/go" }
]
}

View File

@@ -0,0 +1,9 @@
{
"version": 2,
"builds": [
{ "src": "*.go", "use": "@now/go" }
],
"env": {
"RANDOMNESS_ENV_VAR": "RANDOMNESS_PLACEHOLDER"
}
}

View File

@@ -0,0 +1,16 @@
package function
import (
"net/http"
"os"
"strconv"
)
// HandlerTest1 function
func HandlerTest1(w http.ResponseWriter, r *http.Request) {
rdm := os.Getenv("RANDOMNESS_ENV_VAR")
w.WriteHeader(401)
w.Header().Set("content-length", strconv.Itoa(len(rdm+":content-length")))
w.Write([]byte(rdm + ":content-length"))
}

View File

@@ -0,0 +1,12 @@
package function
import (
"net/http"
)
// HandlerTest2 function
func HandlerTest2(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "2")
w.WriteHeader(401)
w.Write([]byte(""))
}

View File

@@ -0,0 +1,13 @@
package function
import (
"net/http"
"os"
)
// HandlerTest3 function
func HandlerTest3(w http.ResponseWriter, r *http.Request) {
rev := os.Getenv("RANDOMNESS_ENV_VAR")
w.WriteHeader(401)
w.Write([]byte(rev + ":content-length"))
}

View File

@@ -0,0 +1,16 @@
package cowsay
import (
"fmt"
"io/ioutil"
"net/http"
)
// Handler function
func Handler(w http.ResponseWriter, r *http.Request) {
bts, err := ioutil.ReadFile("templates/foo.txt")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
fmt.Fprintf(w, string(bts))
}

View File

@@ -0,0 +1,20 @@
{
"version": 2,
"builds": [
{
"src": "index.go",
"use": "@now/go",
"config": {
"includeFiles": [
"templates/**"
]
}
}
],
"probes": [
{
"path": "/",
"mustContain": "foobar from file"
}
]
}

View File

@@ -0,0 +1 @@
foobar from file

View File

@@ -0,0 +1,22 @@
package function
import (
"fmt"
"net/http"
)
// Person struct
type Person struct {
name string
age int
}
// NewPerson struct method
func NewPerson(name string, age int) *Person {
return &Person{name: name, age: age}
}
// H func
func H(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "RANDOMNESS_PLACEHOLDER")
}

View File

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

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"declaration": false,
"esModuleInterop": true,
"lib": ["esnext"],
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitThis": false,
"types": ["node"],
"strict": true,
"target": "esnext"
}
}

View File

@@ -0,0 +1,169 @@
package main
import (
"encoding/json"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
)
type analyze struct {
PackageName string `json:"packageName"`
FuncName string `json:"functionName"`
Watch []string `json:"watch"`
}
// parse go file
func parse(fileName string) *ast.File {
fset := token.NewFileSet()
parsed, err := parser.ParseFile(fset, fileName, nil, parser.ParseComments)
if err != nil {
log.Fatalf("Could not parse Go file \"%s\"\n", fileName)
os.Exit(1)
}
return parsed
}
// ensure we only working with interest go file(s)
func visit(files *[]string) filepath.WalkFunc {
return func(path string, info os.FileInfo, err error) error {
itf, err := filepath.Match("*test.go", path)
if err != nil {
log.Fatal(err)
}
// we don't need Dirs, or test files
// we only want `.go` files
if info.IsDir() || itf || filepath.Ext(path) != ".go" {
return nil
}
*files = append(*files, path)
return nil
}
}
// return unique file
func unique(files []string) []string {
encountered := map[string]bool{}
for v := range files {
encountered[files[v]] = true
}
result := []string{}
for key := range encountered {
result = append(result, key)
}
return result
}
func main() {
if len(os.Args) != 2 {
// Args should have the program name on `0`
// and the file name on `1`
fmt.Println("Wrong number of args; Usage is:\n ./go-analyze file_name.go")
os.Exit(1)
}
fileName := os.Args[1]
rf, err := ioutil.ReadFile(fileName)
if err != nil {
log.Fatal(err)
}
se := string(rf)
var files []string
var relatedFiles []string
// Add entrypoint to watchlist
relFileName, err := filepath.Rel(filepath.Dir(fileName), fileName)
if err != nil {
log.Fatal(err)
}
relatedFiles = append(relatedFiles, relFileName)
// looking for all go files that have export func
// using in entrypoint
err = filepath.Walk(filepath.Dir(fileName), visit(&files))
if err != nil {
log.Fatal(err)
}
for _, file := range files {
absFileName, _ := filepath.Abs(fileName)
absFile, _ := filepath.Abs(file)
// if it isn't entrypoint
if absFileName != absFile {
// find all export structs and functions
pf := parse(file)
var exportedDecl []string
ast.Inspect(pf, func(n ast.Node) bool {
switch t := n.(type) {
case *ast.FuncDecl:
if t.Name.IsExported() {
exportedDecl = append(exportedDecl, t.Name.Name)
}
// find variable declarations
case *ast.TypeSpec:
// which are public
if t.Name.IsExported() {
switch t.Type.(type) {
// and are interfaces
case *ast.StructType:
exportedDecl = append(exportedDecl, t.Name.Name)
}
}
}
return true
})
for _, ed := range exportedDecl {
if strings.Contains(se, ed) {
// find relative path of related file
rel, err := filepath.Rel(filepath.Dir(fileName), file)
if err != nil {
log.Fatal(err)
}
relatedFiles = append(relatedFiles, rel)
}
}
}
}
parsed := parse(fileName)
offset := parsed.Pos()
reqRep := "*http.Request http.ResponseWriter"
for _, decl := range parsed.Decls {
fn, ok := decl.(*ast.FuncDecl)
if !ok {
// this declaration is not a function
// so we're not interested
continue
}
if fn.Name.IsExported() == true {
// find a valid `net/http` handler function
for _, param := range fn.Type.Params.List {
if strings.Contains(reqRep, string(rf[param.Type.Pos()-offset:param.Type.End()-offset])) {
// we found the first exported function with `net/http`
// we're done!
analyzed := analyze{
PackageName: parsed.Name.Name,
FuncName: fn.Name.Name,
Watch: unique(relatedFiles),
}
analyzedJSON, _ := json.Marshal(analyzed)
fmt.Print(string(analyzedJSON))
os.Exit(0)
}
}
}
}
}

View File

@@ -1,41 +0,0 @@
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
)
func main() {
if len(os.Args) != 2 {
// Args should have the program name on `0`
// and the file name on `1`
fmt.Println("Wrong number of args; Usage is:\n ./get-exported-function-name file_name.go")
os.Exit(1)
}
fileName := os.Args[1]
fset := token.NewFileSet()
parsed, err := parser.ParseFile(fset, fileName, nil, parser.ParseComments)
if err != nil {
fmt.Printf("Could not parse Go file \"%s\"\n", fileName)
os.Exit(1)
}
for _, decl := range parsed.Decls {
fn, ok := decl.(*ast.FuncDecl)
if !ok {
// this declaraction is not a function
// so we're not interested
continue
}
if fn.Name.IsExported() == true {
// we found the first exported function
// we're done!
fmt.Print(fn.Name.Name, ",", parsed.Name.Name)
os.Exit(0)
}
}
}

View File

@@ -1,18 +0,0 @@
const { join } = require('path');
const { downloadGo } = require('../go-helpers');
async function main() {
// First download the `go` binary for this platform/arch.
const go = await downloadGo();
// Build the `get-exported-function-name` helper program.
// `go get` is not necessary because the program has no external deps.
const src = join(__dirname, 'get-exported-function-name.go');
const dest = join(__dirname, '../get-exported-function-name');
await go.build({ src, dest });
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -1,4 +1,4 @@
const FileBlob = require('@now/build-utils/file-blob.js'); // eslint-disable-line import/no-extraneous-dependencies
const { FileBlob, shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
const { minify } = require('html-minifier');
const defaultOptions = {
@@ -28,3 +28,5 @@ exports.build = async ({ files, entrypoint, config }) => {
return { [entrypoint]: result };
};
exports.shouldServe = shouldServe;

View File

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

View File

@@ -1,5 +1,6 @@
const { Lambda } = require('@now/build-utils/lambda.js'); // eslint-disable-line import/no-extraneous-dependencies
const streamToBuffer = require('@now/build-utils/fs/stream-to-buffer.js'); // eslint-disable-line import/no-extraneous-dependencies
const { shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
exports.build = async ({ files, entrypoint, config }) => {
if (!files[entrypoint]) throw new Error('Entrypoint not found in files');
@@ -10,3 +11,5 @@ exports.build = async ({ files, entrypoint, config }) => {
const lambda = new Lambda({ zipBuffer, handler, runtime });
return { [entrypoint]: lambda };
};
exports.shouldServe = shouldServe;

View File

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

View File

@@ -1,4 +1,4 @@
const FileBlob = require('@now/build-utils/file-blob.js'); // eslint-disable-line import/no-extraneous-dependencies
const { FileBlob, shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
const unified = require('unified');
const unifiedStream = require('unified-stream');
const markdown = require('remark-parse');
@@ -38,3 +38,5 @@ exports.build = async ({ files, entrypoint, config }) => {
return { [replacedEntrypoint]: result };
};
exports.shouldServe = shouldServe;

View File

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

View File

@@ -8,9 +8,11 @@ const { runNpmInstall } = require('@now/build-utils/fs/run-user-scripts.js'); //
const writeFile = promisify(fs.writeFile);
exports.build = async ({ files, entrypoint, workPath }) => {
exports.build = async ({
files, entrypoint, workPath, meta,
}) => {
console.log('downloading user files...');
const downloadedFiles = await download(files, workPath);
const downloadedFiles = await download(files, workPath, meta);
console.log('writing package.json...');
const packageJson = { dependencies: { 'mdx-deck': '1.7.15' } };
const packageJsonPath = path.join(workPath, 'package.json');

View File

@@ -1,6 +1,6 @@
{
"name": "@now/mdx-deck",
"version": "0.4.19-canary.2",
"version": "0.5.4",
"license": "MIT",
"repository": {
"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,13 +0,0 @@
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = process.env.NOW_REGION === 'dev1' ? 'development' : 'production';
}
const { Server } = require('http');
const { Bridge } = require('./now__bridge');
const page = require('./page');
const server = new Server(page.render);
const bridge = new Bridge(server);
bridge.listen();
exports.launcher = bridge.launcher;

View File

@@ -1,9 +1,12 @@
{
"name": "@now/next",
"version": "0.1.3-canary.9",
"version": "0.3.2",
"license": "MIT",
"main": "./dist/index",
"scripts": {
"build": "tsc"
"build": "./getBridgeTypes.sh && tsc",
"test": "npm run build && jest",
"prepublish": "yarn run build"
},
"repository": {
"type": "git",
@@ -11,10 +14,20 @@
"directory": "packages/now-next"
},
"dependencies": {
"@now/node-bridge": "^1.0.2-canary.0",
"execa": "^1.0.0",
"@now/node-bridge": "^1.1.2",
"fs-extra": "^7.0.0",
"resolve-from": "^4.0.0",
"get-port": "^5.0.0",
"resolve-from": "^5.0.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,44 @@
import resolveFrom from 'resolve-from';
import { parse } from 'url';
import getPort from 'get-port';
import { createServer } from 'http';
import { syncEnvVars } from './utils';
process.on('unhandledRejection', err => {
console.error('Exiting builder due to build error:');
console.error(err);
process.exit(1);
});
async function main(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();
// The runtime env vars are passed in to `argv[2]`
// as a base64-encoded JSON string
const runtimeEnv = JSON.parse(
Buffer.from(process.argv[2], 'base64').toString()
);
syncEnvVars(process.env, process.env, runtimeEnv);
createServer((req, res) => {
const parsedUrl = parse(req.url || '', true);
handler(req, res, parsedUrl);
}).listen(openPort, () => {
if (process.send) {
process.send(url);
}
});
}
main(process.cwd());

View File

@@ -1,55 +1,62 @@
const { createLambda } = require('@now/build-utils/lambda'); // eslint-disable-line import/no-extraneous-dependencies
const download = require('@now/build-utils/fs/download'); // eslint-disable-line import/no-extraneous-dependencies
const FileFsRef = require('@now/build-utils/file-fs-ref'); // eslint-disable-line import/no-extraneous-dependencies
const FileBlob = require('@now/build-utils/file-blob'); // eslint-disable-line import/no-extraneous-dependencies
const resolveFrom = require('resolve-from');
const path = require('path');
const url = require('url');
const {
import { ChildProcess, fork, SpawnOptions } from 'child_process';
import {
pathExists,
readFile,
unlink as unlinkFile,
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,
runPackageJsonScript,
} = require('@now/build-utils/fs/run-user-scripts'); // eslint-disable-line import/no-extraneous-dependencies
const glob = require('@now/build-utils/fs/glob'); // eslint-disable-line import/no-extraneous-dependencies
const {
readFile,
writeFile,
unlink: unlinkFile,
remove: removePath,
pathExists,
} = require('fs-extra');
const semver = require('semver');
const nextLegacyVersions = require('./legacy-versions');
const {
} from '@now/build-utils';
import nextLegacyVersions from './legacy-versions';
import {
EnvConfig,
excludeFiles,
validateEntrypoint,
getNextConfig,
getPathsInside,
getRoutes,
includeOnlyEntryDirectory,
normalizePackageJson,
onlyStaticDirectory,
getNextConfig,
} = require('./utils');
filesFromDirectory,
stringMap,
syncEnvVars,
validateEntrypoint,
} from './utils';
/** @typedef { import('@now/build-utils/file-ref').Files } Files */
/** @typedef { import('@now/build-utils/fs/download').DownloadedFiles } DownloadedFiles */
interface BuildParamsMeta {
isDev: boolean | undefined;
env?: EnvConfig;
buildEnv?: EnvConfig;
}
/**
* @typedef {Object} BuildParamsMeta
* @property {boolean} [isDev] - Files object
* @property {?string} [requestPath] - Entrypoint specified for the builder
*/
interface BuildParamsType extends BuildOptions {
files: Files;
entrypoint: string;
workPath: string;
meta: BuildParamsMeta;
}
/**
* @typedef {Object} BuildParamsType
* @property {Files} files - Files object
* @property {string} entrypoint - Entrypoint specified for the builder
* @property {string} workPath - Working directory for this build
* @property {BuildParamsMeta} [meta] - Various meta settings
*/
export const version = 2;
/**
* Read package.json from files
* @param {string} entryPath
*/
async function readPackageJson(entryPath) {
async function readPackageJson(entryPath: string) {
const packagePath = path.join(entryPath, 'package.json');
try {
@@ -62,29 +69,28 @@ async function readPackageJson(entryPath) {
/**
* Write package.json
* @param {string} workPath
* @param {Object} packageJson
*/
async function writePackageJson(workPath, packageJson) {
async function writePackageJson(workPath: string, packageJson: Object) {
await writeFile(
path.join(workPath, 'package.json'),
JSON.stringify(packageJson, null, 2),
JSON.stringify(packageJson, null, 2)
);
}
/**
* 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(
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;
if (packageJson.dependencies && packageJson.dependencies.next) {
nextVersion = packageJson.dependencies.next;
@@ -94,7 +100,7 @@ function getNextVersion(packageJson) {
return nextVersion;
}
function isLegacyNext(nextVersion) {
function isLegacyNext(nextVersion: string) {
// If version is using the dist-tag instead of a version range
if (nextVersion === 'canary' || nextVersion === 'latest') {
return false;
@@ -114,43 +120,105 @@ function isLegacyNext(nextVersion) {
return true;
}
exports.config = {
const name = '[@now/next]';
const urls: stringMap = {};
function startDevServer(entryPath: string, runtimeEnv: EnvConfig) {
// The runtime env vars are encoded and passed in as `argv[2]`, so that the
// dev-server process can replace them onto `process.env` after the Next.js
// "prepare" step
const encodedEnv = Buffer.from(JSON.stringify(runtimeEnv)).toString('base64');
// `env` is omitted since that
// makes it default to `process.env`
const forked = fork(path.join(__dirname, 'dev-server.js'), [encodedEnv], {
cwd: entryPath,
execArgv: [],
});
const getUrl = () =>
new Promise<string>((resolve, reject) => {
forked.on('message', resolve);
forked.on('error', reject);
});
return { forked, getUrl };
}
export const config = {
maxLambdaSize: '5mb',
};
/**
* @param {BuildParamsType} buildParams
* @returns {Promise<Files>}
*/
exports.build = async ({
files, workPath, entrypoint, meta = {},
}) => {
export const build = async ({
files,
workPath,
entrypoint,
meta = {} as BuildParamsMeta,
}: BuildParamsType): Promise<{
routes?: any[];
output: Files;
watch?: string[];
childProcesses: ChildProcess[];
}> => {
validateEntrypoint(entrypoint);
console.log('downloading user files...');
const routes: any[] = [];
const entryDirectory = path.dirname(entrypoint);
await download(files, workPath);
const entryPath = path.join(workPath, entryDirectory);
const dotNext = path.join(entryPath, '.next');
if (await pathExists(dotNext)) {
if (meta.isDev) {
await removePath(dotNext).catch((e) => {
if (e.code !== 'ENOENT') throw e;
});
} else {
console.warn(
'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.',
);
}
}
console.log(`${name} Downloading user files...`);
await download(files, workPath, meta);
const pkg = await readPackageJson(entryPath);
const nextVersion = getNextVersion(pkg);
let 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"',
'No Next.js version could be detected in "package.json". Make sure `"next"` is installed in "dependencies" or "devDependencies"'
);
}
process.env.__NEXT_BUILDER_EXPERIMENTAL_TARGET = 'serverless';
if (meta.isDev) {
// eslint-disable-next-line no-underscore-dangle
process.env.__NEXT_BUILDER_EXPERIMENTAL_DEBUG = 'true';
let childProcess: ChildProcess | undefined;
// If this is the initial build, we want to start the server
if (!urls[entrypoint]) {
console.log(`${name} Installing dependencies...`);
await runNpmInstall(entryPath, ['--prefer-offline']);
// The runtime env vars consist of the base `process.env` vars, but with the
// build env vars removed, and the runtime env vars mixed in afterwards
const runtimeEnv: EnvConfig = Object.assign({}, process.env);
syncEnvVars(runtimeEnv, meta.buildEnv || {}, meta.env || {});
const { forked, getUrl } = startDevServer(entryPath, runtimeEnv);
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.'
);
}
@@ -172,7 +240,7 @@ exports.build = async ({
}
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');
@@ -181,7 +249,7 @@ exports.build = async ({
await writePackageJson(entryPath, packageJson);
} else if (!pkg.scripts || !pkg.scripts['now-build']) {
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 = {
'now-build': 'next build',
@@ -199,45 +267,14 @@ exports.build = async ({
console.log('installing dependencies...');
await runNpmInstall(entryPath, ['--prefer-offline']);
nextVersion = JSON.parse(
await readFile(resolveFrom(entryPath, 'next/package.json'), 'utf8'),
).version;
const isUpdated = (v) => {
if (v === 'canary') return true;
try {
return semver.satisfies(v, '>=8.0.5-canary.14', {
includePrerelease: true,
});
} catch (e) {
return false;
}
};
if ((meta.isDev || meta.requestPath) && !isUpdated(nextVersion)) {
throw new Error(
'`now dev` can only be used with Next.js >=8.0.5-canary.14!',
);
}
if (meta.isDev) {
// eslint-disable-next-line no-underscore-dangle
process.env.__NEXT_BUILDER_EXPERIMENTAL_DEBUG = 'true';
}
if (meta.requestPath) {
const { pathname } = url.parse(meta.requestPath);
const assetPath = pathname.match(
/^\/?_next\/static\/[^/]+\/pages\/(.+)\.js$/,
);
// eslint-disable-next-line no-underscore-dangle
process.env.__NEXT_BUILDER_EXPERIMENTAL_PAGE = assetPath
? assetPath[1]
: pathname;
}
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) {
console.log('running npm install --production...');
@@ -248,21 +285,21 @@ exports.build = async ({
await unlinkFile(path.join(entryPath, '.npmrc'));
}
const lambdas = {};
const lambdas: { [key: string]: Lambda } = {};
if (isLegacy) {
const filesAfterBuild = await glob('**', entryPath);
console.log('preparing lambda files...');
let buildId;
let buildId: string;
try {
buildId = await readFile(
path.join(entryPath, '.next', 'BUILD_ID'),
'utf8',
'utf8'
);
} catch (err) {
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');
}
@@ -270,12 +307,12 @@ exports.build = async ({
const dotNextServerRootFiles = await glob('.next/server/*', entryPath);
const nodeModules = excludeFiles(
await glob('node_modules/**', entryPath),
file => file.startsWith('node_modules/.cache'),
file => file.startsWith('node_modules/.cache')
);
const launcherFiles = {
'now__bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
};
const nextFiles = {
const nextFiles: { [key: string]: FileFsRef } = {
...nodeModules,
...dotNextRootFiles,
...dotNextServerRootFiles,
@@ -286,13 +323,13 @@ exports.build = async ({
}
const pages = await glob(
'**/*.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 launcherData = await readFile(launcherPath, 'utf8');
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
if (['_app.js', '_error.js', '_document.js'].includes(page)) {
return;
@@ -301,7 +338,7 @@ exports.build = async ({
const pathname = page.replace(/\.js$/, '');
const launcher = launcherData.replace(
'PATHNAME_PLACEHOLDER',
`/${pathname.replace(/(^|\/)index$/, '')}`,
`/${pathname.replace(/(^|\/)index$/, '')}`
);
const pageFiles = {
@@ -330,7 +367,7 @@ exports.build = async ({
runtime: 'nodejs8.10',
});
console.log(`Created lambda for page: "${page}"`);
}),
})
);
} else {
console.log('preparing lambda files...');
@@ -342,7 +379,7 @@ exports.build = async ({
};
const pages = await glob(
'**/*.js',
path.join(entryPath, '.next', 'serverless', 'pages'),
path.join(entryPath, '.next', 'serverless', 'pages')
);
const pageKeys = Object.keys(pages);
@@ -357,14 +394,14 @@ exports.build = async ({
}
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
const assets = await glob(
'assets/**',
path.join(entryPath, '.next', 'serverless'),
path.join(entryPath, '.next', 'serverless')
);
const assetKeys = Object.keys(assets);
@@ -374,7 +411,7 @@ exports.build = async ({
}
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
if (['_app.js', '_error.js', '_document.js'].includes(page)) {
return;
@@ -393,37 +430,65 @@ exports.build = async ({
runtime: 'nodejs8.10',
});
console.log(`Created lambda for page: "${page}"`);
}),
})
);
}
const nextStaticFiles = await glob(
'**',
path.join(entryPath, '.next', 'static'),
path.join(entryPath, '.next', 'static')
);
const staticFiles = Object.keys(nextStaticFiles).reduce(
(mappedFiles, file) => ({
...mappedFiles,
[path.join(entryDirectory, `_next/static/${file}`)]: nextStaticFiles[file],
[path.join(entryDirectory, `_next/static/${file}`)]: nextStaticFiles[
file
],
}),
{},
{}
);
const staticDirectoryFiles = onlyStaticDirectory(
includeOnlyEntryDirectory(files, entryDirectory),
entryDirectory,
const entryDirectoryFiles = includeOnlyEntryDirectory(files, entryDirectory);
const staticDirectoryFiles = filesFromDirectory(
entryDirectoryFiles,
path.join(entryDirectory, 'static')
);
const publicDirectoryFiles = filesFromDirectory(
entryDirectoryFiles,
path.join(entryDirectory, 'public')
);
const publicFiles = Object.keys(publicDirectoryFiles).reduce(
(mappedFiles, file) => ({
...mappedFiles,
[file.replace(/public[/\\]+/, '')]: publicDirectoryFiles[file],
}),
{}
);
return { ...lambdas, ...staticFiles, ...staticDirectoryFiles };
return {
output: {
...publicFiles,
...lambdas,
...staticFiles,
...staticDirectoryFiles,
},
routes: [],
watch: [],
childProcesses: [],
};
};
exports.prepareCache = async ({ workPath, entrypoint }) => {
export const prepareCache = async ({
workPath,
entrypoint,
}: PrepareCacheOptions) => {
console.log('preparing cache ...');
const entryDirectory = path.dirname(entrypoint);
const entryPath = path.join(workPath, entryDirectory);
const pkg = await readPackageJson(entryPath);
const nextVersion = getNextVersion(pkg);
if (!nextVersion) throw new Error('Could not parse Next.js version');
const isLegacy = isLegacyNext(nextVersion);
if (isLegacy) {
@@ -433,33 +498,12 @@ exports.prepareCache = async ({ workPath, entrypoint }) => {
console.log('producing cache file manifest ...');
const cacheEntrypoint = path.relative(workPath, entryPath);
return {
...(await glob(
path.join(
cacheEntrypoint,
'node_modules/{**,!.*,.yarn*,.cache/next-minifier/**}',
),
workPath,
)),
const cache = {
...(await glob(path.join(cacheEntrypoint, 'node_modules/**'), workPath)),
...(await glob(path.join(cacheEntrypoint, '.next/cache/**'), workPath)),
...(await glob(path.join(cacheEntrypoint, 'package-lock.json'), workPath)),
...(await glob(path.join(cacheEntrypoint, 'yarn.lock'), workPath)),
};
};
exports.subscribe = async ({ entrypoint, files }) => {
const entryDirectory = path.dirname(entrypoint);
const pageFiles = includeOnlyEntryDirectory(
files,
path.join(entryDirectory, 'pages'),
);
return [
path.join(entryDirectory, '_next/static/**'),
// List all pages without their extensions
...Object.keys(pageFiles).map(page => page
.replace(/^pages\//i, '')
.split('.')
.slice(0, -1)
.join('.')),
];
console.log('cache file manifest produced');
return cache;
};

View File

@@ -0,0 +1,17 @@
if (!process.env.NODE_ENV) {
process.env.NODE_ENV =
process.env.NOW_REGION === 'dev1' ? 'development' : 'production';
}
const { Server } = require('http');
const { Bridge } = require('./now__bridge');
const page = require('./page');
// page.render is for React rendering
// page.default is for /api rendering
// page is for module.exports in /api
const server = new Server(page.render || page.default || page);
const bridge = new Bridge(server);
bridge.listen();
exports.launcher = bridge.launcher;

View File

@@ -1,7 +1,7 @@
const { Server } = require('http');
const next = require('next-server');
const url = require('url');
const { Bridge } = require('./now__bridge');
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';
@@ -10,7 +10,7 @@ if (!process.env.NODE_ENV) {
const app = next({});
const server = new Server((req, res) => {
const parsedUrl = url.parse(req.url, true);
const parsedUrl = url.parse(req.url || '', true);
app.render(req, res, 'PATHNAME_PLACEHOLDER', parsedUrl.query, parsedUrl);
});

View File

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

View File

@@ -0,0 +1,279 @@
import fs from 'fs-extra';
import path from 'path';
import { Files } from '@now/build-utils';
type stringMap = { [key: string]: string };
export interface EnvConfig {
[name: string]: string | undefined;
}
/**
* Validate if the entrypoint is allowed to be used
*/
function validateEntrypoint(entrypoint: string) {
if (
!/package\.json$/.exec(entrypoint) &&
!/next\.config\.js$/.exec(entrypoint)
) {
throw new Error(
'Specified "src" for "@now/next" has to be "package.json" or "next.config.js"'
);
}
}
/**
* Exclude certain files from the files object
*/
function excludeFiles(
files: Files,
matcher: (filePath: string) => boolean
): Files {
return Object.keys(files).reduce((newFiles, filePath) => {
if (matcher(filePath)) {
return newFiles;
}
return {
...newFiles,
[filePath]: files[filePath],
};
}, {});
}
/**
* Creates a new Files object holding only the entrypoint files
*/
function includeOnlyEntryDirectory(
files: Files,
entryDirectory: string
): Files {
if (entryDirectory === '.') {
return files;
}
function matcher(filePath: string) {
return !filePath.startsWith(entryDirectory);
}
return excludeFiles(files, matcher);
}
/**
* Exclude package manager lockfiles from files
*/
function excludeLockFiles(files: Files): Files {
const newFiles = files;
if (newFiles['package-lock.json']) {
delete newFiles['package-lock.json'];
}
if (newFiles['yarn.lock']) {
delete newFiles['yarn.lock'];
}
return files;
}
/**
* Include only the files from a selected directory
*/
function filesFromDirectory(files: Files, dir: string): Files {
function matcher(filePath: string) {
return !filePath.startsWith(dir.replace(/\\/g, '/'));
}
return excludeFiles(files, matcher);
}
/**
* Enforce specific package.json configuration for smallest possible lambda
*/
function normalizePackageJson(
defaultPackageJson: {
dependencies?: stringMap;
devDependencies?: stringMap;
scripts?: stringMap;
} = {}
) {
const dependencies: stringMap = {};
const devDependencies: stringMap = {
...defaultPackageJson.dependencies,
...defaultPackageJson.devDependencies,
};
if (devDependencies.react) {
dependencies.react = devDependencies.react;
delete devDependencies.react;
}
if (devDependencies['react-dom']) {
dependencies['react-dom'] = devDependencies['react-dom'];
delete devDependencies['react-dom'];
}
return {
...defaultPackageJson,
dependencies: {
// react and react-dom can be overwritten
react: 'latest',
'react-dom': 'latest',
...dependencies, // override react if user provided it
// next-server is forced to canary
'next-server': 'v7.0.2-canary.49',
},
devDependencies: {
...devDependencies,
// next is forced to canary
next: 'v7.0.2-canary.49',
// next-server is a dependency here
'next-server': undefined,
},
scripts: {
...defaultPackageJson.scripts,
'now-build':
'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas',
},
};
}
async function getNextConfig(workPath: string, entryPath: string) {
const entryConfig = path.join(entryPath, './next.config.js');
if (await fs.pathExists(entryConfig)) {
return fs.readFile(entryConfig, 'utf8');
}
const workConfig = path.join(workPath, './next.config.js');
if (await fs.pathExists(workConfig)) {
return fs.readFile(workConfig, 'utf8');
}
return null;
}
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`,
},
];
const filePaths = Object.keys(filesInside);
for (const file of filePaths) {
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}`,
});
}
}
// Add public folder routes
for (const file of filePaths) {
const relativePath = path.relative(entryDirectory, file);
const isPublic = pathIsInside('public', relativePath);
if (!isPublic) continue;
const fileName = path.relative('public', relativePath);
const route = {
src: `${prefix}${fileName}`,
dest: `${url}/${fileName}`,
};
// Only add the route if a page is not already using it
if (!routes.some(r => r.src === route.src)) {
routes.push(route);
}
}
return routes;
}
function syncEnvVars(base: EnvConfig, removeEnv: EnvConfig, addEnv: EnvConfig) {
// Remove any env vars from `removeEnv`
// that are not present in the `addEnv`
const addKeys = new Set(Object.keys(addEnv));
for (const name of Object.keys(removeEnv)) {
if (!addKeys.has(name)) {
delete base[name];
}
}
// Add in the keys from `addEnv`
Object.assign(base, addEnv);
}
export {
excludeFiles,
validateEntrypoint,
includeOnlyEntryDirectory,
excludeLockFiles,
normalizePackageJson,
filesFromDirectory,
getNextConfig,
getPathsInside,
getRoutes,
stringMap,
syncEnvVars,
};

View File

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

View File

@@ -0,0 +1 @@
data

View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow: /

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