Compare commits

..

123 Commits

Author SHA1 Message Date
Andy Bitz
9f05a1865c Publish Stable
- @vercel/frameworks@0.0.17
 - @vercel/build-utils@2.4.2
 - vercel@19.2.0
 - @vercel/client@8.2.1
 - @vercel/go@1.1.4
 - @vercel/next@2.6.13
 - @vercel/node@1.7.3
 - @vercel/static-build@0.17.6
2020-07-21 14:58:27 +02:00
Andy
8d1afc026f Revert "[go] Implement startDevServer() function (#4662)" (#4899)
This reverts commit 04bea1e3cd.
2020-07-21 14:55:59 +02:00
Nathan Rajlich
130f36aad6 Publish Canary
- vercel@19.1.3-canary.6
2020-07-20 23:11:46 -07:00
Nathan Rajlich
dd87c9b0c6 Publish Canary
- vercel@19.1.3-canary.5
 - @vercel/next@2.6.13-canary.2
2020-07-20 19:51:15 -07:00
Max Leiter
f813b3340b [cli] Fetch environment variables from Project Settings if .env file is not present (#4562)
Previously, users would have to run `vc env pull` to fetch cloud environment
variables into `.env`. After this PR, if no `.env` or `.build.env` file is present,
environment variables will be pulled by `vc dev` from your Vercel Environment
Variables settings, no file necessary.
2020-07-20 19:47:13 -07:00
Joe Haddad
976b02e895 [next] Add tests for trailing slash behavior (#4894) 2020-07-20 22:29:09 -04:00
Nathan Rajlich
843be9658c [cli] Use update-notifier instead of update-check (#4896)
This makes the updating logic be asynchronous instead of synchronous, and as such will make boot-up of CLI be faster.

The actual update notification display is identical to previous, we are not using `update-notifier`'s default boxen rendering.
2020-07-20 22:31:15 +00:00
dependabot[bot]
ad501a4cd0 Bump lodash from 4.17.15 to 4.17.19 (#4877)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-07-20 14:00:32 -04:00
Steven
727ae587db Publish Canary
- @vercel/frameworks@0.0.17-canary.2
 - @vercel/build-utils@2.4.2-canary.1
 - vercel@19.1.3-canary.4
 - @vercel/client@8.2.1-canary.1
 - @vercel/next@2.6.13-canary.1
 - @vercel/node@1.7.3-canary.0
2020-07-20 10:27:10 -04:00
Steven
536b15079b [node][next] Bump node-file-trace to 0.8.0 (#4814)
Bump `node-file-trace` to version [0.7.0](https://github.com/vercel/node-file-trace/releases/tag/0.7.0) and then [0.8.0](https://github.com/vercel/node-file-trace/releases/tag/0.8.0)

- Add support for `exports`, dynamic import from CJS support, package boundary emission
- Fix trailing slash in `require()`
- Import named instead of default
2020-07-20 10:24:05 -04:00
Nathan Rajlich
887882697b [cli] Remove "npm pack" logic from output-is-source dev test (#4870)
Now that `@vercel/static-build` is being symlinked to CLI due to the
yarn workspace, `vc dev` will always use the latest code for
zero-config, so this "npm pack" logic is no longer necessary.

The `vercel.json` also no longer gets created in this test so that
zero-config is used.
2020-07-16 16:44:28 -07:00
Nathan Rajlich
e2db7c7734 [build-utils] Add getScriptName() function (#4869)
And allow a set of possible script names to use in `runPackageJsonScript()`.

These are only the changes to `@vercel/build-utils` from #4863.
2020-07-16 16:42:55 -07:00
Steven
022504787c [frameworks][examples] Update blitz new command (#4868)
Updated description per @leo
2020-07-16 22:16:03 +00:00
Nathan Rajlich
0f424de406 [cli] Set server: Vercel header and remove x-now response headers in vercel dev (#4867)
This matches production.
2020-07-16 20:23:44 +00:00
Nathan Rajlich
4819c3ac61 Publish Canary
- vercel@19.1.3-canary.3
2020-07-15 11:04:16 -07:00
Nathan Rajlich
c28ca7ef2d [cli] Set Vercel platform env vars for startDevServer() invocations (#4855)
Ensures that `VERCEL_REGION` is set for API calls when the runtime
implements `startDevServer()`.

Also adds `VERCEL_URL` which was previously not defined.
2020-07-15 10:34:27 -07:00
Nathan Rajlich
068ea00615 [cli] Set Vercel proxy request headers in startDevServer() (#4850)
The Vercel proxy request headers (`x-vercel-deployment-url` for example)
were not being sent to the dev server when the Runtime defines
`startDevServer()`. This is now fixed.

Also updates the header names to match production:

 - `now` -> `vercel`
 - 'x-now-trace', 'x-now-id', and 'x-now-log-id' are removed

Fixes #4729.
2020-07-14 14:48:46 -07:00
Paco
7f8145ab40 [cli] Minor text changes to wrong config error message (#4848)
CH-3242

From review:
- Capitalize "Project"
- Highlight ".vercel" in code snippet style

Thanks to @styfle for pointing me to output/code.
2020-07-14 13:53:23 +00:00
Steven
1a12715096 Publish Canary
- @vercel/build-utils@2.4.2-canary.0
 - vercel@19.1.3-canary.2
 - @vercel/client@8.2.1-canary.0
 - @vercel/next@2.6.13-canary.0
2020-07-13 18:57:39 -04:00
Steven
5b6d565360 [build-utils] Update error message for unmatched function pattern (#4849)
- [x] Depends on https://github.com/vercel/docs/pull/1985
2020-07-13 22:55:15 +00:00
JJ Kasper
0794158906 Publish Stable
- @vercel/next@2.6.12
2020-07-12 10:30:51 -05:00
JJ Kasper
02cdb88d3b Publish Canary
- vercel@19.1.3-canary.1
 - @vercel/next@2.6.12-canary.0
2020-07-12 10:25:27 -05:00
Paco
fa58855114 [cli] Show helpful error when using unauthorized .vercel config (#4807)
CH-3242

When a user has project configuration in `.vercel` that corresponds to a team that they are not a part of, we currently throw a 403 error. This adds special handling so that we can show a specific message when this happens.

I didn't want to update the return type of `getProjectByIdOrName` because we only want to handle this 403 in a special case, so I `throw` it instead.

Other changes are linter.
2020-07-11 03:52:02 +00:00
JJ Kasper
1fac11792f [next] Fix nested public files being renamed incorrectly (#4817)
This corrects the file rename for public files to not incorrectly rename nested `public` files

x-ref: https://github.com/vercel-support/next-nested-public-dir
2020-07-10 23:52:54 +00:00
Nathan Rajlich
05ffc9ce2b Publish Canary
- @vercel/frameworks@0.0.17-canary.1
 - vercel@19.1.3-canary.0
 - @vercel/go@1.1.4-canary.0
 - @vercel/static-build@0.17.6-canary.1
2020-07-10 15:51:19 -07:00
Nathan Rajlich
f848551043 [cli] Pin @vercel/static-build to "0.17.6-canary.0" (#4837)
It got de-synced in c68e83f972.
2020-07-10 22:49:35 +00:00
Steven
9712abc5bf [frameworks][api] Move dependency to devdependency (#4836)
Follow up to #4829
2020-07-10 16:38:19 -04:00
Nathan Rajlich
a9ef3cc726 [go] Add filesystem write fallback for startDevServer() (#4830)
This is for `@vercel/go` to work on Windows, which currently fails with
this error:

```
panic: write pipe: The handle is invalid.

goroutine 1 [running]:
main.main()
	C:/Users/Nathan/Code/casca/vercel-go-test/.vercel/cache/go/2flefmhra8o/api/vercel-dev-server-main.go:25 +0x1ec
exit status 2
```

So this fallback writes the port number to a temp file that
`startDevServer()` polls for in order to figure out the port number.
2020-07-10 19:55:53 +00:00
Steven
e5cc1d643a [frameworks][api] Add sort order (#4829)
This PR adds an optional property called `sort` to each framework so that we can change the order returned in the API. 
The reason this is necessary is because the order of the original array determines the precedence of framework detection. So we need another way to indicate the order of templates/examples returned from the API.

In particular, we need "Next.js" to be first and "Other" to be last.

I also updated the deprecated `@now/node` usage to `@vercel/node` in the API.
2020-07-10 18:09:14 +00:00
Andy Bitz
c68e83f972 Publish Canary
- @vercel/frameworks@0.0.17-canary.0
 - @vercel/static-build@0.17.6-canary.0
2020-07-10 18:45:47 +02:00
Sébastien Chopin
80118de040 [build-utils] Add Nuxt cache and default routes (#4755)
* [@vercel/build-utils] Improve Nuxt build

Need review from @pi0 and @danielroe

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

Co-authored-by: Daniel Roe <daniel@roe.dev>

Co-authored-by: Daniel Roe <daniel@roe.dev>
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>
2020-07-10 18:41:02 +02:00
Sébastien Chopin
c69da18e9c fix: Also handle nuxt-edge detection (#4756)
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>
2020-07-10 17:55:29 +02:00
Steven
e19446f89c Publish Stable
- @vercel/frameworks@0.0.16
 - @vercel/build-utils@2.4.1
 - vercel@19.1.2
 - @vercel/client@8.2.0
 - @vercel/go@1.1.3
 - @vercel/next@2.6.11
 - @vercel/node@1.7.2
 - @vercel/ruby@1.2.3
 - @vercel/static-build@0.17.5
2020-07-10 08:20:44 -07:00
JJ Kasper
ea3233502d Publish Stable
- @vercel/next@2.6.10
2020-07-09 21:30:31 -05:00
JJ Kasper
0a8810b64f Publish Canary
- vercel@19.1.2-canary.22
 - @vercel/next@2.6.10-canary.2
2020-07-09 21:29:18 -05:00
JJ Kasper
f6b373f0f4 [next] Fix preview mode with dynamic routes in shared lambdas (#4812)
This fixes dynamic routes missing inside of shared lambdas for non-revalidate pages that leverage preview mode which causes them to not be able to render. Additional tests have also been added to ensure preview mode is working properly
2020-07-10 02:22:17 +00:00
Steven
7ad2a99cd7 Publish Canary
- @vercel/frameworks@0.0.16-canary.2
 - @vercel/build-utils@2.4.1-canary.3
 - vercel@19.1.2-canary.21
 - @vercel/client@8.1.1-canary.3
 - @vercel/next@2.6.10-canary.1
 - @vercel/static-build@0.17.5-canary.1
2020-07-09 16:42:50 -07:00
JJ Kasper
0349eea494 [next] Add pages with invalid route regexes (#4813)
This adds failing page names to our tests which should be resolved once the correct regexes is being generated for the routes-manifest in Next.js

Co-authored-by: Joe Haddad <joe.haddad@zeit.co>
Co-authored-by: Steven <steven@ceriously.com>
2020-07-09 19:37:13 -04:00
Luc
ed4d006fb7 replace "more details" with "learn more" (#4828)
Follow up to PR #4825. Replace more "More details" with "Learn More".
2020-07-09 19:31:04 -04:00
Steven
12a9d203e9 [cli] Handle 400 errors as user errors (#4827)
When a user performs an action such as trying to add a domain they don't own, the API returns a 400-499 response code.

Previously, we would save this error in Sentry but this PR changes the behavior so that the error is printed locally for the user but not saved in Sentry.
2020-07-09 21:57:18 +00:00
Luc
ac1f4cf789 [cli] Handle API Error action property (#4825)
* handle error action

* fix test
2020-07-09 22:14:30 +02:00
Steven
b284ca350a Revert "[frameworks] Remove blitz demo (#4816)" (#4826)
This reverts commit f11eb32b2c.

Reverts PR #4816

This PR adds back the Blitz.js demo now that we confirmed the example works and is deployed to the correct account.
2020-07-09 13:28:38 -04:00
Steven
a43bf6c912 [cli][static-build] Use titlecase for "Project Settings" (#4820)
"project settings" should be "Project Settings"
2020-07-09 11:56:11 -04:00
Nathan Rajlich
62410806bb Publish Canary
- vercel@19.1.2-canary.20
 - @vercel/next@2.6.10-canary.0
2020-07-08 16:24:18 -07:00
Steven
47e3111cab [all] Change prettier config arrowParens: avoid (#4819)
In PR #4760, Prettier was upgraded to 2.0 which changed the default configuration for `arrowParens` from `avoid` to `always`. This PR changes it back to `avoid` so we don't have PRs changing unnecessary lines during in the commit hook.

See the [Prettier 2.0 Announcement](https://prettier.io/blog/2020/03/21/2.0.0.html#change-default-value-for-arrowparens-to-always-7430httpsgithubcomprettierprettierpull7430-by-kachkaevhttpsgithubcomkachkaev) for more.
2020-07-08 18:46:48 -04:00
Nathan Rajlich
135f35002f [cli] Remove caching of vercel.json config in vc dev (#4808)
This is a re-introduction of #4697, which was reverted for performance
resons. But now that #4760 and #4793 have landed, we can enable this
behavior again.
2020-07-08 22:37:55 +00:00
Steven
ee40052cee Publish Stable
- @vercel/next@2.6.9
2020-07-08 13:53:18 -07:00
JJ Kasper
86b730c1cd Publish Canary
- vercel@19.1.2-canary.19
 - @vercel/next@2.6.9-canary.1
2020-07-08 15:08:29 -05:00
JJ Kasper
b440249c26 [next] Don't use potentially invalid routeKeys/regexes (#4803)
The first version of `routeKeys` which was Array based could contain invalid named regexes which would break the build during route validation so this disables using them in that version of the manifest instead of trying to leverage them still. It also adds an additional test fixture with this version of the manifest to ensure it is still working properly
2020-07-08 20:05:00 +00:00
Steven
5380c12569 Publish Canary
- @vercel/frameworks@0.0.16-canary.1
 - @vercel/build-utils@2.4.1-canary.2
 - vercel@19.1.2-canary.18
 - @vercel/client@8.1.1-canary.2
 - @vercel/next@2.6.9-canary.0
2020-07-08 11:27:30 -07:00
Steven
f11eb32b2c [frameworks] Remove blitz demo (#4816)
This is not ready yet. We need to confirm Blitz is working before we deploy the demo.
2020-07-08 14:19:55 -04:00
Brandon Bayer
3d40e343ac [frameworks][examples] Add Blitz.js (#4465)
This adds [Blitz.js](https://github.com/blitz-js/blitz) to the zero-config frameworks.

Blitz compiles to a Next.js app, so it uses the same `@vercel/next` builder.

The change in `packages/now-build-utils/src/detect-builders.ts` was made according to @styfle's suggestion in Slack.

---

**This is a rerun of #4219 which had to be reverted because of this:**

> It looks like the @vercel/next package still requires next in the dependencies.
>
> When I've deployed the example I've got only a file listing, but when I added next to the dependencies it worked. I'll revert for now.

This new PR fixes that problem with a small refactor to `@vercel/next`.

`@vercel/next` was getting the Next version two different ways:

1. By checking project root package.json for `next` in deps or devDeps
2. By trying to resolve the installed next version and get `version` from its package.json

My refactor consolidates those two approaches by changing the existing `getNextVersion()` function to first try and resolve the real next version. If that fails, fall back to checking the project root.

Blitz bundles Next, so the real next package version should always be resolved.
2020-07-08 13:18:01 -04:00
Nathan Rajlich
80f525796f [cli] Make filesystem globbing faster in vercel dev (#4793)
The current code uses `getAllProjectFiles()` which globs for every file
in the project, not considering the `.vercelignore` file and default
ignore list. For example, every file in the `node_modules` directory is
selected, just for `vercel dev` to manually ignore them afterwards,
which is very slow and wasteful. For a simple Next.js project, this
globbing was taking over 3 seconds on my machine 🤯!

Solution is to use the `staticFiles()` function which is the same as
`vercel dev` uses at boot-up time. This function properly considers the
ignore list and thus is much faster, and the manual filtering is no
longer necessary by `vercel dev`. For the same simple Next.js project,
this function takes on average 10 milliseconds.

Also did a bit of cleanup and removed the `getAllProjectFiles()`
function since it is no longer used anywhere.
2020-07-07 20:30:21 +00:00
Nathan Rajlich
af4ad358f2 [cli] Print a warning if both "functions" and "builds" are defined in vercel.json in vercel dev (#4795)
This matches the error message that is used in production when using the
`vercel dev` command.
2020-07-07 19:41:29 +00:00
Nathan Rajlich
e6033d7a2d [client] Add @vercel/build-utils as a "dependency" (#4804)
Fixes: https://github.com/philcockfield/sample.vercel.client-error
2020-07-07 18:56:31 +00:00
JJ Kasper
d3148dffaa Publish Stable
- @vercel/next@2.6.8
2020-07-06 08:40:44 -05:00
JJ Kasper
30048cf4ff Publish Canary
- vercel@19.1.2-canary.17
 - @vercel/next@2.6.8-canary.7
2020-07-06 08:31:35 -05:00
JJ Kasper
07c65fa5c8 [next] Use correct data route regex (#4778)
For `getServerSideProps` routes the non-named data route was being used for routes causing dynamic route params that were being trusted from the proxy to be incorrect. Our tests weren't failing after trusting the values from the proxy was landed in Next.js on canary due to the `21-server-props` test suite not running against the latest canary which is updated in this PR. 

Note: the below Next.js PR is not needed or can be reverted if this PR is landed

x-ref: https://github.com/vercel/next.js/pull/14829
2020-07-05 21:13:01 +00:00
Nathan Rajlich
411ec64986 [cli] Reduce number of times getNowConfig() is invoked in vercel dev (#4760)
* [cli] Reduce number of times `getNowConfig()` is invoked in `vercel dev`

When using the `vercel dev` command, the `getNowConfig()` function was
being invoked many times per HTTP-request. It should only be read _once_
per HTTP request as it may be an expensive function depending on the
project size.

This change reduces the number of times the function is called to once
per HTTP request, and passes around the resulting `nowConfig` object to
the functions that require it.

* Fix default values

* Remove unnecessary type narrowing

* Remove one more `getNowConfig()` invocation

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-07-02 17:25:07 -07:00
Joe Haddad
e4d2cc704c Add Lockfile to Fix Gatsby Example (#4771)
This adds the Lockfile from Gatsby's official starter to fix deployments.

```
❯ npx gatsby-cli new ~/Desktop/scratch/test-default
❯ cp ~/Desktop/scratch/test-default/yarn.lock examples/gatsby
❯ cd examples/gatsby
❯ yarn
❯ git add examples/gatsby/yarn.lock
```
2020-07-02 21:06:44 +00:00
Joe Haddad
38db720586 Regenerate yarn.lock file (#4769) 2020-07-02 11:20:35 -07:00
Joe Haddad
c26c7886be [next] Skip Lambda Deployment for Static Sites (#4748) 2020-07-02 11:51:02 -04:00
JJ Kasper
b0e5d308ca [next] Add additiontal test cases for basePath support (#4747)
This adds some additional test cases for `basePath` support in Next.js to ensure it is working properly with `@vercel/next`
2020-07-02 09:40:15 +00:00
JJ Kasper
609b98cc73 Publish Canary
- vercel@19.1.2-canary.16
 - @vercel/next@2.6.8-canary.6
2020-07-01 14:46:28 -05:00
JJ Kasper
e0ec6c792b [next] Re-enable shared lambdas by default (#4757)
This re-enables the shared lambdas optimization by default and also adds additional tests to ensure that when a `now.json` or `vercel.json` contains either `routes` or `functions` configs the shared lambda optimization is disabled. 

Additional test deployments done:

- minimal `now.json` with `builds` config [deploy](https://shared-lambdas-tests-d646fsqju.vercel.app)
- `now.json` with `functions` config [deploy](https://shared-lambdas-tests-ahnuosp4s.vercel.app)
- `now.json` with `routes` config [deploy](https://shared-lambdas-tests-gulam3jda.vercel.app)
- minimal `vercel.json` with `builds` config [deploy](https://shared-lambdas-tests-7ic7wzirs.vercel.app)
- `vercel.json` with `functions` config [deploy](https://shared-lambdas-tests-7ic7wzirs.vercel.app)
- `vercel.json` with `routes` config [deploy](https://shared-lambdas-tests-rja2391tq.vercel.app)
2020-07-01 19:44:09 +00:00
Nathan Rajlich
b29db2fd1d [cli] Pin @vercel/static-build to v0.17.5-canary.0 (#4751)
It got de-synced in fa0f1b90b4.
2020-07-01 10:59:22 -07:00
JJ Kasper
b604ced99d Publish Canary
- vercel@19.1.2-canary.15
 - @vercel/next@2.6.8-canary.5
2020-07-01 10:53:18 -05:00
JJ Kasper
b7fd69517e [next] Fix dynamic route mapping with shared lambdas (#4740)
This fixes mapping dynamic routes to their shared lambdas failing from the `dynamicPageLambdaRoute` not being injected directly after the dynamic route due to dynamic routes now containing a `query` which caused the route lookup to fail. 

This is resolved by making sure to parse the pathname while injecting the `dynamicPageLambdaRoute` so the route can be looked up correctly. This also adds additional test cases to prevent regressing on this along with updating existing fixtures for custom routes being de-experimentalized
2020-07-01 07:30:41 +00:00
Nathan Rajlich
4c821a6fb5 Publish Canary
- vercel@19.1.2-canary.14
 - @vercel/node@1.7.2-canary.1
 - @vercel/static-build@0.17.5-canary.0
2020-06-30 16:27:46 -07:00
Nathan Rajlich
5c4fb319af [cli] Revert "Remove caching of vercel.json config in vc dev" (#4749)
This reverts commit ffb98781f1 (#4697),
because it was realized that `vc dev` reads this configuration file many
times per HTTP request, causing the server to feel extremely slow.

Reverting this optimization for now until the over-reading issue is
addressed.
2020-06-30 16:26:56 -07:00
Nathan Rajlich
fbe9ea0750 [node] Set the TypeScript "target" in vc dev (#4728)
Fixes: https://github.com/vercel/vercel/discussions/4724
2020-06-30 16:07:04 -07:00
Andy Bitz
fa0f1b90b4 Publish Stable 2020-06-30 23:33:47 +02:00
Andy Bitz
2daa0e28d3 Publish Canary
- @vercel/frameworks@0.0.16-canary.0
 - vercel@19.1.2-canary.13
 - @vercel/static-build@0.17.4-canary.1
2020-06-30 13:21:02 +02:00
Andy
48358b4986 [@vercel/build-utils] Cache .cache for Eleventy (#4743) 2020-06-30 12:54:44 +02:00
Steven
c59f44a63b [frameworks] Fix build command placeholder nuxt generate (#4737)
The Nuxt placeholder was wrong, it should be `nuxt generate` like the build command here: ca27864201/packages/now-static-build/src/frameworks.ts (L516)
2020-06-29 11:59:34 -04:00
Joe Haddad
ca27864201 Update "description" in package.json for Vercel (#4735) 2020-06-28 22:05:30 -07:00
Steven
6c81a87338 Publish Canary
- vercel@19.1.2-canary.12
 - @vercel/static-build@0.17.4-canary.0
2020-06-27 01:02:10 -04:00
Steven
a7bef9387b [static-build] Fix error handling for unexpected output directory (#4727)
This PR improves the error handling when a zero config framework has an unexpected output directory.

Previously, setting a Docusaurus 2.0 build command to `docusaurus build && mv build foo` would fail with the following:

```
Error: ENOENT: no such file or directory, scandir '/vercel/514ce14b/build'
```

With this PR, the error message will show the expected:

```
Error: No Output Directory named "build" found after the Build completed. You can configure the Output Directory in your project settings. Learn more: https://vercel.com/docs/v2/platform/frequently-asked-questions#missing-public-directory
```

I also changed the usage of [`promisify(fs)`](https://nodejs.org/docs/latest-v10.x/api/util.html#util_util_promisify_original) to [`fs.promises`](https://nodejs.org/docs/latest-v10.x/api/fs.html#fs_fs_promises_api) which is available in Node 10 or newer.

Lastly, I updated the test suite to check if the correct error message is returned for builds we expect to fail.
2020-06-26 23:56:45 -04:00
Nathan Rajlich
65d6c5e1f4 [cli] Use the configured "Output Directory" from project's settings in vc dev (#4715)
Previously, when a project has a specific "Output Directory" configured
in the project settings page, it was not being correctly considered when
using `vercel dev`.

The main fix here is passing in the full project settings object
(including the `outputDirectory`) to the `detectBuilders()` function.

Also cleaned up a few types and updated the error message that was
previously being rendered to use a short link.
2020-06-26 16:07:07 -07:00
Steven
c09355fdb3 Publish Canary
- vercel@19.1.2-canary.11
 - @vercel/client@8.1.1-canary.1
2020-06-26 13:11:05 -04:00
Steven
d9ac4c45e1 [cli] Fix vc dev serving pure static dot files (#4714)
Fixes ch1233 and discussion #4475
2020-06-26 17:10:11 +00:00
nkzawa
c2ff95714f Publish Canary
- @vercel/build-utils@2.4.1-canary.1
 - vercel@19.1.2-canary.10
 - @vercel/client@8.1.1-canary.0
 - @vercel/go@1.1.3-canary.1
 - @vercel/next@2.6.8-canary.4
2020-06-26 13:47:50 +09:00
Naoyuki Kanezawa
a51feb7a62 add source prop when deploying (#4701) 2020-06-26 13:44:31 +09:00
JJ Kasper
ff10918230 [next] Memoize lstating files (#4712)
Closes: https://github.com/vercel/next.js/issues/14429
2020-06-25 22:55:09 +00:00
Nathan Rajlich
04bea1e3cd [go] Implement startDevServer() function (#4662)
Due to Go's peculiar package importing structure, there's unfortunately no way to _directly_ invoke a Go serverless file, so the approach for this `startDevServer()` function is:

 * Create a temp directory in `.vercel/cache`
 * Run the `analyze` Go program on the entrypoint file to determine the `functionName`
 * Copy the entrypoint file into the temp directory, while modifying it to use `package main`
 * Copy the `dev-server.go` file into the temp directory, replacing the placeholder function name with the `functionName` retrieved from the analysis step from before
 * Execute `go run $tmp_dir` to spawn the Go HTTP server on an ephemeral port
 * Return the Go process id and port number for `vercel dev` to proxy to
 * After the Go process has exited, delete the temp directory
2020-06-24 19:50:00 +00:00
Milan Felix Šulc
bc5e5e8a9c [tests] Change now-php to vercel-php (#4710) 2020-06-24 11:58:17 -07:00
JJ Kasper
2e647175f5 Publish Canary
- vercel@19.1.2-canary.9
 - @vercel/next@2.6.8-canary.3
2020-06-24 10:20:04 -05:00
JJ Kasper
1a4e1d2fdd [next] Update revalidate tests and correct shared lambdas revalidate (#4704)
This adds more fine grained tests for our revalidate behavior to ensure pages are actually being updated instead of only checking the headers, it also corrects page lookups failing with shared lambdas enabled for dynamic pages using revalidate. 

Closes: https://github.com/vercel/next.js/issues/14497
2020-06-24 14:53:43 +00:00
Steven
49e2274d81 Publish Canary
- @vercel/build-utils@2.4.1-canary.0
 - vercel@19.1.2-canary.8
2020-06-24 09:24:46 -04:00
Steven
9af3938544 [cli][build-utils] Refactor ajv error message (#4705)
This PR is a follow up to #4694 so we can use this same helper function in the API.

I tried to follow the existing error message format so that the API can use a different prefix than the CLI.

The API currently returns a 400 with `Invalid request` when a Git deploy fails validation:

![api-comment](https://user-images.githubusercontent.com/229881/85466548-1c140b00-b578-11ea-9bed-1eb14df2685a.png)
2020-06-24 13:23:51 +00:00
Nathan Rajlich
7b5bf061c2 Publish Canary
- vercel@19.1.2-canary.7
 - @vercel/go@1.1.3-canary.0
2020-06-22 19:12:28 -07:00
Nathan Rajlich
ffb98781f1 [cli] Remove caching of vercel.json config in vc dev (#4697)
Since the filesystem watcher may be slow, it's actually faster and more
reliable to simply re-read the `vercel.json` configuration for every
HTTP request. This also simplifies the logic as an added benefit.

Some `sleep()` calls are removed from relevant tests that were
previously necessary due to the lag in the filesystem watcher.
2020-06-22 23:45:23 +00:00
Steven
3411fcbb68 Publish Canary
- vercel@19.1.2-canary.6
2020-06-22 18:42:23 -04:00
Steven
55cfd33338 [cli] Improve validation error message for vercel.json (#4694)
This PR improves the validation error message when the user has an invalid `vercel.json` file.

Previously, the error message did not account for nested properties so `{"foo": "will error"}` looked fine because it would mention there is an additional property `foo`. However, the error message for `{ "routes": [{ "foo": "will error" }] }` did not mention anything about `routes` when it explaining there was an additional property `foo`. This became more apparent as we added nested properties for `rewrites` and `redirects` (see tests in this PR).

This PR also adds suggestions for common mistakes such as `src` vs `source`.
2020-06-22 22:38:13 +00:00
JJ Kasper
93a9e5bed3 Publish Canary
- vercel@19.1.2-canary.5
 - @vercel/next@2.6.8-canary.2
2020-06-22 16:38:23 -05:00
JJ Kasper
b454021234 [next] Update tests to run against latest canary (#4682)
In the latest canary of Next.js pages are no longer nested under the `BUILD_ID` folder and instead are nested under a hash for the page bundle content. To prevent these tests from breaking too often from changes in canary this updates to locate the page bundle using the `buildManifest`. This also updates our latest SSG fixture to test against the latest canary to help ensure the feature doesn't break with a new canary release
2020-06-22 21:31:37 +00:00
Nathan Rajlich
bb705cd091 [docs] Refactor "Developing a Runtime" docs and add startDevServer() (#4675)
Co-authored-by: Steven <steven@ceriously.com>
2020-06-22 13:41:10 -07:00
Naoyuki Kanezawa
0986f4bcb6 [cli] Do not confirm vc env pull overwrite if created by CLI (#4678)
Append `# Created by Vercel CLI` to the head of `.env` file and automatically overwrite the file if it's there next time without confirmation.

https://app.clubhouse.io/vercel/story/316
2020-06-22 18:47:34 +00:00
Steven
83f77223aa Publish Canary
- vercel@19.1.2-canary.4
2020-06-19 16:23:32 -04:00
Steven
effda1fa6c [cli] Fix error message when no internet connectivity (#4687)
This PR fixes the error message when the client does not have internet connectivity or perhaps DNS is misconfigured such that the hostname cannot be resolved.
2020-06-19 15:57:26 -04:00
Steven
2ab6a7ef0c Publish Canary
- vercel@19.1.2-canary.3
 - @vercel/next@2.6.8-canary.1
2020-06-18 19:57:28 -04:00
Jean Helou
200bf5e996 [next] Fix nextjs routes in vercel dev (#4510)
This PR fixes #4239 where using `vercel dev` to work on monorepos where
the Next.js app is not in the topmost directory fails to correctly route
to dynamic pages.
After investigating the devServer router, @styfle prompted me to
investigate the @vercel/next builder. He also suggested restricting
`check` to be false only when running in `now dev`.

Co-authored-by: Steven <steven@ceriously.com>
2020-06-18 19:28:53 -04:00
Steven
94ab2512e9 Publish Canary
- vercel@19.1.2-canary.2
 - @vercel/ruby@1.2.3-canary.0
2020-06-18 17:19:29 -04:00
Jared White
da3207278e [ruby] Fix for UTF-8 responses in Ruby functions (#4593)
There's an unexpected string encoding issue with `Net::HTTP`, so this is a workaround. Further details:
https://bugs.ruby-lang.org/issues/15517

Co-authored-by: Steven <steven@ceriously.com>
2020-06-18 17:12:28 -04:00
nkzawa
8e10a82b43 Publish Canary
- vercel@19.1.2-canary.1
2020-06-18 20:24:49 +09:00
Naoyuki Kanezawa
d7731d191b Fix to not render no token error, prompt login instead (#4659)
When `auth.json` exists but no `token` property exists in it, we currently render error like the following screenshot.
<img width="488" alt="Screen Shot 2020-06-15 at 21 32 55" src="https://user-images.githubusercontent.com/775227/84657782-cd4eeb80-af4f-11ea-901d-2ad4b52b6667.png">

This PR fixes to prompt login instead.
<img width="994" alt="Screen Shot 2020-06-15 at 21 28 09" src="https://user-images.githubusercontent.com/775227/84657706-aa243c00-af4f-11ea-915e-4ada7de9c467.png">
2020-06-17 17:20:08 +00:00
Steven
0f5f99e667 Publish Canary
- vercel@19.1.2-canary.0
 - @vercel/next@2.6.8-canary.0
 - @vercel/node@1.7.2-canary.0
2020-06-16 17:41:45 -04:00
Steven
93a7831943 [node][next] Bump node-file-trace to 0.6.5 (#4667)
Bump `node-file-trace` to version [0.6.5](https://github.com/vercel/node-file-trace/releases/tag/0.6.5) to fix a webpack wrappers bug
2020-06-16 21:41:13 +00:00
Steven
0eacbeae11 Publish Stable
- vercel@19.1.1
 - @vercel/next@2.6.7
 - @vercel/node@1.7.1
 - @vercel/static-build@0.17.3
2020-06-16 13:09:42 -04:00
Steven
e50417dc47 Publish Canary
- vercel@19.1.1-canary.4
 - @vercel/static-build@0.17.3-canary.0
2020-06-16 08:55:29 -04:00
Paco
4840a25d30 [static-build] Apply Gatsby default caching routes automatically (#4645)
CH-702
Fixes #3331

If the file generated by `gatsby-plugin-now` does not exist, we'll set up these routes automatically.
2020-06-15 22:15:14 +00:00
Steven
785e91979d Publish Canary
- vercel@19.1.1-canary.3
 - @vercel/next@2.6.7-canary.1
 - @vercel/node@1.7.1-canary.1
2020-06-15 16:46:03 -04:00
Steven
7a776c54b8 [node][next] Bump node-file-trace to 0.6.4 (#4617)
Bump node-file-trace to fix a webpack tracing bug that looks like the following:


```
TypeError: Cannot read property 'type' of null
    at handleWrappers (/vercel/e77875438c1cd74b/.build-utils/.builder/node_modules/@vercel/next/dist/index.js:12763:58)
    at module.exports.447.module.exports (/vercel/e77875438c1cd74b/.build-utils/.builder/node_modules/@vercel/next/dist/index.js:14674:3)
    at Job.emitDependency (/vercel/e77875438c1cd74b/.build-utils/.builder/node_modules/@vercel/next/dist/index.js:11730:40)
    at /vercel/e77875438c1cd74b/.build-utils/.builder/node_modules/@vercel/next/dist/index.js:11755:20
    at async Promise.all (index 9)
    at async Job.emitDependency (/vercel/e77875438c1cd74b/.build-utils/.builder/node_modules/@vercel/next/dist/index.js:11736:5)
    at async Promise.all (index 1)
    at async Object.module.exports.328.module.exports [as default] (/vercel/e77875438c1cd74b/.build-utils/.builder/node_modules/@vercel/next/dist/index.js:11514:3)
    at async Object.module.exports.178.exports.build (/vercel/e77875438c1cd74b/.build-utils/.builder/node_modules/@vercel/next/dist/index.js:6076:69)
    at async buildStep (/var/task/sandbox-worker.js:20407:20)
```

- [0.6.2](https://github.com/vercel/node-file-trace/releases/tag/0.6.2)
- [0.6.3](https://github.com/vercel/node-file-trace/releases/tag/0.6.3) 
- [0.6.4](https://github.com/vercel/node-file-trace/releases/tag/0.6.4)

Depends on #4355
2020-06-15 20:45:15 +00:00
JJ Kasper
997031c53b [next] Update handling experimental basePath (#3791)
As mentioned by @dav-is we can prefix the outputs with the `basePath` instead of using a rewrite so that we make sure to 404 when the basePath isn't prefixed on the output. This behavior is also matched in Next.js with [this PR](https://github.com/zeit/next.js/pull/9988). 

x-ref: https://github.com/zeit/now/pull/3478
2020-06-15 17:43:37 +00:00
Steven
fdee03a599 Publish Canary
- vercel@19.1.1-canary.2
2020-06-15 12:38:11 -04:00
Steven
c5a93ecdad [cli] Fix vc dev proxy to upstream frontend dev server (#4644)
This PR fixes a longstanding issue introduced in #3673 that prevents routing properties from applying to the framework's upstream dev server.

This mimic's the older proxy logic used in build matches here: 5035fa537f/packages/now-cli/src/util/dev/server.ts (L1535-L1539)

- Related to #3777
- Related to #4510
2020-06-15 16:37:08 +00:00
JJ Kasper
5a9391b7ce Publish Canary
- vercel@19.1.1-canary.1
 - @vercel/next@2.6.7-canary.0
2020-06-15 10:20:38 -05:00
JJ Kasper
99e49473b8 [next] Update dynamic route named regexes and route keys (#4355)
This updates to leverage changes from https://github.com/zeit/next.js/pull/12801 which resolves invalid named regexes being used when the segments contain non-word characters e.g. `/[hello-world]`. 

Failing page names have also been added to the `23-custom-routes-verbose` fixture. Since the routeKeys aren't applied for dynamic routes in this PR until the routes-manifest version is bumped in the latest canary of Next.js the added test cases will be passing now and should be re-run to ensure passing after a new canary of Next.js is released with the routes-manifest version bump
2020-06-15 15:15:38 +00:00
Nathan Rajlich
de1cc6f9a7 [cli] Add a test case for resolving default "typescript" version in @vercel/node (#4656)
Adds a test case for #4655 (it got auto-merged too quickly).
2020-06-14 21:52:33 +00:00
Nathan Rajlich
08eedd8f34 Publish Canary
- vercel@19.1.1-canary.0
 - @vercel/node@1.7.1-canary.0
2020-06-14 14:00:04 -07:00
Nathan Rajlich
5021a71a8e [node] Don't resolve "typescript" from the dist dir (#4655)
On Node 10, the `require.resolve()` with "paths" does not return the
proper value relative to the `node_modules` directory. To wit:

```
$ node -v
v10.16.3

$ node -p "require.resolve('typescript', { paths: [process.cwd()] })"
/Users/nrajlich/Code/vercel/vercel/packages/now-node/dist/typescript.js

$ node -v
v14.4.0

$ node -p "require.resolve('typescript', { paths: [process.cwd()] })"
/Users/nrajlich/Code/vercel/vercel/node_modules/typescript/lib/typescript.js
```

(**Note:** cwd when running these commands is the `dist` dir of `@vercel/node`)

So the solution is to just let `require.resolve()` throw an error so the
default string "typescript" is used instead of a resolved absolute path.
2020-06-14 20:47:23 +00:00
Max Leiter
56671d7c2f [tests] Don't revoke VERCEL_TOKEN after tests run (#4643)
`VERCEL_TOKEN` will no longer be invalidated during tests

Story: https://app.clubhouse.io/vercel/story/2281
2020-06-12 22:49:37 +00:00
Steven
5035fa537f [cli] Add test for gatsby cache (#4447)
This PR adds a E2E CLI test to ensure that the Gatsby example deploys correctly and that the second deployment has the proper cached directories.

Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>
2020-06-12 16:51:55 -04:00
267 changed files with 32801 additions and 1844 deletions

View File

@@ -1,10 +1,33 @@
# Runtime Developer Reference # Runtime Developer Reference
The following page is a reference for how to create a Runtime using the available Runtime API. The following page is a reference for how to create a Runtime by implementing
the Runtime API interface.
A Runtime is an npm module that exposes a `build` function and optionally an `analyze` function and `prepareCache` function. A Runtime is an npm module that implements the following interface:
Official Runtimes are published to [npmjs.com](https://npmjs.com) as a package and referenced in the `use` property of the `vercel.json` configuration file.
However, the `use` property will work with any [npm install argument](https://docs.npmjs.com/cli/install) such as a git repo url which is useful for testing your Runtime. ```typescript
interface Runtime {
version: number;
build: (options: BuildOptions) => Promise<BuildResult>;
analyze?: (options: AnalyzeOptions) => Promise<string>;
prepareCache?: (options: PrepareCacheOptions) => Promise<CacheOutputs>;
shouldServe?: (options: ShouldServeOptions) => Promise<boolean>;
startDevServer?: (
options: StartDevServerOptions
) => Promise<StartDevServerResult>;
}
```
The `version` property and the `build()` function are the only _required_ fields.
The rest are optional extensions that a Runtime _may_ implement in order to
enhance functionality. These functions are documented in more detail below.
Official Runtimes are published to [the npm registry](https://npmjs.com) as a package and referenced in the `use` property of the `vercel.json` configuration file.
> **Note:** The `use` property in the `builds` array will work with any [npm
> install argument](https://docs.npmjs.com/cli/install) such as a git repo URL,
> which is useful for testing your Runtime. Alternatively, the `functions` property
> requires that you specify a specifc tag published to npm, for stability purposes.
See the [Runtimes Documentation](https://vercel.com/docs/runtimes) to view example usage. See the [Runtimes Documentation](https://vercel.com/docs/runtimes) to view example usage.
@@ -16,146 +39,170 @@ A **required** exported constant that decides which version of the Runtime API t
The latest and suggested version is `3`. The latest and suggested version is `3`.
### `analyze` **Example:**
An **optional** exported function that returns a unique fingerprint used for the purpose of [build de-duplication](https://vercel.com/docs/v2/platform/deployments#deduplication). If the `analyze` function is not supplied, a random fingerprint is assigned to each build. ```typescript
export const version = 3;
```js
export analyze({
files: Files,
entrypoint: String,
workPath: String,
config: Object
}) : String fingerprint
``` ```
If you are using TypeScript, you should use the following types: ### `build()`
```ts A **required** exported function that returns a Serverless Function.
import { AnalyzeOptions } from '@vercel/build-utils'
export analyze(options: AnalyzeOptions) { > What's a Serverless Function? Read about [Serverless Functions](https://vercel.com/docs/v2/serverless-functions/introduction) to learn more.
return 'fingerprint goes here'
**Example:**
```typescript
import { BuildOptions, createLambda } from '@vercel/build-utils';
export async function build(options: BuildOptions) {
// Build the code here…
const lambda = createLambda(/* … */);
return {
output: lambda,
watch: [
// Dependent files to trigger a rebuild in `vercel dev` go here…
],
routes: [
// If your Runtime needs to define additional routing, define it here…
],
};
} }
``` ```
### `build` ### `analyze()`
A **required** exported function that returns a [Serverless Function](#serverless-function). An **optional** exported function that returns a unique fingerprint used for the
purpose of [build
de-duplication](https://vercel.com/docs/v2/platform/deployments#deduplication).
If the `analyze()` function is not supplied, then a random fingerprint is
assigned to each build.
What's a Serverless Function? Read about [Serverless Functions](https://vercel.com/docs/v2/serverless-functions/introduction) to learn more. **Example:**
```js ```typescript
build({ import { AnalyzeOptions } from '@vercel/build-utils';
files: Files,
entrypoint: String, export async function analyze(options: AnalyzeOptions) {
workPath: String, // Do calculations to generate a fingerprint based off the source code here…
config: Object,
meta?: { return 'fingerprint goes here';
isDev?: Boolean,
requestPath?: String,
filesChanged?: Array<String>,
filesRemoved?: Array<String>
}
}) : {
watch?: Array<String>,
output: Lambda,
routes?: Object
} }
``` ```
If you are using TypeScript, you should use the following types: ### `prepareCache()`
```ts An **optional** exported function that is executed after [`build()`](#build) is
import { BuildOptions } from '@vercel/build-utils' completed. The implementation should return an object of `File`s that will be
pre-populated in the working directory for the next build run in the user's
project. An example use-case is that `@vercel/node` uses this function to cache
the `node_modules` directory, making it faster to install npm dependencies for
future builds.
export build(options: BuildOptions) { **Example:**
// Build the code here
```typescript
import { PrepareCacheOptions } from '@vercel/build-utils';
export async function prepareCache(options: PrepareCacheOptions) {
// Create a mapping of file names and `File` object instances to cache here…
return { return {
output: { 'path-to-file': File,
'path-to-file': File, };
'path-to-lambda': Lambda
},
watch: [],
routes: {}
}
} }
``` ```
### `prepareCache` ### `shouldServe()`
An **optional** exported function that is equivalent to [`build`](#build), but it executes the instructions necessary to prepare a cache for the next run. An **optional** exported function that is only used by `vercel dev` in [Vercel
CLI](https://vercel.com/download) and indicates whether a
[Runtime](https://vercel.com/docs/runtimes) wants to be responsible for responding
to a certain request path.
```js **Example:**
prepareCache({
files: Files,
entrypoint: String,
workPath: String,
cachePath: String,
config: Object
}) : Files cacheOutput
```
If you are using TypeScript, you can import the types for each of these functions by using the following: ```typescript
import { ShouldServeOptions } from '@vercel/build-utils';
```ts export async function shouldServe(options: ShouldServeOptions) {
import { PrepareCacheOptions } from '@vercel/build-utils' // Determine whether or not the Runtime should respond to the request path here…
export prepareCache(options: PrepareCacheOptions) { return options.requestPath === options.entrypoint;
return { 'path-to-file': File }
} }
``` ```
### `shouldServe` If this function is not defined, Vercel CLI will use the [default implementation](https://github.com/vercel/vercel/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
An **optional** exported function that is only used by `vercel dev` in [Vercel CLI](https:///download) and indicates whether a [Runtime](https://vercel.com/docs/runtimes) wants to be responsible for building a certain request path. ### `startDevServer()`
```js An **optional** exported function that is only used by `vercel dev` in [Vercel
shouldServe({ CLI](https://vercel.com/download). If this function is defined, Vercel CLI will
entrypoint: String, **not** invoke the `build()` function, and instead invoke this function for every
files: Files, HTTP request. It is an opportunity to provide an optimized development experience
config: Object, rather than going through the entire `build()` process that is used in production.
requestPath: String,
workPath: String
}) : Boolean
```
If you are using TypeScript, you can import the types for each of these functions by using the following: This function is invoked _once per HTTP request_ and is expected to spawn a child
process which creates an HTTP server that will execute the entrypoint code when
an HTTP request is received. This child process is _single-serve_ (only used for
a single HTTP request). After the HTTP response is complete, `vercel dev` sends
a shut down signal to the child process.
```ts The `startDevServer()` function returns an object with the `port` number that the
import { ShouldServeOptions } from '@vercel/build-utils' child process' HTTP server is listening on (which should be an [ephemeral
port](https://stackoverflow.com/a/28050404/376773)) as well as the child process'
Process ID, which `vercel dev` uses to send the shut down signal to.
export shouldServe(options: ShouldServeOptions) { > **Hint:** To determine which ephemeral port the child process is listening on,
return Boolean > some form of [IPC](https://en.wikipedia.org/wiki/Inter-process_communication) is
> required. For example, in `@vercel/go` the child process writes the port number
> to [_file descriptor 3_](https://en.wikipedia.org/wiki/File_descriptor), which is read by the `startDevServer()` function
> implementation.
It may also return `null` to opt-out of this behavior for a particular request
path or entrypoint.
**Example:**
```typescript
import { spawn } from 'child_process';
import { StartDevServerOptions } from '@vercel/build-utils';
export async function startDevServer(options: StartDevServerOptions) {
// Create a child process which will create an HTTP server.
//
// Note: `my-runtime-dev-server` is an example dev server program name.
// Your implementation will spawn a different program specific to your runtime.
const child = spawn('my-runtime-dev-server', [options.entrypoint], {
stdio: ['ignore', 'inherit', 'inherit', 'pipe'],
});
// In this example, the child process will write the port number to FD 3…
const portPipe = child.stdio[3];
const childPort = await new Promise(resolve => {
portPipe.setEncoding('utf8');
portPipe.once('data', data => {
resolve(Number(data));
});
});
return { pid: child.pid, port: childPort };
} }
``` ```
If this method is not defined, Vercel CLI will default to [this function](https://github.com/vercel/vercel/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
### Runtime Options
The exported functions [`analyze`](#analyze), [`build`](#build), and [`prepareCache`](#preparecache) receive one argument with the following properties.
**Properties:**
- `files`: All source files of the project as a [Files](#files) data structure.
- `entrypoint`: Name of entrypoint file for this particular build job. Value `files[entrypoint]` is guaranteed to exist and be a valid [File](#files) reference. `entrypoint` is always a discrete file and never a glob, since globs are expanded into separate builds at deployment time.
- `workPath`: A writable temporary directory where you are encouraged to perform your build process. This directory will be populated with the restored cache from the previous run (if any) for [`analyze`](#analyze) and [`build`](#build).
- `cachePath`: A writable temporary directory where you can build a cache for the next run. This is only passed to `prepareCache`.
- `config`: An arbitrary object passed from by the user in the [Build definition](#defining-the-build-step) in `vercel.json`.
## Examples
Check out our [Node.js Runtime](https://github.com/vercel/vercel/tree/master/packages/now-node), [Go Runtime](https://github.com/vercel/vercel/tree/master/packages/now-go), [Python Runtime](https://github.com/vercel/vercel/tree/master/packages/now-python) or [Ruby Runtime](https://github.com/vercel/vercel/tree/master/packages/now-ruby) for examples of how to build one.
## Technical Details
### Execution Context ### Execution Context
A [Serverless Function](https://vercel.com/docs/v2/serverless-functions/introduction) is created where the Runtime logic is executed. The lambda is run using the Node.js 8 runtime. A brand new sandbox is created for each deployment, for security reasons. The sandbox is cleaned up between executions to ensure no lingering temporary files are shared from build to build. - Runtimes are executed in a Linux container that closely matches the Servereless Function runtime environment.
- The Runtime code is executed using Node.js version **12.x**.
- A brand new sandbox is created for each deployment, for security reasons.
- The sandbox is cleaned up between executions to ensure no lingering temporary files are shared from build to build.
All the APIs you export ([`analyze`](#analyze), [`build`](#build) and [`prepareCache`](#preparecache)) are not guaranteed to be run in the same process, but the filesystem we expose (e.g.: `workPath` and the results of calling [`getWriteableDirectory`](#getWriteableDirectory) ) is retained. All the APIs you export ([`analyze()`](#analyze), [`build()`](#build),
[`prepareCache()`](#preparecache), etc.) are not guaranteed to be run in the
same process, but the filesystem we expose (e.g.: `workPath` and the results
of calling [`getWritableDirectory`](#getWritableDirectory) ) is retained.
If you need to share state between those steps, use the filesystem. If you need to share state between those steps, use the filesystem.
@@ -173,11 +220,11 @@ The env and secrets specified by the user as `build.env` are passed to the Runti
When you publish your Runtime to npm, make sure to not specify `@vercel/build-utils` (as seen below in the API definitions) as a dependency, but rather as part of `peerDependencies`. When you publish your Runtime to npm, make sure to not specify `@vercel/build-utils` (as seen below in the API definitions) as a dependency, but rather as part of `peerDependencies`.
## Types ## `@vercel/build-utils` Types
### `Files` ### `Files`
```ts ```typescript
import { File } from '@vercel/build-utils'; import { File } from '@vercel/build-utils';
type Files = { [filePath: string]: File }; type Files = { [filePath: string]: File };
``` ```
@@ -188,7 +235,7 @@ When used as an input, the `Files` object will only contain `FileRefs`. When `Fi
An example of a valid output `Files` object is: An example of a valid output `Files` object is:
```json ```javascript
{ {
"index.html": FileRef, "index.html": FileRef,
"api/index.js": Lambda "api/index.js": Lambda
@@ -199,7 +246,7 @@ An example of a valid output `Files` object is:
This is an abstract type that can be imported if you are using TypeScript. This is an abstract type that can be imported if you are using TypeScript.
```ts ```typescript
import { File } from '@vercel/build-utils'; import { File } from '@vercel/build-utils';
``` ```
@@ -211,71 +258,71 @@ Valid `File` types include:
### `FileRef` ### `FileRef`
```ts ```typescript
import { FileRef } from '@vercel/build-utils'; import { FileRef } from '@vercel/build-utils';
``` ```
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract file instance stored in our platform, based on the file identifier string (its checksum). When a `Files` object is passed as an input to `analyze` or `build`, all its values will be instances of `FileRef`. This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract file instance stored in our platform, based on the file identifier string (its checksum). When a `Files` object is passed as an input to `analyze` or `build`, all its values will be instances of `FileRef`.
**Properties:** **Properties:**
- `mode : Number` file mode - `mode: Number` file mode
- `digest : String` a checksum that represents the file - `digest: String` a checksum that represents the file
**Methods:** **Methods:**
- `toStream() : Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body - `toStream(): Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body
### `FileFsRef` ### `FileFsRef`
```ts ```typescript
import { FileFsRef } from '@vercel/build-utils'; import { FileFsRef } from '@vercel/build-utils';
``` ```
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract instance of a file present in the filesystem that the build process is executing in. This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract instance of a file present in the filesystem that the build process is executing in.
**Properties:** **Properties:**
- `mode : Number` file mode - `mode: Number` file mode
- `fsPath : String` the absolute path of the file in file system - `fsPath: String` the absolute path of the file in file system
**Methods:** **Methods:**
- `static async fromStream({ mode : Number, stream : Stream, fsPath : String }) : FileFsRef` creates an instance of a [FileFsRef](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) from `Stream`, placing file at `fsPath` with `mode` - `static async fromStream({ mode: Number, stream: Stream, fsPath: String }): FileFsRef` creates an instance of a [FileFsRef](#FileFsRef) from `Stream`, placing file at `fsPath` with `mode`
- `toStream() : Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body - `toStream(): Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body
### `FileBlob` ### `FileBlob`
```ts ```typescript
import { FileBlob } from '@vercel/build-utils'; import { FileBlob } from '@vercel/build-utils';
``` ```
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract instance of a file present in memory. This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract instance of a file present in memory.
**Properties:** **Properties:**
- `mode : Number` file mode - `mode: Number` file mode
- `data : String | Buffer` the body of the file - `data: String | Buffer` the body of the file
**Methods:** **Methods:**
- `static async fromStream({ mode : Number, stream : Stream }) :FileBlob` creates an instance of a [FileBlob](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) from [`Stream`](https://nodejs.org/api/stream.html) with `mode` - `static async fromStream({ mode: Number, stream: Stream }): FileBlob` creates an instance of a [FileBlob](#FileBlob) from [`Stream`](https://nodejs.org/api/stream.html) with `mode`
- `toStream() : Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body - `toStream(): Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body
### `Lambda` ### `Lambda`
```ts ```typescript
import { Lambda } from '@vercel/build-utils'; import { Lambda } from '@vercel/build-utils';
``` ```
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes), called a Serverless Function, that can be created by supplying `files`, `handler`, `runtime`, and `environment` as an object to the [`createLambda`](#createlambda) helper. The instances of this class should not be created directly. Instead, invoke the [`createLambda`](#createlambda) helper function. This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents a Serverless Function. An instance can be created by supplying `files`, `handler`, `runtime`, and `environment` as an object to the [`createLambda`](#createlambda) helper. The instances of this class should not be created directly. Instead, invoke the [`createLambda`](#createlambda) helper function.
**Properties:** **Properties:**
- `files : Files` the internal filesystem of the lambda - `files: Files` the internal filesystem of the lambda
- `handler : String` path to handler file and (optionally) a function name it exports - `handler: String` path to handler file and (optionally) a function name it exports
- `runtime : LambdaRuntime` the name of the lambda runtime - `runtime: LambdaRuntime` the name of the lambda runtime
- `environment : Object` key-value map of handler-related (aside of those passed by user) environment variables - `environment: Object` key-value map of handler-related (aside of those passed by user) environment variables
### `LambdaRuntime` ### `LambdaRuntime`
@@ -291,15 +338,15 @@ This is an abstract enumeration type that is implemented by one of the following
- `ruby2.5` - `ruby2.5`
- `provided` - `provided`
## JavaScript API ## `@vercel/build-utils` Helper Functions
The following is exposed by `@vercel/build-utils` to simplify the process of writing Runtimes, manipulating the file system, using the above types, etc. The following is exposed by `@vercel/build-utils` to simplify the process of writing Runtimes, manipulating the file system, using the above types, etc.
### `createLambda` ### `createLambda()`
Signature: `createLambda(Object spec) : Lambda` Signature: `createLambda(Object spec): Lambda`
```ts ```typescript
import { createLambda } from '@vercel/build-utils'; import { createLambda } from '@vercel/build-utils';
``` ```
@@ -316,29 +363,33 @@ await createLambda({
}); });
``` ```
### `download` ### `download()`
Signature: `download() : Files` Signature: `download(): Files`
```ts ```typescript
import { download } from '@vercel/build-utils'; import { download } from '@vercel/build-utils';
``` ```
This utility allows you to download the contents of a [`Files`](#files) data structure, therefore creating the filesystem represented in it. This utility allows you to download the contents of a [`Files`](#files) data
structure, therefore creating the filesystem represented in it.
Since `Files` is an abstract way of representing files, you can think of `download` as a way of making that virtual filesystem _real_. Since `Files` is an abstract way of representing files, you can think of
`download()` as a way of making that virtual filesystem _real_.
If the **optional** `meta` property is passed (the argument for [build](#build)), only the files that have changed are downloaded. This is decided using `filesRemoved` and `filesChanged` inside that object. If the **optional** `meta` property is passed (the argument for
[`build()`](#build)), only the files that have changed are downloaded.
This is decided using `filesRemoved` and `filesChanged` inside that object.
```js ```js
await download(files, workPath, meta); await download(files, workPath, meta);
``` ```
### `glob` ### `glob()`
Signature: `glob() : Files` Signature: `glob(): Files`
```ts ```typescript
import { glob } from '@vercel/build-utils'; import { glob } from '@vercel/build-utils';
``` ```
@@ -355,21 +406,21 @@ exports.build = ({ files, workPath }) => {
} }
``` ```
### `getWriteableDirectory` ### `getWritableDirectory()`
Signature: `getWriteableDirectory() : String` Signature: `getWritableDirectory(): String`
```ts ```typescript
import { getWriteableDirectory } from '@vercel/build-utils'; import { getWritableDirectory } from '@vercel/build-utils';
``` ```
In some occasions, you might want to write to a temporary directory. In some occasions, you might want to write to a temporary directory.
### `rename` ### `rename()`
Signature: `rename(Files) : Files` Signature: `rename(Files, Function): Files`
```ts ```typescript
import { rename } from '@vercel/build-utils'; import { rename } from '@vercel/build-utils';
``` ```

View File

@@ -1,4 +1,4 @@
import { NowRequest, NowResponse } from '@now/node'; import { NowRequest, NowResponse } from '@vercel/node';
import { errorHandler } from './error-handler'; import { errorHandler } from './error-handler';
type Handler = (req: NowRequest, res: NowResponse) => Promise<any>; type Handler = (req: NowRequest, res: NowResponse) => Promise<any>;

View File

@@ -2,7 +2,7 @@ import fs from 'fs';
// @ts-ignore // @ts-ignore
import tar from 'tar-fs'; import tar from 'tar-fs';
import { extract } from '../../_lib/examples/extract'; import { extract } from '../../_lib/examples/extract';
import { NowRequest, NowResponse } from '@now/node'; import { NowRequest, NowResponse } from '@vercel/node';
import { withApiHandler } from '../../_lib/util/with-api-handler'; import { withApiHandler } from '../../_lib/util/with-api-handler';
const TMP_DIR = '/tmp'; const TMP_DIR = '/tmp';
@@ -15,8 +15,8 @@ function notFound(res: NowResponse, message: string) {
return res.status(404).send({ return res.status(404).send({
error: { error: {
code: 'not_found', code: 'not_found',
message message,
} },
}); });
} }
@@ -35,7 +35,10 @@ function streamToBuffer(stream: any) {
}); });
} }
export default withApiHandler(async function(req: NowRequest, res: NowResponse) { export default withApiHandler(async function (
req: NowRequest,
res: NowResponse
) {
const ext = '.tar.gz'; const ext = '.tar.gz';
const { segment = '' } = req.query; const { segment = '' } = req.query;

View File

@@ -3,12 +3,12 @@
// @ts-ignore // @ts-ignore
import parseGitUrl from 'parse-github-url'; import parseGitUrl from 'parse-github-url';
import { NowRequest, NowResponse } from '@now/node'; import { NowRequest, NowResponse } from '@vercel/node';
import { withApiHandler } from '../_lib/util/with-api-handler'; import { withApiHandler } from '../_lib/util/with-api-handler';
import { getGitHubRepoInfo } from '../_lib/examples/github-repo-info'; import { getGitHubRepoInfo } from '../_lib/examples/github-repo-info';
import { getGitLabRepoInfo } from '../_lib/examples/gitlab-repo-info'; import { getGitLabRepoInfo } from '../_lib/examples/gitlab-repo-info';
export default withApiHandler(async function( export default withApiHandler(async function (
req: NowRequest, req: NowRequest,
res: NowResponse res: NowResponse
) { ) {

View File

@@ -1,8 +1,8 @@
import { NowRequest, NowResponse } from '@now/node'; import { NowRequest, NowResponse } from '@vercel/node';
import { getExampleList } from '../_lib/examples/example-list'; import { getExampleList } from '../_lib/examples/example-list';
import { withApiHandler } from '../_lib/util/with-api-handler'; import { withApiHandler } from '../_lib/util/with-api-handler';
export default withApiHandler(async function( export default withApiHandler(async function (
req: NowRequest, req: NowRequest,
res: NowResponse res: NowResponse
) { ) {

View File

@@ -1,10 +1,10 @@
import { extract } from '../_lib/examples/extract'; import { extract } from '../_lib/examples/extract';
import { summary } from '../_lib/examples/summary'; import { summary } from '../_lib/examples/summary';
import { NowRequest, NowResponse } from '@now/node'; import { NowRequest, NowResponse } from '@vercel/node';
import { mapOldToNew } from '../_lib/examples/map-old-to-new'; import { mapOldToNew } from '../_lib/examples/map-old-to-new';
import { withApiHandler } from '../_lib/util/with-api-handler'; import { withApiHandler } from '../_lib/util/with-api-handler';
export default withApiHandler(async function( export default withApiHandler(async function (
req: NowRequest, req: NowRequest,
res: NowResponse res: NowResponse
) { ) {

View File

@@ -1,21 +1,27 @@
import { NowRequest, NowResponse } from '@now/node'; import { NowRequest, NowResponse } from '@vercel/node';
import { withApiHandler } from './_lib/util/with-api-handler'; import { withApiHandler } from './_lib/util/with-api-handler';
import frameworkList, { Framework } from '../packages/frameworks'; import _frameworks, { Framework } from '../packages/frameworks';
const frameworks = (frameworkList as Framework[]).map(frameworkItem => { const frameworks = (_frameworks as Framework[])
const framework = { .sort(
...frameworkItem, (a, b) =>
detectors: undefined, (a.sort || Number.MAX_SAFE_INTEGER) - (b.sort || Number.MAX_SAFE_INTEGER)
}; )
.map(frameworkItem => {
const framework = {
...frameworkItem,
detectors: undefined,
sort: undefined,
};
if (framework.logo) { if (framework.logo) {
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`; framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
} }
return framework; return framework;
}); });
export default withApiHandler(async function( export default withApiHandler(async function (
req: NowRequest, req: NowRequest,
res: NowResponse res: NowResponse
) { ) {

View File

@@ -13,12 +13,12 @@
"node-fetch": "2.6.0", "node-fetch": "2.6.0",
"parse-github-url": "1.0.2", "parse-github-url": "1.0.2",
"tar-fs": "2.0.0", "tar-fs": "2.0.0",
"typescript": "3.7.4",
"unzip-stream": "0.3.0" "unzip-stream": "0.3.0"
}, },
"devDependencies": { "devDependencies": {
"@now/node": "1.3.3",
"@types/node": "13.1.4", "@types/node": "13.1.4",
"@types/node-fetch": "2.5.4" "@types/node-fetch": "2.5.4",
"@vercel/node": "1.7.2",
"typescript": "3.9.6"
} }
} }

View File

@@ -2,13 +2,6 @@
# yarn lockfile v1 # yarn lockfile v1
"@now/node@1.3.3":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@now/node/-/node-1.3.3.tgz#5407cb6a730d4dd9b6b6b0bc4a316f29086c9feb"
integrity sha512-s1qajtQttWhhSs1k6FX0/6eTFYFUplzultrQeKfOPMoYzzz6OxDq5qrQ3elpsGlZlDVmO+x+JOJ7yad+3yBgpg==
dependencies:
"@types/node" "*"
"@sentry/apm@5.11.1": "@sentry/apm@5.11.1":
version "5.11.1" version "5.11.1"
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.11.1.tgz#cc89fa4150056fbf009f92eca94fccc3980db34e" resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.11.1.tgz#cc89fa4150056fbf009f92eca94fccc3980db34e"
@@ -141,11 +134,25 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@vercel/node@1.7.2":
version "1.7.2"
resolved "https://registry.yarnpkg.com/@vercel/node/-/node-1.7.2.tgz#85cb8aac661c02dfef6fe752740f5b162e90767b"
integrity sha512-XV5lrLC+K/cxsaFj8H2OoGu1zliOqnxcrOnPInI8HmQjR/Tztt+0nzgpt+7sx8wXcrib0Nu7lK303jP7VjSETw==
dependencies:
"@types/node" "*"
ts-node "8.9.1"
typescript "3.9.3"
agent-base@5: agent-base@5:
version "5.1.1" version "5.1.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
binary@^0.3.0: binary@^0.3.0:
version "0.3.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
@@ -161,6 +168,11 @@ bl@^3.0.0:
dependencies: dependencies:
readable-stream "^3.0.1" readable-stream "^3.0.1"
buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
buffers@~0.1.1: buffers@~0.1.1:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
@@ -229,6 +241,11 @@ defer-to-connect@^1.1.1:
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.1.tgz#88ae694b93f67b81815a2c8c769aef6574ac8f2f" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.1.tgz#88ae694b93f67b81815a2c8c769aef6574ac8f2f"
integrity sha512-J7thop4u3mRTkYRQ+Vpfwy2G5Ehoy82I14+14W4YMDLKdWloI9gSzRbV30s/NckQGVJtPkWNcW4oMAUigTdqiQ== integrity sha512-J7thop4u3mRTkYRQ+Vpfwy2G5Ehoy82I14+14W4YMDLKdWloI9gSzRbV30s/NckQGVJtPkWNcW4oMAUigTdqiQ==
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
duplexer3@^0.1.4: duplexer3@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@@ -313,6 +330,11 @@ lru_map@^0.3.3:
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0= integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
mimic-response@^1.0.0: mimic-response@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
@@ -396,6 +418,19 @@ safe-buffer@~5.2.0:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
source-map-support@^0.5.17:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
string_decoder@^1.1.1: string_decoder@^1.1.1:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@@ -434,6 +469,17 @@ to-readable-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=
ts-node@8.9.1:
version "8.9.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.9.1.tgz#2f857f46c47e91dcd28a14e052482eb14cfd65a5"
integrity sha512-yrq6ODsxEFTLz0R3BX2myf0WBCSQh9A+py8PBo1dCzWIOcvisbyH6akNKqDHMgXePF2kir5mm5JXJTH3OUJYOQ==
dependencies:
arg "^4.1.0"
diff "^4.0.1"
make-error "^1.1.1"
source-map-support "^0.5.17"
yn "3.1.1"
tslib@^1.9.3: tslib@^1.9.3:
version "1.10.0" version "1.10.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
@@ -444,10 +490,15 @@ type-fest@^0.8.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
typescript@3.7.4: typescript@3.9.3:
version "3.7.4" version "3.9.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a"
integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw== integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==
typescript@3.9.6:
version "3.9.6"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a"
integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==
unzip-stream@0.3.0: unzip-stream@0.3.0:
version "0.3.0" version "0.3.0"
@@ -466,3 +517,8 @@ wrappy@1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==

View File

@@ -0,0 +1,4 @@
module.exports = {
presets: ["next/babel"],
plugins: [],
}

View File

@@ -0,0 +1,10 @@
module.exports = {
extends: ["react-app", "plugin:jsx-a11y/recommended"],
plugins: ["jsx-a11y"],
rules: {
"import/no-anonymous-default-export": "error",
"import/no-webpack-loader-syntax": "off",
"react/react-in-jsx-scope": "off", // React is always in scope with Blitz
"jsx-a11y/anchor-is-valid": "off", //Doesn't play well with Blitz/Next <Link> usage
},
}

56
examples/blitzjs/.gitignore vendored Normal file
View File

@@ -0,0 +1,56 @@
# dependencies
node_modules
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.pnp.*
.npm
web_modules/
# blitz
/.blitz/
/.next/
*.sqlite
.now
.vercel
.blitz-console-history
blitz-log.log
# misc
.DS_Store
# local env files
.env
.envrc
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Testing
coverage
*.lcov
.nyc_output
lib-cov
# Caches
*.tsbuildinfo
.eslintcache
.node_repl_history
.yarn-integrity
# Serverless directories
.serverless/
# Stores VSCode versions used for testing VSCode extensions
.vscode-test

1
examples/blitzjs/.npmrc Normal file
View File

@@ -0,0 +1 @@
save-exact=true

View File

@@ -0,0 +1,6 @@
.gitkeep
.env
*.ico
*.lock
db/migrations

View File

@@ -0,0 +1,21 @@
![Blitz Logo](https://github.com/vercel/vercel/blob/master/packages/frameworks/logos/blitz.svg)
This is a [Blitz.js](https://blitzjs.com/) project bootstrapped with `npx blitz new`.
## Getting Started
First, run the development server:
```bash
npx blitz start
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
## Learn More
To learn more about Blitz.js, view [Blitzjs.com](https://blitzjs.com)
## Deploy on Vercel
View the [documentation on deploying to Vercel](https://blitzjs.com/docs/deploy-vercel)

View File

View File

@@ -0,0 +1,21 @@
import React from "react"
export default class ErrorBoundary extends React.Component<{
fallback: (error: any) => React.ReactNode
}> {
state = { hasError: false, error: null }
static getDerivedStateFromError(error: any) {
return {
hasError: true,
error,
}
}
render() {
if (this.state.hasError) {
return this.props.fallback(this.state.error)
}
return this.props.children
}
}

View File

View File

@@ -0,0 +1,5 @@
import { AppProps } from "blitz"
export default function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}

View File

@@ -0,0 +1,23 @@
import { Document, Html, DocumentHead, Main, BlitzScript /*DocumentContext*/ } from "blitz"
class MyDocument extends Document {
// Only uncomment if you need to customize this behaviour
// static async getInitialProps(ctx: DocumentContext) {
// const initialProps = await Document.getInitialProps(ctx)
// return {...initialProps}
// }
render() {
return (
<Html lang="en">
<DocumentHead />
<body>
<Main />
<BlitzScript />
</body>
</Html>
)
}
}
export default MyDocument

View File

@@ -0,0 +1,197 @@
import { Head, Link } from "blitz"
const Home = () => (
<div className="container">
<Head>
<title>blitzjs</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<div className="logo">
<img src="/logo.png" alt="blitz.js" />
</div>
<p>1. Run this command in your terminal:</p>
<pre>
<code>blitz generate all project name:string</code>
</pre>
<p>2. Then run this command:</p>
<pre>
<code>blitz db migrate</code>
</pre>
<p>
3. Go to{" "}
<Link href="/projects">
<a>/projects</a>
</Link>
</p>
<div className="buttons">
<a
className="button"
href="https://github.com/blitz-js/blitz/blob/master/USER_GUIDE.md?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
<a
className="button-outline"
href="https://github.com/blitz-js/blitz"
target="_blank"
rel="noopener noreferrer"
>
Github Repo
</a>
<a
className="button-outline"
href="https://slack.blitzjs.com"
target="_blank"
rel="noopener noreferrer"
>
Slack Community
</a>
</div>
</main>
<footer>
<a
href="https://blitzjs.com?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
target="_blank"
rel="noopener noreferrer"
>
Powered by Blitz.js
</a>
</footer>
<style jsx>{`
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
main {
padding: 5rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
main p {
font-size: 1.2rem;
}
footer {
width: 100%;
height: 60px;
border-top: 1px solid #eaeaea;
display: flex;
justify-content: center;
align-items: center;
background-color: #45009d;
}
footer a {
display: flex;
justify-content: center;
align-items: center;
}
footer a {
color: #f4f4f4;
text-decoration: none;
}
.logo {
margin-bottom: 2rem;
}
.logo img {
width: 300px;
}
.buttons {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 0.5rem;
margin-top: 6rem;
}
a.button {
background-color: #6700eb;
padding: 1rem 2rem;
color: #f4f4f4;
text-align: center;
}
a.button:hover {
background-color: #45009d;
}
a.button-outline {
border: 2px solid #6700eb;
padding: 1rem 2rem;
color: #6700eb;
text-align: center;
}
a.button-outline:hover {
border-color: #45009d;
color: #45009d;
}
pre {
background: #fafafa;
border-radius: 5px;
padding: 0.75rem;
}
code {
font-size: 0.9rem;
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}
.grid {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
max-width: 800px;
margin-top: 3rem;
}
@media (max-width: 600px) {
.grid {
width: 100%;
flex-direction: column;
}
}
`}</style>
<style jsx global>{`
@import url("https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;700&display=swap");
html,
body {
padding: 0;
margin: 0;
font-family: "Libre Franklin", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
box-sizing: border-box;
}
`}</style>
</div>
)
export default Home

View File

@@ -0,0 +1,15 @@
module.exports = {
/*
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
// Note: we provide webpack above so you should not `require` it
// Perform customizations to webpack config
// Important: return the modified config
return config
},
webpackDevMiddleware: (config) => {
// Perform customizations to webpack dev middleware config
// Important: return the modified config
return config
},
*/
}

View File

@@ -0,0 +1,15 @@
import { PrismaClient } from "@prisma/client"
export * from "@prisma/client"
let prisma: PrismaClient
if (process.env.NODE_ENV === "production") {
prisma = new PrismaClient()
} else {
// Ensure the prisma instance is re-used during hot-reloading
// Otherwise, a new client will be created on every reload
global["prisma"] = global["prisma"] || new PrismaClient()
prisma = global["prisma"]
}
export default prisma

View File

View File

@@ -0,0 +1,27 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource sqlite {
provider = "sqlite"
url = "file:./db.sqlite"
}
// SQLite is easy to start with, but if you use Postgres in production
// you should also use it in development with the following:
//datasource postgresql {
// provider = "postgresql"
// url = env("DATABASE_URL")
//}
generator client {
provider = "prisma-client-js"
}
// --------------------------------------
//model Project {
// id Int @default(autoincrement()) @id
// name String
//}

View File

View File

@@ -0,0 +1,55 @@
{
"name": "blitzjs",
"version": "1.0.0",
"scripts": {
"start": "blitz start",
"studio": "blitz db studio",
"build": "blitz build",
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
"test": "echo \"No tests yet\""
},
"browserslist": [
"defaults"
],
"prettier": {
"semi": false,
"printWidth": 100
},
"husky": {
"hooks": {
"pre-commit": "lint-staged && pretty-quick --staged",
"pre-push": "blitz test"
}
},
"lint-staged": {
"*.{js,ts,tsx}": [
"eslint --fix"
]
},
"dependencies": {
"@prisma/cli": "latest",
"@prisma/client": "latest",
"blitz": "latest",
"react": "experimental",
"react-dom": "experimental"
},
"devDependencies": {
"@types/react": "16.9.36",
"@typescript-eslint/eslint-plugin": "2.x",
"@typescript-eslint/parser": "2.x",
"babel-eslint": "10.1.0",
"eslint": "6.8.0",
"eslint-config-react-app": "5.2.1",
"eslint-plugin-flowtype": "4.7.0",
"eslint-plugin-import": "2.21.2",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-react": "7.20.0",
"eslint-plugin-react-hooks": "3.0.0",
"husky": "4.2.5",
"lint-staged": "10.2.10",
"prettier": "2.0.5",
"pretty-quick": "2.0.1",
"typescript": "3.9.5"
},
"private": true
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"baseUrl": "./",
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}

View File

11913
examples/gatsby/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,7 @@
"@zeit/ncc": "0.20.4", "@zeit/ncc": "0.20.4",
"async-retry": "1.2.3", "async-retry": "1.2.3",
"buffer-replace": "1.0.0", "buffer-replace": "1.0.0",
"cheerio": "1.0.0-rc.3",
"eslint": "6.2.2", "eslint": "6.2.2",
"eslint-config-prettier": "6.1.0", "eslint-config-prettier": "6.1.0",
"eslint-plugin-jest": "23.8.2", "eslint-plugin-jest": "23.8.2",
@@ -28,7 +29,7 @@
"lint-staged": "9.2.5", "lint-staged": "9.2.5",
"node-fetch": "2.6.0", "node-fetch": "2.6.0",
"npm-package-arg": "6.1.0", "npm-package-arg": "6.1.0",
"prettier": "1.18.2" "prettier": "2.0.5"
}, },
"scripts": { "scripts": {
"lerna": "lerna", "lerna": "lerna",
@@ -63,7 +64,8 @@
}, },
"prettier": { "prettier": {
"trailingComma": "es5", "trailingComma": "es5",
"singleQuote": true "singleQuote": true,
"arrowParens": "avoid"
}, },
"eslintConfig": { "eslintConfig": {
"root": true, "root": true,

View File

@@ -1,12 +1,41 @@
[ [
{
"name": "Blitz.js",
"slug": "blitzjs",
"demo": "https://blitzjs.now-examples.now.sh",
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/blitz.svg",
"tagline": "Blitz.js: The Fullstack React Framework",
"description": "A brand new Blitz.js app - the result of running `npx blitz new`.",
"website": "https://blitzjs.com",
"detectors": {
"every": [
{
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"blitz\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`npm run build` or `blitz build`"
},
"devCommand": {
"value": "blitz start"
},
"outputDirectory": {
"placeholder": "Next.js default"
}
}
},
{ {
"name": "Next.js", "name": "Next.js",
"slug": "nextjs", "slug": "nextjs",
"demo": "https://nextjs.now-examples.now.sh", "demo": "https://nextjs.now-examples.now.sh",
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/next.svg", "logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/next.svg",
"tagline": "Next.js makes you productive with React instantly — whether you want to build static or dynamic sites. ", "tagline": "Next.js makes you productive with React instantly — whether you want to build static or dynamic sites.",
"description": "A Next.js app and a Serverless Function API.", "description": "A Next.js app and a Serverless Function API.",
"website": "https://nextjs.org", "website": "https://nextjs.org",
"sort": 1,
"detectors": { "detectors": {
"every": [ "every": [
{ {
@@ -35,6 +64,7 @@
"tagline": "Gatsby helps developers build blazing fast websites and apps with React.", "tagline": "Gatsby helps developers build blazing fast websites and apps with React.",
"description": "A Gatsby app, using the default starter theme and a Serverless Function API.", "description": "A Gatsby app, using the default starter theme and a Serverless Function API.",
"website": "https://gatsbyjs.org", "website": "https://gatsbyjs.org",
"sort": 2,
"detectors": { "detectors": {
"every": [ "every": [
{ {
@@ -62,7 +92,8 @@
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/hexo.svg", "logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/hexo.svg",
"tagline": "Hexo is a fast, simple & powerful blog framework powered by Node.js.", "tagline": "Hexo is a fast, simple & powerful blog framework powered by Node.js.",
"description": "A Hexo site, created with the Hexo CLI.", "description": "A Hexo site, created with the Hexo CLI.",
"website": "https://hexo.io/", "website": "https://hexo.io",
"sort": 3,
"detectors": { "detectors": {
"every": [ "every": [
{ {
@@ -90,7 +121,8 @@
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/eleventy.svg", "logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/eleventy.svg",
"tagline": "11ty is a simpler static site generator written in JavaScript, created to be an alternative to Jekyll.", "tagline": "11ty is a simpler static site generator written in JavaScript, created to be an alternative to Jekyll.",
"description": "An Eleventy site, created with npm init.", "description": "An Eleventy site, created with npm init.",
"website": "https://www.11ty.dev/", "website": "https://www.11ty.dev",
"sort": 4,
"detectors": { "detectors": {
"every": [ "every": [
{ {
@@ -634,13 +666,13 @@
"every": [ "every": [
{ {
"path": "package.json", "path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"nuxt\":\\s*\".+?\"[^}]*}" "matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"nuxt(-edge)?\":\\s*\".+?\"[^}]*}"
} }
] ]
}, },
"settings": { "settings": {
"buildCommand": { "buildCommand": {
"placeholder": "`npm run build` or `nuxt build`" "placeholder": "`npm run build` or `nuxt generate`"
}, },
"devCommand": { "devCommand": {
"value": "nuxt" "value": "nuxt"
@@ -658,6 +690,7 @@
"tagline": "Hugo is the worlds fastest framework for building websites, written in Go.", "tagline": "Hugo is the worlds fastest framework for building websites, written in Go.",
"description": "A Hugo site, created with the Hugo CLI.", "description": "A Hugo site, created with the Hugo CLI.",
"website": "https://gohugo.io", "website": "https://gohugo.io",
"sort": 5,
"detectors": { "detectors": {
"some": [ "some": [
{ {

View File

@@ -19,6 +19,7 @@ export interface Framework {
tagline?: string; tagline?: string;
website?: string; website?: string;
description: string; description: string;
sort?: number;
detectors?: { detectors?: {
every?: FrameworkDetectionItem[]; every?: FrameworkDetectionItem[];
some?: FrameworkDetectionItem[]; some?: FrameworkDetectionItem[];

View File

@@ -0,0 +1,30 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M95.4242 249.857H173.991C203.89 249.857 232.049 263.909 250.026 287.799L327.526 390.789C328.991 392.736 329.212 395.349 328.095 397.513L283.421 484.069C281.278 488.221 275.532 488.71 272.719 484.978L95.4242 249.857Z" fill="url(#paint0_linear)"/>
<g filter="url(#filter0_d)">
<path d="M404.558 249.991H325.991C296.093 249.991 267.933 235.939 249.956 212.049L172.456 109.059C170.991 107.112 170.771 104.499 171.888 102.335L216.561 15.7794C218.705 11.6267 224.45 11.1382 227.264 14.8695L404.558 249.991Z" fill="url(#paint1_linear)"/>
</g>
</g>
<defs>
<filter id="filter0_d" x="71.1812" y="-39.6553" width="433.377" height="437.646" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="48"/>
<feGaussianBlur stdDeviation="50"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.270588 0 0 0 0 0 0 0 0 0 0.615686 0 0 0 0.2 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<linearGradient id="paint0_linear" x1="163.936" y1="392.775" x2="316.429" y2="155.244" gradientUnits="userSpaceOnUse">
<stop stop-color="#6700EB"/>
<stop offset="1" stop-color="#45009D"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="336.047" y1="107.073" x2="183.554" y2="344.604" gradientUnits="userSpaceOnUse">
<stop stop-color="#6700EB"/>
<stop offset="1" stop-color="#45009D"/>
</linearGradient>
<clipPath id="clip0">
<rect width="500" height="500" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/frameworks", "name": "@vercel/frameworks",
"version": "0.0.15", "version": "0.0.17",
"main": "frameworks.json", "main": "frameworks.json",
"license": "UNLICENSED", "license": "UNLICENSED",
"scripts": { "scripts": {

View File

@@ -1,11 +1,9 @@
import Ajv from 'ajv'; import Ajv from 'ajv';
import path from 'path'; import { join } from 'path';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { isString } from 'util';
import { Framework } from '../'; import { Framework } from '../';
const frameworkList = require('../frameworks.json') as Framework[];
function isString(arg: any): arg is string {
return typeof arg === 'string';
}
const SchemaFrameworkDetectionItem = { const SchemaFrameworkDetectionItem = {
type: 'array', type: 'array',
@@ -60,6 +58,7 @@ const Schema = {
properties: { properties: {
name: { type: 'string' }, name: { type: 'string' },
slug: { type: ['string', 'null'] }, slug: { type: ['string', 'null'] },
sort: { type: 'number' },
logo: { type: 'string' }, logo: { type: 'string' },
demo: { type: 'string' }, demo: { type: 'string' },
tagline: { type: 'string' }, tagline: { type: 'string' },
@@ -89,12 +88,10 @@ const Schema = {
describe('frameworks', () => { describe('frameworks', () => {
it('ensure there is an example for every framework', async () => { it('ensure there is an example for every framework', async () => {
const root = path.join(__dirname, '..', '..', '..'); const root = join(__dirname, '..', '..', '..');
const getExample = (name: string) => path.join(root, 'examples', name); const getExample = (name: string) => join(root, 'examples', name);
const frameworks = require('../frameworks.json') as Framework[]; const result = frameworkList
const result = frameworks
.map(f => f.slug) .map(f => f.slug)
.filter(isString) .filter(isString)
.filter(f => existsSync(getExample(f)) === false); .filter(f => existsSync(getExample(f)) === false);
@@ -103,10 +100,8 @@ describe('frameworks', () => {
}); });
it('ensure schema', async () => { it('ensure schema', async () => {
const frameworks = require('../frameworks.json') as Framework[];
const ajv = new Ajv(); const ajv = new Ajv();
const result = ajv.validate(Schema, frameworks); const result = ajv.validate(Schema, frameworkList);
if (ajv.errors) { if (ajv.errors) {
console.error(ajv.errors); console.error(ajv.errors);
@@ -116,17 +111,26 @@ describe('frameworks', () => {
}); });
it('ensure logo', async () => { it('ensure logo', async () => {
const frameworks = require('../frameworks.json') as Framework[]; const missing = frameworkList
const missing = frameworks
.map(f => f.logo) .map(f => f.logo)
.filter(url => { .filter(url => {
const prefix = const prefix =
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/'; 'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/';
const name = url.replace(prefix, ''); const name = url.replace(prefix, '');
return existsSync(path.join(__dirname, '..', 'logos', name)) === false; return existsSync(join(__dirname, '..', 'logos', name)) === false;
}); });
expect(missing).toEqual([]); expect(missing).toEqual([]);
}); });
it('ensure unique sort number', async () => {
const sortNumToSlug = new Map<number, string | null>();
frameworkList.forEach(f => {
if (f.sort) {
const duplicateSlug = sortNumToSlug.get(f.sort);
expect(duplicateSlug).toStrictEqual(undefined);
sortNumToSlug.set(f.sort, f.slug);
}
});
});
}); });

View File

@@ -1,5 +1,6 @@
#!/bin/sh #!/bin/bash
set -eu set -euo pipefail
out="dist" out="dist"
rm -rf "$out" rm -rf "$out"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/build-utils", "name": "@vercel/build-utils",
"version": "2.4.0", "version": "2.4.2",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.js", "types": "./dist/index.d.js",

View File

@@ -8,6 +8,8 @@ import { isOfficialRuntime } from './';
interface ErrorResponse { interface ErrorResponse {
code: string; code: string;
message: string; message: string;
action?: string;
link?: string;
} }
interface Options { interface Options {
@@ -462,7 +464,7 @@ function detectFrontBuilder(
}); });
} }
if (framework === 'nextjs') { if (framework === 'nextjs' || framework === 'blitzjs') {
return { src: 'package.json', use: `@vercel/next${withTag}`, config }; return { src: 'package.json', use: `@vercel/next${withTag}`, config };
} }
@@ -496,7 +498,7 @@ function getMissingBuildScriptError() {
code: 'missing_build_script', code: 'missing_build_script',
message: message:
'Your `package.json` file is missing a `build` property inside the `scripts` property.' + 'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
'\nMore details: https://vercel.com/docs/v2/platform/frequently-asked-questions#missing-build-script', '\nLearn More: https://vercel.com/docs/v2/platform/frequently-asked-questions#missing-build-script',
}; };
} }
@@ -608,20 +610,22 @@ function checkUnusedFunctions(
} else { } else {
return { return {
code: 'unused_function', code: 'unused_function',
message: `The function for ${fnKey} can't be handled by any builder`, message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions.`,
action: 'Learn More',
link: 'https://vercel.link/unmatched-function-pattern',
}; };
} }
} }
} }
if (unusedFunctions.size) { if (unusedFunctions.size) {
const [unusedFunction] = Array.from(unusedFunctions); const [fnKey] = Array.from(unusedFunctions);
return { return {
code: 'unused_function', code: 'unused_function',
message: message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
`The function for ${unusedFunction} can't be handled by any builder. ` + action: 'Learn More',
`Make sure it is inside the api/ directory.`, link: 'https://vercel.link/unmatched-function-pattern',
}; };
} }

View File

@@ -34,7 +34,82 @@ interface Props {
*/ */
link?: string; link?: string;
/** /**
* Optional "action" to display before the `link`, such as "More details". * Optional "action" to display before the `link`, such as "Learn More".
*/ */
action?: string; action?: string;
} }
export function getPrettyError(obj: {
dataPath?: string;
message?: string;
params: any;
}): NowBuildError {
const docsUrl = 'https://vercel.com/docs/configuration';
try {
const { dataPath, params, message: ajvMessage } = obj;
const prop = getTopLevelPropertyName(dataPath);
let message =
dataPath && dataPath.startsWith('.') ? `\`${dataPath.slice(1)}\` ` : '';
if (params && typeof params.additionalProperty === 'string') {
const suggestion = getSuggestion(prop, params.additionalProperty);
message += `should NOT have additional property \`${params.additionalProperty}\`. ${suggestion}`;
} else if (params && typeof params.missingProperty === 'string') {
message += `missing required property \`${params.missingProperty}\`.`;
} else {
message += `${ajvMessage}.`;
}
return new NowBuildError({
code: 'DEV_VALIDATE_CONFIG',
message: message,
link: prop ? `${docsUrl}#project/${prop.toLowerCase()}` : docsUrl,
action: 'View Documentation',
});
} catch (e) {
return new NowBuildError({
code: 'DEV_VALIDATE_CONFIG',
message: `Failed to validate configuration.`,
link: docsUrl,
action: 'View Documentation',
});
}
}
/**
* Get the top level property from the dataPath.
* `.cleanUrls` => `cleanUrls`
* `.headers[0].source` => `headers`
* `.headers[0].headers[0]` => `headers`
* `` => ``
*/
function getTopLevelPropertyName(dataPath?: string): string {
if (dataPath && dataPath.startsWith('.')) {
const lastIndex = dataPath.indexOf('[');
return lastIndex > -1 ? dataPath.slice(1, lastIndex) : dataPath.slice(1);
}
return '';
}
const mapTypoToSuggestion: { [key: string]: { [key: string]: string } } = {
'': {
builder: 'builds',
'build.env': '{ "build": { "env": {"name": "value"} } }',
'builds.env': '{ "build": { "env": {"name": "value"} } }',
},
rewrites: { src: 'source', dest: 'destination' },
redirects: { src: 'source', dest: 'destination', status: 'statusCode' },
headers: { src: 'source', header: 'headers' },
routes: {
source: 'src',
destination: 'dest',
header: 'headers',
method: 'methods',
},
};
function getSuggestion(topLevelProp: string, additionalProperty: string) {
const choices = mapTypoToSuggestion[topLevelProp];
const choice = choices ? choices[additionalProperty] : undefined;
return choice ? `Did you mean \`${choice}\`?` : 'Please remove it.';
}

View File

@@ -349,20 +349,35 @@ export async function runPipInstall(
); );
} }
export function getScriptName(
pkg: Pick<PackageJson, 'scripts'> | null | undefined,
possibleNames: Iterable<string>
): string | null {
if (pkg && pkg.scripts) {
for (const name of possibleNames) {
if (name in pkg.scripts) {
return name;
}
}
}
return null;
}
export async function runPackageJsonScript( export async function runPackageJsonScript(
destPath: string, destPath: string,
scriptName: string, scriptNames: string | Iterable<string>,
spawnOpts?: SpawnOptions spawnOpts?: SpawnOptions
) { ) {
assert(path.isAbsolute(destPath)); assert(path.isAbsolute(destPath));
const { packageJson, cliType } = await scanParentDirs(destPath, true); const { packageJson, cliType } = await scanParentDirs(destPath, true);
const hasScript = Boolean( const scriptName = getScriptName(
packageJson && packageJson,
packageJson.scripts && typeof scriptNames === 'string' ? [scriptNames] : scriptNames
scriptName &&
packageJson.scripts[scriptName]
); );
if (!hasScript) return false; if (!scriptName) return false;
debug('Running user script...');
const runScriptTime = Date.now();
if (cliType === 'npm') { if (cliType === 'npm') {
const prettyCommand = `npm run ${scriptName}`; const prettyCommand = `npm run ${scriptName}`;
@@ -382,6 +397,7 @@ export async function runPackageJsonScript(
}); });
} }
debug(`Script complete [${Date.now() - runScriptTime}ms]`);
return true; return true;
} }

View File

@@ -13,6 +13,7 @@ import {
execCommand, execCommand,
spawnCommand, spawnCommand,
walkParentDirs, walkParentDirs,
getScriptName,
installDependencies, installDependencies,
runPackageJsonScript, runPackageJsonScript,
runNpmInstall, runNpmInstall,
@@ -47,6 +48,7 @@ export {
rename, rename,
execAsync, execAsync,
spawnAsync, spawnAsync,
getScriptName,
installDependencies, installDependencies,
runPackageJsonScript, runPackageJsonScript,
execCommand, execCommand,

View File

@@ -493,7 +493,7 @@ describe('Test `detectBuilders`', () => {
}); });
it('use a custom runtime', async () => { it('use a custom runtime', async () => {
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } }; const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
const files = ['api/user.php']; const files = ['api/user.php'];
const { builders, errors } = await detectBuilders(files, null, { const { builders, errors } = await detectBuilders(files, null, {
functions, functions,
@@ -501,11 +501,11 @@ describe('Test `detectBuilders`', () => {
expect(errors).toBe(null); expect(errors).toBe(null);
expect(builders!.length).toBe(1); expect(builders!.length).toBe(1);
expect(builders![0].use).toBe('now-php@0.0.8'); expect(builders![0].use).toBe('vercel-php@0.1.0');
}); });
it('use a custom runtime but without a source', async () => { it('use a custom runtime but without a source', async () => {
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } }; const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
const files = ['api/team.js']; const files = ['api/team.js'];
const { errors } = await detectBuilders(files, null, { const { errors } = await detectBuilders(files, null, {
functions, functions,
@@ -775,8 +775,9 @@ describe('Test `detectBuilders`', () => {
expect(errors).toEqual([ expect(errors).toEqual([
{ {
code: 'unused_function', code: 'unused_function',
message: message: `The pattern "server/**/*.ts" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
"The function for server/**/*.ts can't be handled by any builder. Make sure it is inside the api/ directory.", action: 'Learn More',
link: 'https://vercel.link/unmatched-function-pattern',
}, },
]); ]);
}); });
@@ -1538,7 +1539,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
}); });
it('use a custom runtime', async () => { it('use a custom runtime', async () => {
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } }; const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
const files = ['api/user.php']; const files = ['api/user.php'];
const { builders, errors } = await detectBuilders(files, null, { const { builders, errors } = await detectBuilders(files, null, {
functions, functions,
@@ -1547,11 +1548,11 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(errors).toBe(null); expect(errors).toBe(null);
expect(builders!.length).toBe(1); expect(builders!.length).toBe(1);
expect(builders![0].use).toBe('now-php@0.0.8'); expect(builders![0].use).toBe('vercel-php@0.1.0');
}); });
it('use a custom runtime but without a source', async () => { it('use a custom runtime but without a source', async () => {
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } }; const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
const files = ['api/team.js']; const files = ['api/team.js'];
const { errors } = await detectBuilders(files, null, { const { errors } = await detectBuilders(files, null, {
functions, functions,
@@ -1878,8 +1879,9 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(errors).toEqual([ expect(errors).toEqual([
{ {
code: 'unused_function', code: 'unused_function',
message: message: `The pattern "server/**/*.ts" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
"The function for server/**/*.ts can't be handled by any builder. Make sure it is inside the api/ directory.", action: 'Learn More',
link: 'https://vercel.link/unmatched-function-pattern',
}, },
]); ]);
}); });
@@ -2081,7 +2083,7 @@ it('Test `detectRoutes`', async () => {
{ {
// use a custom runtime // use a custom runtime
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } }; const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
const files = ['api/user.php']; const files = ['api/user.php'];
const { defaultRoutes } = await detectBuilders(files, null, { functions }); const { defaultRoutes } = await detectBuilders(files, null, { functions });
@@ -2393,7 +2395,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
{ {
// use a custom runtime // use a custom runtime
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } }; const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
const files = ['api/user.php']; const files = ['api/user.php'];
const { defaultRoutes, rewriteRoutes } = await detectBuilders(files, null, { const { defaultRoutes, rewriteRoutes } = await detectBuilders(files, null, {
@@ -2692,7 +2694,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
{ {
// use a custom runtime // use a custom runtime
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } }; const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
const files = ['api/user.php']; const files = ['api/user.php'];
const { const {
@@ -2941,7 +2943,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
{ {
// use a custom runtime // use a custom runtime
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } }; const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
const files = ['api/user.php']; const files = ['api/user.php'];
const { const {

View File

@@ -0,0 +1,72 @@
import assert from 'assert';
import { getScriptName } from '../src';
describe('Test `getScriptName()`', () => {
it('should return "vercel-*"', () => {
const pkg = {
scripts: {
'vercel-dev': '',
'vercel-build': '',
dev: '',
build: '',
},
};
assert.equal(
getScriptName(pkg, ['vercel-dev', 'now-dev', 'dev']),
'vercel-dev'
);
assert.equal(
getScriptName(pkg, ['vercel-build', 'now-build', 'build']),
'vercel-build'
);
assert.equal(getScriptName(pkg, ['dev']), 'dev');
assert.equal(getScriptName(pkg, ['build']), 'build');
});
it('should return "now-*"', () => {
const pkg = {
scripts: {
'now-dev': '',
'now-build': '',
dev: '',
build: '',
},
};
assert.equal(
getScriptName(pkg, ['vercel-dev', 'now-dev', 'dev']),
'now-dev'
);
assert.equal(
getScriptName(pkg, ['vercel-build', 'now-build', 'build']),
'now-build'
);
assert.equal(getScriptName(pkg, ['dev']), 'dev');
assert.equal(getScriptName(pkg, ['build']), 'build');
});
it('should return base script name', () => {
const pkg = {
scripts: {
dev: '',
build: '',
},
};
assert.equal(getScriptName(pkg, ['dev']), 'dev');
assert.equal(getScriptName(pkg, ['build']), 'build');
});
it('should return `null`', () => {
assert.equal(getScriptName(undefined, ['build']), null);
assert.equal(getScriptName({}, ['build']), null);
assert.equal(getScriptName({ scripts: {} }, ['build']), null);
const pkg = {
scripts: {
dev: '',
build: '',
},
};
assert.equal(getScriptName(pkg, ['vercel-dev', 'now-dev']), null);
assert.equal(getScriptName(pkg, ['vercel-build', 'now-build']), null);
});
});

View File

@@ -1,9 +1,9 @@
{ {
"name": "vercel", "name": "vercel",
"version": "19.1.0", "version": "19.2.0",
"preferGlobal": true, "preferGlobal": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "The command-line interface for Now", "description": "The command-line interface for Vercel",
"homepage": "https://vercel.com", "homepage": "https://vercel.com",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -12,7 +12,7 @@
}, },
"scripts": { "scripts": {
"preinstall": "node ./scripts/preinstall.js", "preinstall": "node ./scripts/preinstall.js",
"test-unit": "nyc ava test/unit.js test/dev-builder.unit.js test/dev-router.unit.js test/dev-server.unit.js --serial --fail-fast --verbose", "test-unit": "nyc ava test/unit.js test/dev-builder.unit.js test/dev-router.unit.js test/dev-server.unit.js test/dev-validate.unit.js --serial --fail-fast --verbose",
"test-integration-cli": "ava test/integration.js --serial --fail-fast --verbose", "test-integration-cli": "ava test/integration.js --serial --fail-fast --verbose",
"test-integration-v1": "ava test/integration-v1.js --serial --fail-fast", "test-integration-v1": "ava test/integration-v1.js --serial --fail-fast",
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose", "test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
@@ -62,13 +62,14 @@
"node": ">= 10" "node": ">= 10"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "2.4.0", "@vercel/build-utils": "2.4.2",
"@vercel/go": "1.1.2", "@vercel/go": "1.1.4",
"@vercel/next": "2.6.6", "@vercel/next": "2.6.13",
"@vercel/node": "1.7.0", "@vercel/node": "1.7.3",
"@vercel/python": "1.2.2", "@vercel/python": "1.2.2",
"@vercel/ruby": "1.2.2", "@vercel/ruby": "1.2.3",
"@vercel/static-build": "0.17.2" "@vercel/static-build": "0.17.6",
"update-notifier": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@sentry/node": "5.5.0", "@sentry/node": "5.5.0",
@@ -185,7 +186,6 @@
"ts-node": "8.3.0", "ts-node": "8.3.0",
"typescript": "3.9.3", "typescript": "3.9.3",
"universal-analytics": "0.4.20", "universal-analytics": "0.4.20",
"update-check": "1.5.3",
"utility-types": "2.1.0", "utility-types": "2.1.0",
"which": "2.0.2", "which": "2.0.2",
"which-promise": "1.0.0", "which-promise": "1.0.0",

View File

@@ -49,7 +49,13 @@ async function main() {
// Do the initial `ncc` build // Do the initial `ncc` build
console.log(); console.log();
const src = join(dirRoot, 'src'); const src = join(dirRoot, 'src');
const args = ['@zeit/ncc', 'build', '--source-map']; const args = [
'@zeit/ncc',
'build',
'--source-map',
'--external',
'update-notifier',
];
if (!isDev) { if (!isDev) {
args.push('--minify'); args.push('--minify');
} }
@@ -86,7 +92,7 @@ async function main() {
// A bunch of source `.ts` files from CLI's `util` directory // A bunch of source `.ts` files from CLI's `util` directory
await remove(join(dirRoot, 'dist', 'util')); await remove(join(dirRoot, 'dist', 'util'));
console.log('Finished building `now-cli`'); console.log('Finished building Vercel CLI');
} }
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => { process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {

View File

@@ -3,8 +3,8 @@ import bytes from 'bytes';
import { join } from 'path'; import { join } from 'path';
import { write as copy } from 'clipboardy'; import { write as copy } from 'clipboardy';
import chalk from 'chalk'; import chalk from 'chalk';
import title from 'title';
import { fileNameSymbol } from '@vercel/client'; import { fileNameSymbol } from '@vercel/client';
import { getPrettyError } from '@vercel/build-utils';
import Client from '../../util/client'; import Client from '../../util/client';
import { handleError } from '../../util/error'; import { handleError } from '../../util/error';
import getArgs from '../../util/get-args'; import getArgs from '../../util/get-args';
@@ -354,7 +354,7 @@ export default async function main(
path, path,
sourcePath, sourcePath,
project project
? `To change your project settings, go to https://vercel.com/${org.slug}/${project.name}/settings` ? `To change your Project Settings, go to https://vercel.com/${org.slug}/${project.name}/settings`
: '' : ''
)) === false )) === false
) { ) {
@@ -738,53 +738,10 @@ function handleCreateDeployError(output, error, localConfig) {
return 1; return 1;
} }
if (error instanceof SchemaValidationFailed) { if (error instanceof SchemaValidationFailed) {
const { message, params, keyword, dataPath } = error.meta; const niceError = getPrettyError(error.meta);
const fileName = localConfig[fileNameSymbol] || 'vercel.json';
if (params && params.additionalProperty) { niceError.message = `Invalid ${fileName} - ${niceError.message}`;
const prop = params.additionalProperty; output.prettyError(niceError);
output.error(
`The property ${code(prop)} is not allowed in ${highlight(
localConfig[fileNameSymbol]
)} please remove it.`
);
if (prop === 'build.env' || prop === 'builds.env') {
output.note(
`Do you mean ${code('build')} (object) with a property ${code(
'env'
)} (object) instead of ${code(prop)}?`
);
}
return 1;
}
if (dataPath === '.name') {
output.error(message);
return 1;
}
if (keyword === 'type') {
const prop = dataPath.substr(1, dataPath.length);
output.error(
`The property ${code(prop)} in ${highlight(
localConfig[fileNameSymbol]
)} can only be of type ${code(title(params.type))}.`
);
return 1;
}
const link = 'https://vercel.com/docs/configuration';
output.error(
`Failed to validate ${highlight(
localConfig[fileNameSymbol]
)}: ${message}\nDocumentation: ${link}`
);
return 1; return 1;
} }
if (error instanceof TooManyRequests) { if (error instanceof TooManyRequests) {

View File

@@ -9,6 +9,9 @@ import { getLinkedProject } from '../../util/projects/link';
import { getFrameworks } from '../../util/get-frameworks'; import { getFrameworks } from '../../util/get-frameworks';
import { isSettingValue } from '../../util/is-setting-value'; import { isSettingValue } from '../../util/is-setting-value';
import { getCommandName } from '../../util/pkg-name'; import { getCommandName } from '../../util/pkg-name';
import { ProjectSettings, ProjectEnvTarget } from '../../types';
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
import { Env } from '@vercel/build-utils';
type Options = { type Options = {
'--debug'?: boolean; '--debug'?: boolean;
@@ -50,21 +53,27 @@ export default async function dev(
return 1; return 1;
} }
let devCommand: undefined | string; let devCommand: string | undefined;
let frameworkSlug: null | string = null; let frameworkSlug: string | undefined;
let projectSettings: ProjectSettings | undefined;
let environmentVars: Env | undefined;
if (link.status === 'linked') { if (link.status === 'linked') {
const { project, org } = link; const { project, org } = link;
client.currentTeam = org.type === 'team' ? org.id : undefined; client.currentTeam = org.type === 'team' ? org.id : undefined;
projectSettings = project;
if (project.devCommand) { if (project.devCommand) {
devCommand = project.devCommand; devCommand = project.devCommand;
} else if (project.framework) { } else if (project.framework) {
const framework = frameworks.find(f => f.slug === project.framework); const framework = frameworks.find(f => f.slug === project.framework);
if (framework) { if (framework) {
frameworkSlug = framework.slug; if (framework.slug) {
const defaults = framework.settings.devCommand; frameworkSlug = framework.slug;
}
const defaults = framework.settings.devCommand;
if (isSettingValue(defaults)) { if (isSettingValue(defaults)) {
devCommand = defaults.value; devCommand = defaults.value;
} }
@@ -74,6 +83,13 @@ export default async function dev(
if (project.rootDirectory) { if (project.rootDirectory) {
cwd = join(cwd, project.rootDirectory); cwd = join(cwd, project.rootDirectory);
} }
environmentVars = await getDecryptedEnvRecords(
output,
client,
project,
ProjectEnvTarget.Development
);
} }
const devServer = new DevServer(cwd, { const devServer = new DevServer(cwd, {
@@ -81,6 +97,8 @@ export default async function dev(
debug, debug,
devCommand, devCommand,
frameworkSlug, frameworkSlug,
projectSettings,
environmentVars,
}); });
process.once('SIGINT', () => devServer.stop()); process.once('SIGINT', () => devServer.stop());

View File

@@ -10,6 +10,7 @@ import handleError from '../../util/handle-error';
import createOutput from '../../util/output/create-output'; import createOutput from '../../util/output/create-output';
import logo from '../../util/output/logo'; import logo from '../../util/output/logo';
import cmd from '../../util/output/cmd'; import cmd from '../../util/output/cmd';
import highlight from '../../util/output/highlight';
import dev from './dev'; import dev from './dev';
import readPackage from '../../util/read-package'; import readPackage from '../../util/read-package';
import readConfig from '../../util/config/read-config'; import readConfig from '../../util/config/read-config';
@@ -96,7 +97,7 @@ export default async function main(ctx: NowContext) {
'package.json' 'package.json'
)} must not contain ${cmd('now dev')}` )} must not contain ${cmd('now dev')}`
); );
output.error(`More details: http://err.sh/now/now-dev-as-dev-script`); output.error(`Learn More: http://err.sh/now/now-dev-as-dev-script`);
return 1; return 1;
} }
if (scripts && scripts.dev && /\bvercel\b\W+\bdev\b/.test(scripts.dev)) { if (scripts && scripts.dev && /\bvercel\b\W+\bdev\b/.test(scripts.dev)) {
@@ -105,7 +106,7 @@ export default async function main(ctx: NowContext) {
'package.json' 'package.json'
)} must not contain ${cmd('vercel dev')}` )} must not contain ${cmd('vercel dev')}`
); );
output.error(`More details: http://err.sh/now/now-dev-as-dev-script`); output.error(`Learn More: http://err.sh/now/now-dev-as-dev-script`);
return 1; return 1;
} }
} }
@@ -119,6 +120,21 @@ export default async function main(ctx: NowContext) {
try { try {
return await dev(ctx, argv, args, output); return await dev(ctx, argv, args, output);
} catch (err) { } catch (err) {
if (err.code === 'ENOTFOUND') {
// Error message will look like the following:
// "request to https://api.vercel.com/www/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
if (matches && matches[1]) {
const hostname = matches[1];
output.error(
`The hostname ${highlight(
hostname
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
);
}
output.debug(err.stack);
return 1;
}
output.prettyError(err); output.prettyError(err);
output.debug(stringifyError(err)); output.debug(stringifyError(err));
return 1; return 1;

View File

@@ -4,21 +4,44 @@ import { Output } from '../../util/output';
import promptBool from '../../util/prompt-bool'; import promptBool from '../../util/prompt-bool';
import Client from '../../util/client'; import Client from '../../util/client';
import stamp from '../../util/output/stamp'; import stamp from '../../util/output/stamp';
import getEnvVariables from '../../util/env/get-env-records'; import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
import getDecryptedSecret from '../../util/env/get-decrypted-secret';
import param from '../../util/output/param'; import param from '../../util/output/param';
import withSpinner from '../../util/with-spinner'; import withSpinner from '../../util/with-spinner';
import { join } from 'path'; import { join } from 'path';
import { promises, existsSync } from 'fs'; import { promises, openSync, closeSync, readSync } from 'fs';
import { emoji, prependEmoji } from '../../util/emoji'; import { emoji, prependEmoji } from '../../util/emoji';
import { getCommandName } from '../../util/pkg-name'; import { getCommandName } from '../../util/pkg-name';
const { writeFile } = promises; const { writeFile } = promises;
import { Env } from '@vercel/build-utils';
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
type Options = { type Options = {
'--debug': boolean; '--debug': boolean;
'--yes': boolean; '--yes': boolean;
}; };
function readHeadSync(path: string, length: number) {
const buffer = Buffer.alloc(length);
const fd = openSync(path, 'r');
try {
readSync(fd, buffer, 0, buffer.length, null);
} finally {
closeSync(fd);
}
return buffer.toString();
}
function tryReadHeadSync(path: string, length: number) {
try {
return readHeadSync(path, length);
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}
}
export default async function pull( export default async function pull(
client: Client, client: Client,
project: Project, project: Project,
@@ -35,10 +58,14 @@ export default async function pull(
const [filename = '.env'] = args; const [filename = '.env'] = args;
const fullPath = join(process.cwd(), filename); const fullPath = join(process.cwd(), filename);
const exists = existsSync(fullPath);
const skipConfirmation = opts['--yes']; const skipConfirmation = opts['--yes'];
if ( const head = tryReadHeadSync(fullPath, Buffer.byteLength(CONTENTS_PREFIX));
const exists = typeof head !== 'undefined';
if (head === CONTENTS_PREFIX) {
output.print(`Overwriting existing ${chalk.bold(filename)} file\n`);
} else if (
exists && exists &&
!skipConfirmation && !skipConfirmation &&
!(await promptBool( !(await promptBool(
@@ -57,45 +84,23 @@ export default async function pull(
); );
const pullStamp = stamp(); const pullStamp = stamp();
const records = await withSpinner('Downloading', async () => { const records: Env = await withSpinner(
const dev = ProjectEnvTarget.Development; 'Downloading',
const envs = await getEnvVariables(output, client, project.id, 4, dev); async () =>
const decryptedValues = await Promise.all( await getDecryptedEnvRecords(
envs.map(async env => { output,
try { client,
const value = await getDecryptedSecret(output, client, env.value); project,
return { value, found: true }; ProjectEnvTarget.Development
} catch (error) { )
if (error && error.status === 404) { );
return { value: '', found: false };
}
throw error;
}
})
);
const results: { key: string; value: string; found: boolean }[] = [];
for (let i = 0; i < decryptedValues.length; i++) {
const { key } = envs[i];
const { value, found } = decryptedValues[i];
results.push({ key, value, found });
}
return results;
});
const contents = const contents =
records CONTENTS_PREFIX +
.filter(obj => { Object.entries(records)
if (!obj.found) { .map(([key, value]) => `${key}="${escapeValue(value)}"`)
output.print(''); .join('\n') +
output.warn( '\n';
`Unable to download variable ${obj.key} because associated secret was deleted`
);
return false;
}
return true;
})
.map(({ key, value }) => `${key}="${escapeValue(value)}"`)
.join('\n') + '\n';
await writeFile(fullPath, contents, 'utf8'); await writeFile(fullPath, contents, 'utf8');
@@ -110,8 +115,10 @@ export default async function pull(
return 0; return 0;
} }
function escapeValue(value: string) { function escapeValue(value: string | undefined) {
return value return value
.replace(new RegExp('\n', 'g'), '\\n') // combine newlines (unix) into one line ? value
.replace(new RegExp('\r', 'g'), '\\r'); // combine newlines (windows) into one line .replace(new RegExp('\n', 'g'), '\\n') // combine newlines (unix) into one line
.replace(new RegExp('\r', 'g'), '\\r') // combine newlines (windows) into one line
: '';
} }

View File

@@ -20,8 +20,7 @@ import sourceMap from '@zeit/source-map-support';
import { mkdirp } from 'fs-extra'; import { mkdirp } from 'fs-extra';
import chalk from 'chalk'; import chalk from 'chalk';
import epipebomb from 'epipebomb'; import epipebomb from 'epipebomb';
import checkForUpdate from 'update-check'; import updateNotifier from 'update-notifier';
import ms from 'ms';
import { URL } from 'url'; import { URL } from 'url';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import { NowBuildError } from '@vercel/build-utils'; import { NowBuildError } from '@vercel/build-utils';
@@ -46,11 +45,20 @@ import reportError from './util/report-error';
import getConfig from './util/get-config'; import getConfig from './util/get-config';
import * as ERRORS from './util/errors-ts'; import * as ERRORS from './util/errors-ts';
import { NowError } from './util/now-error'; import { NowError } from './util/now-error';
import { APIError } from './util/errors-ts.ts';
import { SENTRY_DSN } from './util/constants.ts'; import { SENTRY_DSN } from './util/constants.ts';
import getUpdateCommand from './util/get-update-command'; import getUpdateCommand from './util/get-update-command';
import { metrics, shouldCollectMetrics } from './util/metrics.ts'; import { metrics, shouldCollectMetrics } from './util/metrics.ts';
import { getCommandName, getTitleName } from './util/pkg-name.ts'; import { getCommandName, getTitleName } from './util/pkg-name.ts';
const isCanary = pkg.version.includes('canary');
// Checks for available update and returns an instance
const notifier = updateNotifier({
pkg,
distTag: isCanary ? 'canary' : 'latest',
});
const VERCEL_DIR = getGlobalPathConfig(); const VERCEL_DIR = getGlobalPathConfig();
const VERCEL_CONFIG_PATH = configFiles.getConfigFilePath(); const VERCEL_CONFIG_PATH = configFiles.getConfigFilePath();
const VERCEL_AUTH_CONFIG_PATH = configFiles.getAuthConfigFilePath(); const VERCEL_AUTH_CONFIG_PATH = configFiles.getAuthConfigFilePath();
@@ -65,7 +73,7 @@ sourceMap.install();
Sentry.init({ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
release: `vercel-cli@${pkg.version}`, release: `vercel-cli@${pkg.version}`,
environment: pkg.version.includes('canary') ? 'canary' : 'stable', environment: isCanary ? 'canary' : 'stable',
}); });
let debug = () => {}; let debug = () => {};
@@ -127,38 +135,20 @@ const main = async argv_ => {
// (as in: `vercel ls`) // (as in: `vercel ls`)
const targetOrSubcommand = argv._[2]; const targetOrSubcommand = argv._[2];
let update = null; if (notifier.update && isTTY) {
const { latest } = notifier.update;
try {
if (targetOrSubcommand !== 'update') {
update = await checkForUpdate(pkg, {
interval: ms('1d'),
distTag: pkg.version.includes('canary') ? 'canary' : 'latest',
});
}
} catch (err) {
console.error(
error(`Checking for updates failed${isDebugging ? ':' : ''}`)
);
if (isDebugging) {
console.error(err);
}
}
if (update && isTTY) {
console.log( console.log(
info( info(
`${chalk.bgRed('UPDATE AVAILABLE')} ` + `${chalk.bgRed('UPDATE AVAILABLE')} ` +
`Run ${cmd( `Run ${cmd(
await getUpdateCommand() await getUpdateCommand()
)} to install ${getTitleName()} CLI ${update.latest}` )} to install ${getTitleName()} CLI ${latest}`
) )
); );
console.log( console.log(
info( info(
`Changelog: https://github.com/vercel/vercel/releases/tag/vercel@${update.latest}` `Changelog: https://github.com/vercel/vercel/releases/tag/vercel@${latest}`
) )
); );
} }
@@ -168,7 +158,7 @@ const main = async argv_ => {
`${getTitleName()} CLI ${pkg.version}${ `${getTitleName()} CLI ${pkg.version}${
targetOrSubcommand === 'dev' ? ' dev (beta)' : '' targetOrSubcommand === 'dev' ? ' dev (beta)' : ''
}${ }${
pkg.version.includes('canary') || targetOrSubcommand === 'dev' isCanary || targetOrSubcommand === 'dev'
? ' — https://vercel.com/feedback' ? ' — https://vercel.com/feedback'
: '' : ''
}` }`
@@ -190,8 +180,7 @@ const main = async argv_ => {
} catch (err) { } catch (err) {
console.error( console.error(
error( error(
`${'An unexpected error occurred while trying to find the ' + `An unexpected error occurred while trying to find the global directory: ${err.message}`
'global directory: '}${err.message}`
) )
); );
@@ -204,8 +193,10 @@ const main = async argv_ => {
} catch (err) { } catch (err) {
console.error( console.error(
error( error(
`${'An unexpected error occurred while trying to create the ' + `${
`global directory "${hp(VERCEL_DIR)}" `}${err.message}` 'An unexpected error occurred while trying to create the ' +
`global directory "${hp(VERCEL_DIR)}" `
}${err.message}`
) )
); );
} }
@@ -219,8 +210,10 @@ const main = async argv_ => {
} catch (err) { } catch (err) {
console.error( console.error(
error( error(
`${'An unexpected error occurred while trying to find the ' + `${
`config file "${hp(VERCEL_CONFIG_PATH)}" `}${err.message}` 'An unexpected error occurred while trying to find the ' +
`config file "${hp(VERCEL_CONFIG_PATH)}" `
}${err.message}`
) )
); );
@@ -235,8 +228,10 @@ const main = async argv_ => {
} catch (err) { } catch (err) {
console.error( console.error(
error( error(
`${'An unexpected error occurred while trying to read the ' + `${
`config in "${hp(VERCEL_CONFIG_PATH)}" `}${err.message}` 'An unexpected error occurred while trying to read the ' +
`config in "${hp(VERCEL_CONFIG_PATH)}" `
}${err.message}`
) )
); );
@@ -267,8 +262,10 @@ const main = async argv_ => {
} catch (err) { } catch (err) {
console.error( console.error(
error( error(
`${'An unexpected error occurred while trying to write the ' + `${
`default config to "${hp(VERCEL_CONFIG_PATH)}" `}${err.message}` 'An unexpected error occurred while trying to write the ' +
`default config to "${hp(VERCEL_CONFIG_PATH)}" `
}${err.message}`
) )
); );
@@ -283,8 +280,10 @@ const main = async argv_ => {
} catch (err) { } catch (err) {
console.error( console.error(
error( error(
`${'An unexpected error occurred while trying to find the ' + `${
`auth file "${hp(VERCEL_AUTH_CONFIG_PATH)}" `}${err.message}` 'An unexpected error occurred while trying to find the ' +
`auth file "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${err.message}`
) )
); );
@@ -301,8 +300,10 @@ const main = async argv_ => {
} catch (err) { } catch (err) {
console.error( console.error(
error( error(
`${'An unexpected error occurred while trying to read the ' + `${
`auth config in "${hp(VERCEL_AUTH_CONFIG_PATH)}" `}${err.message}` 'An unexpected error occurred while trying to read the ' +
`auth config in "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${err.message}`
) )
); );
@@ -314,21 +315,6 @@ const main = async argv_ => {
// need to migrate. // need to migrate.
if (authConfig.credentials) { if (authConfig.credentials) {
authConfigExists = false; authConfigExists = false;
} else if (
!authConfig.token &&
!subcommandsWithoutToken.includes(targetOrSubcommand) &&
!argv['--help'] &&
!argv['--token']
) {
console.error(
error(
`The content of "${hp(VERCEL_AUTH_CONFIG_PATH)}" is invalid. ` +
`No \`token\` property found inside. Run ${getCommandName(
'login'
)} to authorize.`
)
);
return 1;
} }
} else { } else {
const results = await getDefaultAuthConfig(authConfig); const results = await getDefaultAuthConfig(authConfig);
@@ -341,10 +327,10 @@ const main = async argv_ => {
} catch (err) { } catch (err) {
console.error( console.error(
error( error(
`${'An unexpected error occurred while trying to write the ' + `${
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `}${ 'An unexpected error occurred while trying to write the ' +
err.message `default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}` }${err.message}`
) )
); );
return 1; return 1;
@@ -638,16 +624,28 @@ const main = async argv_ => {
.send(); .send();
} }
} catch (err) { } catch (err) {
if (err.code === 'ENOTFOUND' && err.hostname === 'api.vercel.com') { if (err.code === 'ENOTFOUND') {
output.error( // Error message will look like the following:
`The hostname ${highlight( // "request to https://api.vercel.com/www/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
'api.vercel.com' const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
)} could not be resolved. Please verify your internet connectivity and DNS configuration.` if (matches && matches[1]) {
); const hostname = matches[1];
output.error(
`The hostname ${highlight(
hostname
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
);
}
output.debug(err.stack); output.debug(err.stack);
return 1; return 1;
} }
if (err instanceof APIError && 400 <= err.status && err.status <= 499) {
err.message = err.serverMessage;
output.prettyError(err);
return 1;
}
if (shouldCollectMetrics) { if (shouldCollectMetrics) {
metric metric
.event(eventCategory, '1', pkg.version) .event(eventCategory, '1', pkg.version)

View File

@@ -244,15 +244,20 @@ export interface ProjectEnvVariable {
system?: boolean; system?: boolean;
} }
export interface Project { export interface ProjectSettings {
framework?: string | null;
devCommand?: string | null;
buildCommand?: string | null;
outputDirectory?: string | null;
rootDirectory?: string | null;
}
export interface Project extends ProjectSettings {
id: string; id: string;
name: string; name: string;
accountId: string; accountId: string;
updatedAt: number; updatedAt: number;
createdAt: number; createdAt: number;
devCommand?: string | null;
framework?: string | null;
rootDirectory?: string | null;
latestDeployments?: Partial<Deployment>[]; latestDeployments?: Partial<Deployment>[];
} }

View File

@@ -56,6 +56,8 @@ async function createBuildProcess(
workPath: string, workPath: string,
output: Output output: Output
): Promise<ChildProcess> { ): Promise<ChildProcess> {
output.debug(`Creating build process for "${match.entrypoint}"`);
const builderWorkerPath = join(__dirname, 'builder-worker.js'); const builderWorkerPath = join(__dirname, 'builder-worker.js');
// Ensure that `node` is in the builder's `PATH` // Ensure that `node` is in the builder's `PATH`
@@ -65,8 +67,6 @@ async function createBuildProcess(
...process.env, ...process.env,
PATH, PATH,
...envConfigs.allEnv, ...envConfigs.allEnv,
NOW_REGION: 'dev1',
VERCEL_REGION: 'dev1',
}; };
const buildProcess = fork(builderWorkerPath, [], { const buildProcess = fork(builderWorkerPath, [], {
@@ -78,7 +78,7 @@ async function createBuildProcess(
buildProcess.on('exit', (code, signal) => { buildProcess.on('exit', (code, signal) => {
output.debug( output.debug(
`Build process for ${match.src} exited with ${signal || code}` `Build process for "${match.entrypoint}" exited with ${signal || code}`
); );
match.buildProcess = undefined; match.buildProcess = undefined;
}); });
@@ -128,7 +128,6 @@ export async function executeBuild(
let { buildProcess } = match; let { buildProcess } = match;
if (!runInProcess && !buildProcess) { if (!runInProcess && !buildProcess) {
devServer.output.debug(`Creating build process for ${entrypoint}`);
buildProcess = await createBuildProcess( buildProcess = await createBuildProcess(
match, match,
envConfigs, envConfigs,
@@ -364,8 +363,6 @@ export async function executeBuild(
...nowConfig.env, ...nowConfig.env,
...asset.environment, ...asset.environment,
...envConfigs.runEnv, ...envConfigs.runEnv,
NOW_REGION: 'dev1',
VERCEL_REGION: 'dev1',
}, },
}, },
}); });
@@ -431,7 +428,7 @@ export async function getBuildMatches(
} }
const files = fileList const files = fileList
.filter(name => name === src || minimatch(name, src)) .filter(name => name === src || minimatch(name, src, { dot: true }))
.map(name => join(cwd, name)); .map(name => join(cwd, name));
if (files.length === 0) { if (files.length === 0) {

View File

@@ -4,7 +4,7 @@ import PCRE from 'pcre-to-regexp';
import isURL from './is-url'; import isURL from './is-url';
import DevServer from './server'; import DevServer from './server';
import { HttpHeadersConfig, RouteResult } from './types'; import { NowConfig, HttpHeadersConfig, RouteResult } from './types';
import { isHandler, Route, HandleValue } from '@vercel/routing-utils'; import { isHandler, Route, HandleValue } from '@vercel/routing-utils';
export function resolveRouteParameters( export function resolveRouteParameters(
@@ -50,6 +50,7 @@ export async function devRouter(
reqMethod?: string, reqMethod?: string,
routes?: Route[], routes?: Route[],
devServer?: DevServer, devServer?: DevServer,
nowConfig?: NowConfig,
previousHeaders?: HttpHeadersConfig, previousHeaders?: HttpHeadersConfig,
missRoutes?: Route[], missRoutes?: Route[],
phase?: HandleValue | null phase?: HandleValue | null
@@ -117,9 +118,12 @@ export async function devRouter(
continue; continue;
} }
if (routeConfig.check && devServer && phase !== 'hit') { if (routeConfig.check && devServer && nowConfig && phase !== 'hit') {
const { pathname = '/' } = url.parse(destPath); const { pathname = '/' } = url.parse(destPath);
const hasDestFile = await devServer.hasFilesystem(pathname); const hasDestFile = await devServer.hasFilesystem(
pathname,
nowConfig
);
if (!hasDestFile) { if (!hasDestFile) {
if (routeConfig.status && phase !== 'miss') { if (routeConfig.status && phase !== 'miss') {
@@ -131,6 +135,7 @@ export async function devRouter(
reqMethod, reqMethod,
missRoutes, missRoutes,
devServer, devServer,
nowConfig,
combinedHeaders, combinedHeaders,
[], [],
'miss' 'miss'

View File

@@ -12,7 +12,7 @@ import { randomBytes } from 'crypto';
import serveHandler from 'serve-handler'; import serveHandler from 'serve-handler';
import { watch, FSWatcher } from 'chokidar'; import { watch, FSWatcher } from 'chokidar';
import { parse as parseDotenv } from 'dotenv'; import { parse as parseDotenv } from 'dotenv';
import { basename, dirname, extname, join } from 'path'; import path, { isAbsolute, basename, dirname, extname, join } from 'path';
import once from '@tootallnate/once'; import once from '@tootallnate/once';
import directoryTemplate from 'serve-handler/src/directory'; import directoryTemplate from 'serve-handler/src/directory';
import getPort from 'get-port'; import getPort from 'get-port';
@@ -49,18 +49,8 @@ import getNowConfigPath from '../config/local-path';
import { MissingDotenvVarsError } from '../errors-ts'; import { MissingDotenvVarsError } from '../errors-ts';
import cliPkg from '../pkg'; import cliPkg from '../pkg';
import { getVercelDirectory } from '../projects/link'; import { getVercelDirectory } from '../projects/link';
import { staticFiles as getFiles, getAllProjectFiles } from '../get-files'; import { staticFiles as getFiles } from '../get-files';
import { import { validateConfig } from './validate';
validateNowConfigBuilds,
validateNowConfigRoutes,
validateNowConfigCleanUrls,
validateNowConfigHeaders,
validateNowConfigRedirects,
validateNowConfigRewrites,
validateNowConfigTrailingSlash,
validateNowConfigFunctions,
} from './validate';
import { devRouter, getRoutesTypes } from './router'; import { devRouter, getRoutesTypes } from './router';
import getMimeType from './mime-type'; import getMimeType from './mime-type';
import { executeBuild, getBuildMatches, shutdownBuilder } from './builder'; import { executeBuild, getBuildMatches, shutdownBuilder } from './builder';
@@ -93,12 +83,17 @@ import {
HttpHeadersConfig, HttpHeadersConfig,
EnvConfigs, EnvConfigs,
} from './types'; } from './types';
import { ProjectSettings } from '../../types';
interface FSEvent { interface FSEvent {
type: string; type: string;
path: string; path: string;
} }
type WithFileNameSymbol<T> = T & {
[fileNameSymbol]: string;
};
function sortBuilders(buildA: Builder, buildB: Builder) { function sortBuilders(buildA: Builder, buildB: Builder) {
if (buildA && buildA.use && isOfficialRuntime('static-build', buildA.use)) { if (buildA && buildA.use && isOfficialRuntime('static-build', buildA.use)) {
return 1; return 1;
@@ -117,12 +112,11 @@ export default class DevServer {
public output: Output; public output: Output;
public proxy: httpProxy; public proxy: httpProxy;
public envConfigs: EnvConfigs; public envConfigs: EnvConfigs;
public frameworkSlug: string | null; public frameworkSlug?: string;
public files: BuilderInputs; public files: BuilderInputs;
public address: string; public address: string;
public devCacheDir: string; public devCacheDir: string;
private cachedNowConfig: NowConfig | null;
private caseSensitive: boolean; private caseSensitive: boolean;
private apiDir: string | null; private apiDir: string | null;
private apiExtensions: Set<string>; private apiExtensions: Set<string>;
@@ -140,22 +134,27 @@ export default class DevServer {
private devProcess?: ChildProcess; private devProcess?: ChildProcess;
private devProcessPort?: number; private devProcessPort?: number;
private devServerPids: Set<number>; private devServerPids: Set<number>;
private projectSettings?: ProjectSettings;
private getNowConfigPromise: Promise<NowConfig> | null; private getNowConfigPromise: Promise<NowConfig> | null;
private blockingBuildsPromise: Promise<void> | null; private blockingBuildsPromise: Promise<void> | null;
private updateBuildersPromise: Promise<void> | null; private updateBuildersPromise: Promise<void> | null;
private updateBuildersTimeout: NodeJS.Timeout | undefined; private updateBuildersTimeout: NodeJS.Timeout | undefined;
private startPromise: Promise<void> | null;
private environmentVars: Env | undefined;
constructor(cwd: string, options: DevServerOptions) { constructor(cwd: string, options: DevServerOptions) {
this.cwd = cwd; this.cwd = cwd;
this.debug = options.debug; this.debug = options.debug;
this.output = options.output; this.output = options.output;
this.envConfigs = { buildEnv: {}, runEnv: {}, allEnv: {} }; this.envConfigs = { buildEnv: {}, runEnv: {}, allEnv: {} };
this.environmentVars = options.environmentVars;
this.files = {}; this.files = {};
this.address = ''; this.address = '';
this.devCommand = options.devCommand; this.devCommand = options.devCommand;
this.projectSettings = options.projectSettings;
this.frameworkSlug = options.frameworkSlug; this.frameworkSlug = options.frameworkSlug;
this.cachedNowConfig = null;
this.caseSensitive = false; this.caseSensitive = false;
this.apiDir = null; this.apiDir = null;
this.apiExtensions = new Set(); this.apiExtensions = new Set();
@@ -174,15 +173,14 @@ export default class DevServer {
this.getNowConfigPromise = null; this.getNowConfigPromise = null;
this.blockingBuildsPromise = null; this.blockingBuildsPromise = null;
this.updateBuildersPromise = null; this.updateBuildersPromise = null;
this.startPromise = null;
this.watchAggregationId = null; this.watchAggregationId = null;
this.watchAggregationEvents = []; this.watchAggregationEvents = [];
this.watchAggregationTimeout = 500; this.watchAggregationTimeout = 500;
this.filter = path => Boolean(path); this.filter = path => Boolean(path);
this.podId = Math.random() this.podId = Math.random().toString(32).slice(-5);
.toString(32)
.slice(-5);
this.devServerPids = new Set(); this.devServerPids = new Set();
} }
@@ -235,16 +233,7 @@ export default class DevServer {
} }
} }
const nowConfig = await this.getNowConfig(false); const nowConfig = await this.getNowConfig();
// Update the env vars configuration
const nowConfigBuild = nowConfig.build || {};
const [runEnv, buildEnv] = await Promise.all([
this.getLocalEnv('.env', nowConfig.env),
this.getLocalEnv('.env.build', nowConfigBuild.env),
]);
const allEnv = { ...buildEnv, ...runEnv };
this.envConfigs = { buildEnv, runEnv, allEnv };
// Update the build matches in case an entrypoint was created or deleted // Update the build matches in case an entrypoint was created or deleted
await this.updateBuildMatches(nowConfig); await this.updateBuildMatches(nowConfig);
@@ -290,18 +279,19 @@ export default class DevServer {
for (const [result, [requestPath, match]] of needsRebuild) { for (const [result, [requestPath, match]] of needsRebuild) {
if ( if (
requestPath === null || requestPath === null ||
(await shouldServe(match, this.files, requestPath, this)) (await shouldServe(match, this.files, requestPath, this, nowConfig))
) { ) {
this.triggerBuild( this.triggerBuild(
match, match,
requestPath, requestPath,
null, null,
nowConfig,
result, result,
filesChangedArray, filesChangedArray,
filesRemovedArray filesRemovedArray
).catch((err: Error) => { ).catch((err: Error) => {
this.output.warn( this.output.warn(
`An error occurred while rebuilding ${match.src}:` `An error occurred while rebuilding \`${match.src}\`:`
); );
console.error(err.stack); console.error(err.stack);
}); });
@@ -388,9 +378,7 @@ export default class DevServer {
const sources = matches.map(m => m.src); const sources = matches.map(m => m.src);
if (isInitial && fileList.length === 0) { if (isInitial && fileList.length === 0) {
this.output.warn( this.output.warn('There are no files inside your deployment.');
'There are no files (or only files starting with a dot) inside your deployment.'
);
} }
// Delete build matches that no longer exists // Delete build matches that no longer exists
@@ -446,6 +434,9 @@ export default class DevServer {
`Cleaning up "blockingBuildsPromise" after error: ${err}` `Cleaning up "blockingBuildsPromise" after error: ${err}`
); );
this.blockingBuildsPromise = null; this.blockingBuildsPromise = null;
if (err) {
this.output.prettyError(err);
}
}); });
} }
@@ -493,13 +484,16 @@ export default class DevServer {
const dotenv = await fs.readFile(filePath, 'utf8'); const dotenv = await fs.readFile(filePath, 'utf8');
this.output.debug(`Using local env: ${filePath}`); this.output.debug(`Using local env: ${filePath}`);
env = parseDotenv(dotenv); env = parseDotenv(dotenv);
env = this.populateVercelEnvVars(env);
} catch (err) { } catch (err) {
if (err.code !== 'ENOENT') { if (err.code !== 'ENOENT') {
throw err; throw err;
} }
} }
try { try {
return this.validateEnvConfig(fileName, base || {}, env); return {
...this.validateEnvConfig(fileName, base || {}, env),
};
} catch (err) { } catch (err) {
if (err instanceof MissingDotenvVarsError) { if (err instanceof MissingDotenvVarsError) {
this.output.error(err.message); this.output.error(err.message);
@@ -511,57 +505,35 @@ export default class DevServer {
return {}; return {};
} }
async getNowConfig(canUseCache: boolean = true): Promise<NowConfig> { clearNowConfigPromise = () => {
this.getNowConfigPromise = null;
};
getNowConfig(): Promise<NowConfig> {
if (this.getNowConfigPromise) { if (this.getNowConfigPromise) {
return this.getNowConfigPromise; return this.getNowConfigPromise;
} }
this.getNowConfigPromise = this._getNowConfig(canUseCache); this.getNowConfigPromise = this._getNowConfig();
try {
return await this.getNowConfigPromise; // Clean up the promise once it has resolved
} finally { const clear = this.clearNowConfigPromise;
this.getNowConfigPromise = null; this.getNowConfigPromise.finally(clear);
}
return this.getNowConfigPromise;
} }
async _getNowConfig(canUseCache: boolean = true): Promise<NowConfig> { async _getNowConfig(): Promise<NowConfig> {
if (canUseCache && this.cachedNowConfig) { const configPath = getNowConfigPath(this.cwd);
return this.cachedNowConfig;
}
const pkg = await this.getPackageJson(); const [
pkg = null,
// The default empty `vercel.json` is used to serve all files as static // The default empty `vercel.json` is used to serve all
// when no `vercel.json` is present // files as static when no `vercel.json` is present
let configPath = 'vercel.json'; config = { version: 2, [fileNameSymbol]: 'vercel.json' },
let config: NowConfig = { ] = await Promise.all([
version: 2, this.readJsonFile<PackageJson>('package.json'),
[fileNameSymbol]: configPath, this.readJsonFile<NowConfig>(configPath),
}; ]);
try {
configPath = getNowConfigPath(this.cwd);
this.output.debug(`Reading ${configPath}`);
config = JSON.parse(await fs.readFile(configPath, 'utf8'));
config[fileNameSymbol] = configPath;
} catch (err) {
if (err.code === 'ENOENT') {
this.output.debug(err.toString());
} else if (err.name === 'SyntaxError') {
this.output.warn(
`There is a syntax error in ${configPath}: ${err.message}`
);
} else {
throw err;
}
}
const allFiles = await getAllProjectFiles(this.cwd, this.output);
const files = allFiles.filter(this.filter);
this.output.debug(
`Found ${allFiles.length} and ` +
`filtered out ${allFiles.length - files.length} files`
);
await this.validateNowConfig(config); await this.validateNowConfig(config);
const { error: routeError, routes: maybeRoutes } = getTransformedRoutes({ const { error: routeError, routes: maybeRoutes } = getTransformedRoutes({
@@ -578,6 +550,11 @@ export default class DevServer {
const featHandleMiss = true; // enable for zero config const featHandleMiss = true; // enable for zero config
const { projectSettings, cleanUrls, trailingSlash } = config; const { projectSettings, cleanUrls, trailingSlash } = config;
const opts = { output: this.output, isBuilds: true };
const files = (await getFiles(this.cwd, config, opts)).map(f =>
relative(this.cwd, f)
);
let { let {
builders, builders,
warnings, warnings,
@@ -589,7 +566,7 @@ export default class DevServer {
} = await detectBuilders(files, pkg, { } = await detectBuilders(files, pkg, {
tag: getDistTag(cliPkg.version) === 'canary' ? 'canary' : 'latest', tag: getDistTag(cliPkg.version) === 'canary' ? 'canary' : 'latest',
functions: config.functions, functions: config.functions,
...(projectSettings ? { projectSettings } : {}), projectSettings: projectSettings || this.projectSettings,
featHandleMiss, featHandleMiss,
cleanUrls, cleanUrls,
trailingSlash, trailingSlash,
@@ -611,6 +588,8 @@ export default class DevServer {
config.builds = config.builds || []; config.builds = config.builds || [];
config.builds.push(...builders); config.builds.push(...builders);
delete config.functions;
} }
let routes: Route[] = []; let routes: Route[] = [];
@@ -644,34 +623,57 @@ export default class DevServer {
await this.validateNowConfig(config); await this.validateNowConfig(config);
this.cachedNowConfig = config;
this.caseSensitive = hasNewRoutingProperties(config); this.caseSensitive = hasNewRoutingProperties(config);
this.apiDir = detectApiDirectory(config.builds || []); this.apiDir = detectApiDirectory(config.builds || []);
this.apiExtensions = detectApiExtensions(config.builds || []); this.apiExtensions = detectApiExtensions(config.builds || []);
// Update the env vars configuration
let [runEnv, buildEnv] = await Promise.all([
this.getLocalEnv('.env', config.env),
this.getLocalEnv('.env.build', config.build?.env),
]);
let allEnv = { ...buildEnv, ...runEnv };
// If no .env/.build.env is present, fetch and use cloud environment variables
if (Object.keys(allEnv).length === 0) {
const cloudEnv = this.populateVercelEnvVars(this.environmentVars);
allEnv = runEnv = buildEnv = cloudEnv;
}
this.envConfigs = { buildEnv, runEnv, allEnv };
return config; return config;
} }
async getPackageJson(): Promise<PackageJson | null> { async readJsonFile<T>(
const pkgPath = join(this.cwd, 'package.json'); filePath: string
let pkg: PackageJson | null = null; ): Promise<WithFileNameSymbol<T> | void> {
let rel, abs;
this.output.debug('Reading `package.json` file'); if (isAbsolute(filePath)) {
rel = path.relative(this.cwd, filePath);
abs = filePath;
} else {
rel = filePath;
abs = join(this.cwd, filePath);
}
this.output.debug(`Reading \`${rel}\` file`);
try { try {
pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8')); const raw = await fs.readFile(abs, 'utf8');
const parsed: WithFileNameSymbol<T> = JSON.parse(raw);
parsed[fileNameSymbol] = rel;
return parsed;
} catch (err) { } catch (err) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
this.output.debug('No `package.json` file present'); this.output.debug(`No \`${rel}\` file present`);
} else if (err.name === 'SyntaxError') { } else if (err.name === 'SyntaxError') {
this.output.warn( this.output.warn(
`There is a syntax error in the \`package.json\` file: ${err.message}` `There is a syntax error in the \`${rel}\` file: ${err.message}`
); );
} else { } else {
throw err; throw err;
} }
} }
return pkg;
} }
async tryValidateOrExit( async tryValidateOrExit(
@@ -693,14 +695,12 @@ export default class DevServer {
return; return;
} }
await this.tryValidateOrExit(config, validateNowConfigBuilds); const error = validateConfig(config);
await this.tryValidateOrExit(config, validateNowConfigRoutes);
await this.tryValidateOrExit(config, validateNowConfigCleanUrls); if (error) {
await this.tryValidateOrExit(config, validateNowConfigHeaders); this.output.prettyError(error);
await this.tryValidateOrExit(config, validateNowConfigRedirects); await this.exit(1);
await this.tryValidateOrExit(config, validateNowConfigRewrites); }
await this.tryValidateOrExit(config, validateNowConfigTrailingSlash);
await this.tryValidateOrExit(config, validateNowConfigFunctions);
} }
validateEnvConfig(type: string, env: Env = {}, localEnv: Env = {}): Env { validateEnvConfig(type: string, env: Env = {}, localEnv: Env = {}): Env {
@@ -746,6 +746,26 @@ export default class DevServer {
return merged; return merged;
} }
populateVercelEnvVars(env: Env | undefined): Env {
if (!env) {
return {};
}
for (const name of Object.keys(env)) {
if (name === 'VERCEL_URL') {
const host = new URL(this.address).host;
env['VERCEL_URL'] = host;
} else if (name === 'VERCEL_REGION') {
env['VERCEL_REGION'] = 'dev1';
}
}
// Always set NOW_REGION to match production
env['NOW_REGION'] = 'dev1';
return env;
}
/** /**
* Create an array of from builder inputs * Create an array of from builder inputs
* and filter them * and filter them
@@ -754,10 +774,20 @@ export default class DevServer {
return Object.keys(files).filter(this.filter); return Object.keys(files).filter(this.filter);
} }
start(...listenSpec: ListenSpec): Promise<void> {
if (!this.startPromise) {
this.startPromise = this._start(...listenSpec).catch(err => {
this.stop();
throw err;
});
}
return this.startPromise;
}
/** /**
* Launches the `vercel dev` server. * Launches the `vercel dev` server.
*/ */
async start(...listenSpec: ListenSpec): Promise<void> { async _start(...listenSpec: ListenSpec): Promise<void> {
if (!fs.existsSync(this.cwd)) { if (!fs.existsSync(this.cwd)) {
throw new Error(`${chalk.bold(this.cwd)} doesn't exist`); throw new Error(`${chalk.bold(this.cwd)} doesn't exist`);
} }
@@ -769,15 +799,39 @@ export default class DevServer {
const { ig } = await getVercelIgnore(this.cwd); const { ig } = await getVercelIgnore(this.cwd);
this.filter = ig.createFilter(); this.filter = ig.createFilter();
// Retrieve the path of the native module let address: string | null = null;
const nowConfig = await this.getNowConfig(false); while (typeof address !== 'string') {
const nowConfigBuild = nowConfig.build || {}; try {
const [runEnv, buildEnv] = await Promise.all([ address = await listen(this.server, ...listenSpec);
this.getLocalEnv('.env', nowConfig.env), } catch (err) {
this.getLocalEnv('.env.build', nowConfigBuild.env), this.output.debug(`Got listen error: ${err.code}`);
]); if (err.code === 'EADDRINUSE') {
const allEnv = { ...buildEnv, ...runEnv }; if (typeof listenSpec[0] === 'number') {
this.envConfigs = { buildEnv, runEnv, allEnv }; // Increase port and try again
this.output.note(
`Requested port ${chalk.yellow(
String(listenSpec[0])
)} is already in use`
);
listenSpec[0]++;
} else {
this.output.error(
`Requested socket ${chalk.cyan(listenSpec[0])} is already in use`
);
process.exit(1);
}
} else {
throw err;
}
}
}
this.address = address
.replace('[::]', 'localhost')
.replace('127.0.0.1', 'localhost');
const nowConfig = await this.getNowConfig();
const devCommandPromise = this.runDevCommand();
const opts = { output: this.output, isBuilds: true }; const opts = { output: this.output, isBuilds: true };
const files = await getFiles(this.cwd, nowConfig, opts); const files = await getFiles(this.cwd, nowConfig, opts);
@@ -869,39 +923,6 @@ export default class DevServer {
this.proxy.ws(req, socket, head, { target }); this.proxy.ws(req, socket, head, { target });
}); });
const devCommandPromise = this.runDevCommand();
let address: string | null = null;
while (typeof address !== 'string') {
try {
address = await listen(this.server, ...listenSpec);
} catch (err) {
this.output.debug(`Got listen error: ${err.code}`);
if (err.code === 'EADDRINUSE') {
if (typeof listenSpec[0] === 'number') {
// Increase port and try again
this.output.note(
`Requested port ${chalk.yellow(
String(listenSpec[0])
)} is already in use`
);
listenSpec[0]++;
} else {
this.output.error(
`Requested socket ${chalk.cyan(listenSpec[0])} is already in use`
);
process.exit(1);
}
} else {
throw err;
}
}
}
this.address = address
.replace('[::]', 'localhost')
.replace('127.0.0.1', 'localhost');
await devCommandPromise; await devCommandPromise;
this.output.ready(`Available at ${link(this.address)}`); this.output.ready(`Available at ${link(this.address)}`);
@@ -1110,10 +1131,7 @@ export default class DevServer {
const allHeaders = { const allHeaders = {
'cache-control': 'public, max-age=0, must-revalidate', 'cache-control': 'public, max-age=0, must-revalidate',
...headers, ...headers,
server: 'now', server: 'Vercel',
'x-now-trace': 'dev1',
'x-now-id': nowRequestId,
'x-now-cache': 'MISS',
'x-vercel-id': nowRequestId, 'x-vercel-id': nowRequestId,
'x-vercel-cache': 'MISS', 'x-vercel-cache': 'MISS',
}; };
@@ -1127,29 +1145,31 @@ export default class DevServer {
*/ */
getNowProxyHeaders( getNowProxyHeaders(
req: http.IncomingMessage, req: http.IncomingMessage,
nowRequestId: string nowRequestId: string,
xfwd: boolean
): http.IncomingHttpHeaders { ): http.IncomingHttpHeaders {
const ip = this.getRequestIp(req); const ip = this.getRequestIp(req);
const { host } = req.headers; const { host } = req.headers;
return { const headers: http.IncomingHttpHeaders = {
...req.headers, connection: 'close',
Connection: 'close',
'x-forwarded-host': host,
'x-forwarded-proto': 'http',
'x-forwarded-for': ip,
'x-real-ip': ip, 'x-real-ip': ip,
'x-now-trace': 'dev1', 'x-vercel-deployment-url': host,
'x-now-deployment-url': host, 'x-vercel-forwarded-for': ip,
'x-now-id': nowRequestId, 'x-vercel-id': nowRequestId,
'x-now-log-id': nowRequestId.split('-')[2],
'x-zeit-co-forwarded-for': ip,
}; };
if (xfwd) {
headers['x-forwarded-host'] = host;
headers['x-forwarded-proto'] = 'http';
headers['x-forwarded-for'] = ip;
}
return headers;
} }
async triggerBuild( async triggerBuild(
match: BuildMatch, match: BuildMatch,
requestPath: string | null, requestPath: string | null,
req: http.IncomingMessage | null, req: http.IncomingMessage | null,
nowConfig: NowConfig,
previousBuildResult?: BuildResult, previousBuildResult?: BuildResult,
filesChanged?: string[], filesChanged?: string[],
filesRemoved?: string[] filesRemoved?: string[]
@@ -1165,10 +1185,11 @@ export default class DevServer {
// A build for `buildKey` is already in progress, so don't trigger // A build for `buildKey` is already in progress, so don't trigger
// another rebuild for this request - just wait on the existing one. // another rebuild for this request - just wait on the existing one.
let msg = `De-duping build "${buildKey}"`; let msg = `De-duping build "${buildKey}"`;
if (req) msg += ` for "${req.method} ${req.url}"`; if (req) {
msg += ` for "${req.method} ${req.url}"`;
}
this.output.debug(msg); this.output.debug(msg);
} else { } else {
const nowConfig = await this.getNowConfig();
if (previousBuildResult) { if (previousBuildResult) {
// Tear down any `output` assets from a previous build, so that they // Tear down any `output` assets from a previous build, so that they
// are not available to be served while the rebuild is in progress. // are not available to be served while the rebuild is in progress.
@@ -1179,7 +1200,9 @@ export default class DevServer {
} }
} }
let msg = `Building asset "${buildKey}"`; let msg = `Building asset "${buildKey}"`;
if (req) msg += ` for "${req.method} ${req.url}"`; if (req) {
msg += ` for "${req.method} ${req.url}"`;
}
this.output.debug(msg); this.output.debug(msg);
buildPromise = executeBuild( buildPromise = executeBuild(
nowConfig, nowConfig,
@@ -1221,6 +1244,8 @@ export default class DevServer {
req: http.IncomingMessage, req: http.IncomingMessage,
res: http.ServerResponse res: http.ServerResponse
) => { ) => {
await this.startPromise;
let nowRequestId = generateRequestId(this.podId); let nowRequestId = generateRequestId(this.podId);
if (this.stopping) { if (this.stopping) {
@@ -1260,7 +1285,7 @@ export default class DevServer {
const { status, headers, dest } = routeResult; const { status, headers, dest } = routeResult;
const location = headers['location'] || dest; const location = headers['location'] || dest;
if (status && location && (300 <= status && status <= 399)) { if (status && location && 300 <= status && status <= 399) {
this.output.debug(`Route found with redirect status code ${status}`); this.output.debug(`Route found with redirect status code ${status}`);
await this.sendRedirect(req, res, nowRequestId, location, status); await this.sendRedirect(req, res, nowRequestId, location, status);
return true; return true;
@@ -1350,6 +1375,7 @@ export default class DevServer {
req.method, req.method,
phaseRoutes, phaseRoutes,
this, this,
nowConfig,
prevHeaders, prevHeaders,
missRoutes, missRoutes,
phase phase
@@ -1377,7 +1403,8 @@ export default class DevServer {
this.buildMatches, this.buildMatches,
this.files, this.files,
routeResult.dest, routeResult.dest,
this this,
nowConfig
); );
if ( if (
@@ -1400,6 +1427,7 @@ export default class DevServer {
req.method, req.method,
missRoutes, missRoutes,
this, this,
nowConfig,
routeResult.headers, routeResult.headers,
[], [],
'miss' 'miss'
@@ -1409,7 +1437,8 @@ export default class DevServer {
this.buildMatches, this.buildMatches,
this.files, this.files,
routeResult.dest, routeResult.dest,
this this,
nowConfig
); );
if ( if (
await this.exitWithStatus( await this.exitWithStatus(
@@ -1432,6 +1461,7 @@ export default class DevServer {
req.method, req.method,
hitRoutes, hitRoutes,
this, this,
nowConfig,
routeResult.headers, routeResult.headers,
[], [],
'hit' 'hit'
@@ -1446,6 +1476,7 @@ export default class DevServer {
req.method, req.method,
errorRoutes, errorRoutes,
this, this,
nowConfig,
routeResult.headers, routeResult.headers,
[], [],
'error' 'error'
@@ -1455,7 +1486,8 @@ export default class DevServer {
this.buildMatches, this.buildMatches,
this.files, this.files,
routeResultForError.dest, routeResultForError.dest,
this this,
nowConfig
); );
if (matchForError) { if (matchForError) {
@@ -1503,16 +1535,15 @@ export default class DevServer {
if (!match) { if (!match) {
// If the dev command is started, then proxy to it // If the dev command is started, then proxy to it
if (this.devProcessPort) { if (this.devProcessPort) {
debug('Proxying to frontend dev server'); const upstream = `http://localhost:${this.devProcessPort}`;
debug(`Proxying to frontend dev server: ${upstream}`);
this.setResponseHeaders(res, nowRequestId); this.setResponseHeaders(res, nowRequestId);
return proxyPass( const origUrl = url.parse(req.url || '/', true);
req, delete origUrl.search;
res, origUrl.pathname = dest;
`http://localhost:${this.devProcessPort}`, Object.assign(origUrl.query, uri_args);
this, req.url = url.format(origUrl);
nowRequestId, return proxyPass(req, res, upstream, this, nowRequestId, false);
false
);
} }
if ( if (
@@ -1544,7 +1575,8 @@ export default class DevServer {
newUrl, newUrl,
req.method, req.method,
buildResult.routes, buildResult.routes,
this this,
nowConfig
); );
if (matchedRoute.found && callLevel === 0) { if (matchedRoute.found && callLevel === 0) {
debug(`Found matching route ${matchedRoute.dest} for ${newUrl}`); debug(`Found matching route ${matchedRoute.dest} for ${newUrl}`);
@@ -1624,6 +1656,12 @@ export default class DevServer {
query: parsed.query, query: parsed.query,
}); });
// Add the Vercel platform proxy request headers
const headers = this.getNowProxyHeaders(req, nowRequestId, false);
for (const [name, value] of Object.entries(headers)) {
req.headers[name] = value;
}
this.setResponseHeaders(res, nowRequestId); this.setResponseHeaders(res, nowRequestId);
return proxyPass( return proxyPass(
req, req,
@@ -1640,7 +1678,7 @@ export default class DevServer {
let foundAsset = findAsset(match, requestPath, nowConfig); let foundAsset = findAsset(match, requestPath, nowConfig);
if (!foundAsset && callLevel === 0) { if (!foundAsset && callLevel === 0) {
await this.triggerBuild(match, buildRequestPath, req); await this.triggerBuild(match, buildRequestPath, req, nowConfig);
// Since the `asset` was just built, resolve again to get the new asset // Since the `asset` was just built, resolve again to get the new asset
foundAsset = findAsset(match, requestPath, nowConfig); foundAsset = findAsset(match, requestPath, nowConfig);
@@ -1672,8 +1710,9 @@ export default class DevServer {
const { asset, assetKey } = foundAsset; const { asset, assetKey } = foundAsset;
debug( debug(
`Serving asset: [${asset.type}] ${assetKey} ${(asset as any) `Serving asset: [${asset.type}] ${assetKey} ${
.contentType || ''}` (asset as any).contentType || ''
}`
); );
/* eslint-disable no-case-declarations */ /* eslint-disable no-case-declarations */
@@ -1735,7 +1774,10 @@ export default class DevServer {
method: req.method || 'GET', method: req.method || 'GET',
host: req.headers.host, host: req.headers.host,
path, path,
headers: this.getNowProxyHeaders(req, nowRequestId), headers: {
...req.headers,
...this.getNowProxyHeaders(req, nowRequestId, true),
},
encoding: 'base64', encoding: 'base64',
body: body.toString('base64'), body: body.toString('base64'),
}; };
@@ -1865,8 +1907,17 @@ export default class DevServer {
return true; return true;
} }
async hasFilesystem(dest: string): Promise<boolean> { async hasFilesystem(dest: string, nowConfig: NowConfig): Promise<boolean> {
if (await findBuildMatch(this.buildMatches, this.files, dest, this, true)) { if (
await findBuildMatch(
this.buildMatches,
this.files,
dest,
this,
nowConfig,
true
)
) {
return true; return true;
} }
return false; return false;
@@ -1896,8 +1947,6 @@ export default class DevServer {
...(this.frameworkSlug === 'create-react-app' ? { BROWSER: 'none' } : {}), ...(this.frameworkSlug === 'create-react-app' ? { BROWSER: 'none' } : {}),
...process.env, ...process.env,
...this.envConfigs.allEnv, ...this.envConfigs.allEnv,
NOW_REGION: 'dev1',
VERCEL_REGION: 'dev1',
PORT: `${port}`, PORT: `${port}`,
}; };
@@ -2047,13 +2096,23 @@ async function findBuildMatch(
files: BuilderInputs, files: BuilderInputs,
requestPath: string, requestPath: string,
devServer: DevServer, devServer: DevServer,
isFilesystem?: boolean nowConfig: NowConfig,
isFilesystem = false
): Promise<BuildMatch | null> { ): Promise<BuildMatch | null> {
requestPath = requestPath.replace(/^\//, ''); requestPath = requestPath.replace(/^\//, '');
let bestIndexMatch: undefined | BuildMatch; let bestIndexMatch: undefined | BuildMatch;
for (const match of matches.values()) { for (const match of matches.values()) {
if (await shouldServe(match, files, requestPath, devServer, isFilesystem)) { if (
await shouldServe(
match,
files,
requestPath,
devServer,
nowConfig,
isFilesystem
)
) {
if (!isIndex(match.src)) { if (!isIndex(match.src)) {
return match; return match;
} else { } else {
@@ -2075,14 +2134,14 @@ async function shouldServe(
files: BuilderInputs, files: BuilderInputs,
requestPath: string, requestPath: string,
devServer: DevServer, devServer: DevServer,
isFilesystem?: boolean nowConfig: NowConfig,
isFilesystem = false
): Promise<boolean> { ): Promise<boolean> {
const { const {
src, src,
config, config,
builderWithPkg: { builder }, builderWithPkg: { builder },
} = match; } = match;
const nowConfig = await devServer.getNowConfig();
const cleanSrc = src.endsWith('.html') ? src.slice(0, -5) : src; const cleanSrc = src.endsWith('.html') ? src.slice(0, -5) : src;
const trimmedPath = requestPath.endsWith('/') const trimmedPath = requestPath.endsWith('/')
? requestPath.slice(0, -1) ? requestPath.slice(0, -1)
@@ -2126,7 +2185,7 @@ async function shouldServe(
return true; return true;
} else if ( } else if (
!isFilesystem && !isFilesystem &&
(await findMatchingRoute(match, requestPath, devServer)) (await findMatchingRoute(match, requestPath, devServer, nowConfig))
) { ) {
// If there's no `shouldServe()` function and no matched asset, then look // If there's no `shouldServe()` function and no matched asset, then look
// up if there's a matching build route on the `match` that has already // up if there's a matching build route on the `match` that has already
@@ -2139,7 +2198,8 @@ async function shouldServe(
async function findMatchingRoute( async function findMatchingRoute(
match: BuildMatch, match: BuildMatch,
requestPath: string, requestPath: string,
devServer: DevServer devServer: DevServer,
nowConfig: NowConfig
): Promise<RouteResult | void> { ): Promise<RouteResult | void> {
const reqUrl = `/${requestPath}`; const reqUrl = `/${requestPath}`;
for (const buildResult of match.buildResults.values()) { for (const buildResult of match.buildResults.values()) {
@@ -2148,7 +2208,8 @@ async function findMatchingRoute(
reqUrl, reqUrl,
undefined, undefined,
buildResult.routes, buildResult.routes,
devServer devServer,
nowConfig
); );
if (route.found) { if (route.found) {
return route; return route;
@@ -2203,7 +2264,9 @@ function isIndex(path: string): boolean {
} }
function minimatches(files: string[], pattern: string): boolean { function minimatches(files: string[], pattern: string): boolean {
return files.some(file => file === pattern || minimatch(file, pattern)); return files.some(
file => file === pattern || minimatch(file, pattern, { dot: true })
);
} }
function fileChanged( function fileChanged(

View File

@@ -17,19 +17,22 @@ import {
import { NowConfig } from '@vercel/client'; import { NowConfig } from '@vercel/client';
import { HandleValue, Route } from '@vercel/routing-utils'; import { HandleValue, Route } from '@vercel/routing-utils';
import { Output } from '../output'; import { Output } from '../output';
import { ProjectSettings } from '../../types';
export { NowConfig }; export { NowConfig };
export interface DevServerOptions { export interface DevServerOptions {
output: Output; output: Output;
debug: boolean; debug: boolean;
devCommand: string | undefined; devCommand?: string;
frameworkSlug: string | null; frameworkSlug?: string;
projectSettings?: ProjectSettings;
environmentVars?: Env;
} }
export interface EnvConfigs { export interface EnvConfigs {
/** /**
* environment variables from `.env.build` file (deprecated) * environment variables from `.env.build` file
*/ */
buildEnv: Env; buildEnv: Env;

View File

@@ -8,71 +8,52 @@ import {
trailingSlashSchema, trailingSlashSchema,
} from '@vercel/routing-utils'; } from '@vercel/routing-utils';
import { NowConfig } from './types'; import { NowConfig } from './types';
import { functionsSchema, buildsSchema } from '@vercel/build-utils'; import {
functionsSchema,
buildsSchema,
NowBuildError,
getPrettyError,
} from '@vercel/build-utils';
import { fileNameSymbol } from '@vercel/client';
const vercelConfigSchema = {
type: 'object',
// These are not all possibilities because `vc dev`
// doesn't need to know about `regions`, `public`, etc.
additionalProperties: true,
properties: {
builds: buildsSchema,
routes: routesSchema,
cleanUrls: cleanUrlsSchema,
headers: headersSchema,
redirects: redirectsSchema,
rewrites: rewritesSchema,
trailingSlash: trailingSlashSchema,
functions: functionsSchema,
},
};
const ajv = new Ajv(); const ajv = new Ajv();
const validate = ajv.compile(vercelConfigSchema);
const validateBuilds = ajv.compile(buildsSchema); export function validateConfig(config: NowConfig): NowBuildError | null {
const validateRoutes = ajv.compile(routesSchema); if (!validate(config)) {
const validateCleanUrls = ajv.compile(cleanUrlsSchema); if (validate.errors && validate.errors[0]) {
const validateHeaders = ajv.compile(headersSchema); const error = validate.errors[0];
const validateRedirects = ajv.compile(redirectsSchema); const fileName = config[fileNameSymbol] || 'vercel.json';
const validateRewrites = ajv.compile(rewritesSchema); const niceError = getPrettyError(error);
const validateTrailingSlash = ajv.compile(trailingSlashSchema); niceError.message = `Invalid ${fileName} - ${niceError.message}`;
const validateFunctions = ajv.compile(functionsSchema); return niceError;
}
export function validateNowConfigBuilds(config: NowConfig) {
return validateKey(config, 'builds', validateBuilds);
}
export function validateNowConfigRoutes(config: NowConfig) {
return validateKey(config, 'routes', validateRoutes);
}
export function validateNowConfigCleanUrls(config: NowConfig) {
return validateKey(config, 'cleanUrls', validateCleanUrls);
}
export function validateNowConfigHeaders(config: NowConfig) {
return validateKey(config, 'headers', validateHeaders);
}
export function validateNowConfigRedirects(config: NowConfig) {
return validateKey(config, 'redirects', validateRedirects);
}
export function validateNowConfigRewrites(config: NowConfig) {
return validateKey(config, 'rewrites', validateRewrites);
}
export function validateNowConfigTrailingSlash(config: NowConfig) {
return validateKey(config, 'trailingSlash', validateTrailingSlash);
}
export function validateNowConfigFunctions(config: NowConfig) {
return validateKey(config, 'functions', validateFunctions);
}
function validateKey(
config: NowConfig,
key: keyof NowConfig,
validate: Ajv.ValidateFunction
) {
const value = config[key];
if (!value) {
return null;
} }
if (!validate(value)) { if (config.functions && config.builds) {
if (!validate.errors) { return new NowBuildError({
return null; code: 'FUNCTIONS_AND_BUILDS',
} message:
'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.',
const error = validate.errors[0]; link: 'https://vercel.link/functions-and-builds',
});
return `Invalid \`${String(key)}\` property: ${error.dataPath} ${
error.message
}`;
} }
return null; return null;

View File

@@ -14,6 +14,7 @@ export class APIError extends Error {
status: number; status: number;
serverMessage: string; serverMessage: string;
link?: string; link?: string;
action?: string;
retryAfter: number | null | 'never'; retryAfter: number | null | 'never';
[key: string]: any; [key: string]: any;

View File

@@ -0,0 +1,46 @@
import getEnvVariables from './env/get-env-records';
import getDecryptedSecret from './env/get-decrypted-secret';
import Client from './client';
import { Output } from './output/create-output';
import { ProjectEnvTarget, Project } from '../types';
import { Env } from '@vercel/build-utils';
export default async function getDecryptedEnvRecords(
output: Output,
client: Client,
project: Project,
target: ProjectEnvTarget
): Promise<Env> {
const envs = await getEnvVariables(output, client, project.id, 4, target);
const decryptedValues = await Promise.all(
envs.map(async env => {
try {
const value = await getDecryptedSecret(output, client, env.value);
return { value, found: true };
} catch (error) {
if (error && error.status === 404) {
return { value: '', found: false };
}
throw error;
}
})
);
const results: Env = {};
for (let i = 0; i < decryptedValues.length; i++) {
const { key } = envs[i];
const { value, found } = decryptedValues[i];
if (!found) {
output.print('');
output.warn(
`Unable to download variable ${key} because associated secret was deleted`
);
continue;
}
results[key] = value ? value : '';
}
return results;
}

View File

@@ -1,7 +1,7 @@
import { resolve, join } from 'path'; import { resolve } from 'path';
import ignore from 'ignore'; import ignore from 'ignore';
import dockerignore from '@zeit/dockerignore'; import dockerignore from '@zeit/dockerignore';
import _glob, { IOptions } from 'glob'; import _glob, { IOptions as GlobOptions } from 'glob';
import fs from 'fs-extra'; import fs from 'fs-extra';
import { getVercelIgnore } from '@vercel/client'; import { getVercelIgnore } from '@vercel/client';
import IGNORED from './ignored'; import IGNORED from './ignored';
@@ -12,11 +12,11 @@ import { NowConfig } from './dev/types';
type NullableString = string | null; type NullableString = string | null;
const flatten = ( function flatten(
arr: NullableString[] | NullableString[][], arr: NullableString[] | NullableString[][],
res: NullableString[] = [] res: NullableString[] = []
) => { ): NullableString[] {
for (let cur of arr) { for (const cur of arr) {
if (Array.isArray(cur)) { if (Array.isArray(cur)) {
flatten(cur, res); flatten(cur, res);
} else { } else {
@@ -24,21 +24,17 @@ const flatten = (
} }
} }
return res; return res;
}; }
const glob = async function(pattern: string, options: IOptions) { async function glob(pattern: string, options: GlobOptions): Promise<string[]> {
return new Promise<string[]>((resolve, reject) => { return new Promise((resolve, reject) => {
_glob(pattern, options, (error, files) => { _glob(pattern, options, (err, files) => {
if (error) { err ? reject(err) : resolve(files);
reject(error);
} else {
resolve(files);
}
}); });
}); });
}; }
interface WalkSyncOptions { interface WalkOptions {
output: Output; output: Output;
} }
@@ -51,27 +47,27 @@ interface WalkSyncOptions {
* - `output` {Object} "output" helper object * - `output` {Object} "output" helper object
* @returns {Array} * @returns {Array}
*/ */
const walkSync = async ( async function walk(
dir: string, dir: string,
path: string, path: string,
filelist: string[] = [], filelist: string[] = [],
opts: WalkSyncOptions opts: WalkOptions
) => { ) {
const { debug } = opts.output; const { debug } = opts.output;
const dirc = await fs.readdir(asAbsolute(dir, path)); const dirc = await fs.readdir(asAbsolute(dir, path));
for (let file of dirc) { for (let file of dirc) {
file = asAbsolute(file, dir); file = asAbsolute(file, dir);
try { try {
const file_stat = await fs.stat(file); const fileStat = await fs.stat(file);
filelist = file_stat.isDirectory() filelist = fileStat.isDirectory()
? await walkSync(file, path, filelist, opts) ? await walk(file, path, filelist, opts)
: filelist.concat(file); : filelist.concat(file);
} catch (e) { } catch (e) {
debug(`Ignoring invalid file ${file}`); debug(`Ignoring invalid file ${file}`);
} }
} }
return filelist; return filelist;
}; }
interface FilesInWhitelistOptions { interface FilesInWhitelistOptions {
output: Output; output: Output;
@@ -85,7 +81,7 @@ interface FilesInWhitelistOptions {
* - `output` {Object} "output" helper object * - `output` {Object} "output" helper object
* @returns {Array} the expanded list of whitelisted files. * @returns {Array} the expanded list of whitelisted files.
*/ */
const getFilesInWhitelist = async function( const getFilesInWhitelist = async function (
whitelist: string[], whitelist: string[],
path: string, path: string,
opts: FilesInWhitelistOptions opts: FilesInWhitelistOptions
@@ -97,10 +93,10 @@ const getFilesInWhitelist = async function(
whitelist.map(async (file: string) => { whitelist.map(async (file: string) => {
file = asAbsolute(file, path); file = asAbsolute(file, path);
try { try {
const file_stat = await fs.stat(file); const fileStat = await fs.stat(file);
if (file_stat.isDirectory()) { if (fileStat.isDirectory()) {
const dir_files = await walkSync(file, path, [], opts); const dirFiles = await walk(file, path, [], opts);
files.push(...dir_files); files.push(...dirFiles);
} else { } else {
files.push(file); files.push(file);
} }
@@ -117,7 +113,7 @@ const getFilesInWhitelist = async function(
* because ignore doesn't like them :| * because ignore doesn't like them :|
*/ */
const clearRelative = function(str: string) { const clearRelative = function (str: string) {
return str.replace(/(\n|^)\.\//g, '$1'); return str.replace(/(\n|^)\.\//g, '$1');
}; };
@@ -127,7 +123,7 @@ const clearRelative = function(str: string) {
* @return {String} results or `''` * @return {String} results or `''`
*/ */
const maybeRead = async function<T>(path: string, default_: T) { const maybeRead = async function <T>(path: string, default_: T) {
try { try {
return await fs.readFile(path, 'utf8'); return await fs.readFile(path, 'utf8');
} catch (err) { } catch (err) {
@@ -143,7 +139,7 @@ const maybeRead = async function<T>(path: string, default_: T) {
* @param {String} parent full path * @param {String} parent full path
*/ */
const asAbsolute = function(path: string, parent: string) { const asAbsolute = function (path: string, parent: string) {
if (path[0] === '/') { if (path[0] === '/') {
return path; return path;
} }
@@ -272,7 +268,7 @@ export async function npm(
const search = Array.prototype.concat.apply( const search = Array.prototype.concat.apply(
[], [],
await Promise.all( await Promise.all(
search_.map(file => search_.map((file) =>
glob(file, { cwd: path, absolute: true, dot: true }) glob(file, { cwd: path, absolute: true, dot: true })
) )
) )
@@ -364,7 +360,7 @@ export async function docker(
const search_ = ['.']; const search_ = ['.'];
// Convert all filenames into absolute paths // Convert all filenames into absolute paths
const search = search_.map(file => asAbsolute(file, path)); const search = search_.map((file) => asAbsolute(file, path));
// Compile list of ignored patterns and files // Compile list of ignored patterns and files
const dockerIgnore = await maybeRead(resolve(path, '.dockerignore'), null); const dockerIgnore = await maybeRead(resolve(path, '.dockerignore'), null);
@@ -382,7 +378,7 @@ export async function docker(
.createFilter(); .createFilter();
const prefixLength = path.length + 1; const prefixLength = path.length + 1;
const accepts = function(file: string) { const accepts = function (file: string) {
const relativePath = file.substr(prefixLength); const relativePath = file.substr(prefixLength);
if (relativePath === '') { if (relativePath === '') {
@@ -415,24 +411,6 @@ export async function docker(
return uniqueStrings(files); return uniqueStrings(files);
} }
/**
* Get a list of all files inside the project folder
*
* @param {String} of the current working directory
* @param {Object} output instance
* @return {Array} of {String}s with the found files
*/
export async function getAllProjectFiles(cwd: string, { debug }: Output) {
// We need a slash at the end to remove it later on from the matched files
const current = join(resolve(cwd), '/');
debug(`Searching files inside of ${current}`);
const list = await glob('**', { cwd: current, absolute: true, nodir: true });
// We need to replace \ with / for windows
return list.map(file => file.replace(current.replace(/\\/g, '/'), ''));
}
interface ExplodeOptions { interface ExplodeOptions {
accepts: (file: string) => boolean; accepts: (file: string) => boolean;
output: Output; output: Output;
@@ -482,7 +460,7 @@ async function explode(
if (s.isDirectory()) { if (s.isDirectory()) {
const all = await fs.readdir(file); const all = await fs.readdir(file);
/* eslint-disable no-use-before-define */ /* eslint-disable no-use-before-define */
const recursive = many(all.map(subdir => asAbsolute(subdir, file))); const recursive = many(all.map((subdir) => asAbsolute(subdir, file)));
return (recursive as any) as Promise<string | null>; return (recursive as any) as Promise<string | null>;
/* eslint-enable no-use-before-define */ /* eslint-enable no-use-before-define */
} }
@@ -494,7 +472,7 @@ async function explode(
return path; return path;
}; };
const many = (all: string[]) => Promise.all(all.map(file => list(file))); const many = (all: string[]) => Promise.all(all.map((file) => list(file)));
const arrayOfArrays = await many(paths); const arrayOfArrays = await many(paths);
return flatten(arrayOfArrays).filter(notNull); return flatten(arrayOfArrays).filter(notNull);
} }

View File

@@ -145,6 +145,7 @@ export default class Now extends EventEmitter {
regions, regions,
target: target || undefined, target: target || undefined,
projectSettings, projectSettings,
source: 'cli',
}; };
// Ignore specific items from vercel.json // Ignore specific items from vercel.json

View File

@@ -44,8 +44,8 @@ export default async function editProjectSettings(
output.print( output.print(
!framework.slug !framework.slug
? `No framework detected. Default project settings:\n` ? `No framework detected. Default Project Settings:\n`
: `Auto-detected project settings (${chalk.bold(framework.name)}):\n` : `Auto-detected Project Settings (${chalk.bold(framework.name)}):\n`
); );
settings.framework = framework.slug; settings.framework = framework.slug;

View File

@@ -27,7 +27,8 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
function warn( function warn(
str: string, str: string,
slug: string | null = null, slug: string | null = null,
link: string | null = null link: string | null = null,
action: string = 'Learn More'
) { ) {
const prevTerm = process.env.TERM; const prevTerm = process.env.TERM;
@@ -42,7 +43,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
boxen( boxen(
chalk.bold.yellow('WARN! ') + chalk.bold.yellow('WARN! ') +
str + str +
(details ? `\nMore details: ${renderLink(details)}` : ''), (details ? `\n${action}: ${renderLink(details)}` : ''),
{ {
padding: { padding: {
top: 0, top: 0,
@@ -67,7 +68,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
str: string, str: string,
slug?: string, slug?: string,
link?: string, link?: string,
action = 'More details' action = 'Learn More'
) { ) {
print(`${chalk.red(`Error!`)} ${str}\n`); print(`${chalk.red(`Error!`)} ${str}\n`);
const details = slug ? `https://err.sh/now/${slug}` : link; const details = slug ? `https://err.sh/now/${slug}` : link;

View File

@@ -8,11 +8,11 @@ const metric = metrics();
export default function error(...input: string[] | [APIError]) { export default function error(...input: string[] | [APIError]) {
let messages = input; let messages = input;
if (typeof input[0] === 'object') { if (typeof input[0] === 'object') {
const { slug, message, link } = input[0]; const { slug, message, link, action = 'Learn More' } = input[0];
messages = [message]; messages = [message];
const details = slug ? `https://err.sh/now/${slug}` : link; const details = slug ? `https://err.sh/now/${slug}` : link;
if (details) { if (details) {
messages.push(`${chalk.bold('More details')}: ${renderLink(details)}`); messages.push(`${chalk.bold(action)}: ${renderLink(details)}`);
} }
} }

View File

@@ -15,6 +15,7 @@ import { prependEmoji, emoji } from '../emoji';
import AJV from 'ajv'; import AJV from 'ajv';
import { isDirectory } from '../config/global-path'; import { isDirectory } from '../config/global-path';
import { NowBuildError, getPlatformEnv } from '@vercel/build-utils'; import { NowBuildError, getPlatformEnv } from '@vercel/build-utils';
import outputCode from '../output/code';
const readFile = promisify(fs.readFile); const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile); const writeFile = promisify(fs.writeFile);
@@ -73,7 +74,7 @@ async function getLinkFromDir(dir: string): Promise<ProjectLink | null> {
if (!ajv.validate(linkSchema, link)) { if (!ajv.validate(linkSchema, link)) {
throw new Error( throw new Error(
`Project settings are invalid. To link your project again, remove the ${dir} directory.` `Project Settings are invalid. To link your project again, remove the ${dir} directory.`
); );
} }
@@ -87,7 +88,7 @@ async function getLinkFromDir(dir: string): Promise<ProjectLink | null> {
// link file can't be read // link file can't be read
if (error.name === 'SyntaxError') { if (error.name === 'SyntaxError') {
throw new Error( throw new Error(
`Project settings could not be retrieved. To link your project again, remove the ${dir} directory.` `Project Settings could not be retrieved. To link your project again, remove the ${dir} directory.`
); );
} }
@@ -141,13 +142,27 @@ export async function getLinkedProject(
} }
const spinner = output.spinner('Retrieving project…', 1000); const spinner = output.spinner('Retrieving project…', 1000);
let org: Org | null; let org: Org | null = null;
let project: Project | ProjectNotFound | null; let project: Project | ProjectNotFound | null = null;
try { try {
[org, project] = await Promise.all([ [org, project] = await Promise.all([
getOrgById(client, link.orgId), getOrgById(client, link.orgId),
getProjectByIdOrName(client, link.projectId, link.orgId), getProjectByIdOrName(client, link.projectId, link.orgId),
]); ]);
} catch (err) {
if (err?.status === 403) {
spinner();
throw new NowBuildError({
message: `Could not retrieve Project Settings. To link your Project, remove the ${outputCode(
'.vercel'
)} directory and deploy again.`,
code: 'PROJECT_UNAUTHORIZED',
link: 'https://vercel.link/cannot-load-project-settings',
});
}
// Not a special case 403, we should still throw it
throw err;
} finally { } finally {
spinner(); spinner();
} }

View File

@@ -6,7 +6,6 @@ export default async function responseError(
fallbackMessage = null, fallbackMessage = null,
parsedBody = {} parsedBody = {}
) { ) {
let message;
let bodyError; let bodyError;
if (res.status >= 400 && res.status < 500) { if (res.status >= 400 && res.status < 500) {
@@ -20,12 +19,9 @@ export default async function responseError(
// Some APIs wrongly return `err` instead of `error` // Some APIs wrongly return `err` instead of `error`
bodyError = body.error || body.err || body; bodyError = body.error || body.err || body;
message = bodyError.message;
} }
if (message == null) { const msg = bodyError?.message || fallbackMessage || 'Response Error';
message = fallbackMessage === null ? 'Response Error' : fallbackMessage;
}
return new APIError(message, res, bodyError); return new APIError(msg, res, bodyError);
} }

View File

@@ -71,7 +71,7 @@ function testFixture(name, fn) {
} }
function validateResponseHeaders(t, res, podId = null) { function validateResponseHeaders(t, res, podId = null) {
t.is(res.headers.get('server'), 'now'); t.is(res.headers.get('server'), 'Vercel');
t.truthy(res.headers.get('cache-control').length > 0); t.truthy(res.headers.get('cache-control').length > 0);
t.truthy( t.truthy(
/^dev1::(dev1::)?[0-9a-z]{5}-[1-9][0-9]+-[a-f0-9]{12}$/.test( /^dev1::(dev1::)?[0-9a-z]{5}-[1-9][0-9]+-[a-f0-9]{12}$/.test(

View File

@@ -0,0 +1,283 @@
import test from 'ava';
import { validateConfig } from '../src/util/dev/validate';
test('[dev-validate] should not error with empty config', async (t) => {
const config = {};
const error = validateConfig(config);
t.deepEqual(error, null);
});
test('[dev-validate] should not error with complete config', async (t) => {
const config = {
version: 2,
public: true,
regions: ['sfo1', 'iad1'],
cleanUrls: true,
headers: [{ source: '/', headers: [{ key: 'x-id', value: '123' }] }],
rewrites: [{ source: '/help', destination: '/support' }],
redirects: [{ source: '/kb', destination: 'https://example.com' }],
trailingSlash: false,
functions: { 'api/user.go': { memory: 128, maxDuration: 5 } },
};
const error = validateConfig(config);
t.deepEqual(error, null);
});
test('[dev-validate] should not error with builds and routes', async (t) => {
const config = {
builds: [{ src: 'api/index.js', use: '@vercel/node' }],
routes: [{ src: '/(.*)', dest: '/api/index.js' }],
};
const error = validateConfig(config);
t.deepEqual(error, null);
});
test('[dev-validate] should error with invalid rewrites due to additional property and offer suggestion', async (t) => {
const config = {
rewrites: [{ src: '/(.*)', dest: '/api/index.js' }],
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'Invalid vercel.json - `rewrites[0]` should NOT have additional property `src`. Did you mean `source`?'
);
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/rewrites'
);
});
test('[dev-validate] should error with invalid routes due to additional property and offer suggestion', async (t) => {
const config = {
routes: [{ source: '/(.*)', destination: '/api/index.js' }],
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'Invalid vercel.json - `routes[0]` should NOT have additional property `source`. Did you mean `src`?'
);
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/routes'
);
});
test('[dev-validate] should error with invalid routes array type', async (t) => {
const config = {
routes: { src: '/(.*)', dest: '/api/index.js' },
};
const error = validateConfig(config);
t.deepEqual(error.message, 'Invalid vercel.json - `routes` should be array.');
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/routes'
);
});
test('[dev-validate] should error with invalid redirects array object', async (t) => {
const config = {
redirects: [
{
/* intentionally empty */
},
],
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'Invalid vercel.json - `redirects[0]` missing required property `source`.'
);
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/redirects'
);
});
test('[dev-validate] should error with invalid redirects.permanent poperty', async (t) => {
const config = {
redirects: [{ source: '/', destination: '/go', permanent: 'yes' }],
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'Invalid vercel.json - `redirects[0].permanent` should be boolean.'
);
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/redirects'
);
});
test('[dev-validate] should error with invalid cleanUrls type', async (t) => {
const config = {
cleanUrls: 'true',
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'Invalid vercel.json - `cleanUrls` should be boolean.'
);
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/cleanurls'
);
});
test('[dev-validate] should error with invalid trailingSlash type', async (t) => {
const config = {
trailingSlash: [true],
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'Invalid vercel.json - `trailingSlash` should be boolean.'
);
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/trailingslash'
);
});
test('[dev-validate] should error with invalid headers property', async (t) => {
const config = {
headers: [{ 'Content-Type': 'text/html' }],
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'Invalid vercel.json - `headers[0]` should NOT have additional property `Content-Type`. Please remove it.'
);
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/headers'
);
});
test('[dev-validate] should error with invalid headers.source type', async (t) => {
const config = {
headers: [{ source: [{ 'Content-Type': 'text/html' }] }],
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'Invalid vercel.json - `headers[0].source` should be string.'
);
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/headers'
);
});
test('[dev-validate] should error with invalid headers additional property', async (t) => {
const config = {
headers: [{ source: '/', stuff: [{ 'Content-Type': 'text/html' }] }],
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'Invalid vercel.json - `headers[0]` should NOT have additional property `stuff`. Please remove it.'
);
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/headers'
);
});
test('[dev-validate] should error with invalid headers wrong nested headers type', async (t) => {
const config = {
headers: [{ source: '/', headers: [{ 'Content-Type': 'text/html' }] }],
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'Invalid vercel.json - `headers[0].headers[0]` should NOT have additional property `Content-Type`. Please remove it.'
);
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/headers'
);
});
test('[dev-validate] should error with invalid headers wrong nested headers additional property', async (t) => {
const config = {
headers: [
{ source: '/', headers: [{ key: 'Content-Type', val: 'text/html' }] },
],
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'Invalid vercel.json - `headers[0].headers[0]` should NOT have additional property `val`. Please remove it.'
);
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/headers'
);
});
test('[dev-validate] should error with too many redirects', async (t) => {
const config = {
redirects: Array.from({ length: 5000 }).map((_, i) => ({
source: `/${i}`,
destination: `/v/${i}`,
})),
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'Invalid vercel.json - `redirects` should NOT have more than 1024 items.'
);
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/redirects'
);
});
test('[dev-validate] should error with too many nested headers', async (t) => {
const config = {
headers: [
{
source: '/',
headers: [{ key: `x-id`, value: `123` }],
},
{
source: '/too-many',
headers: Array.from({ length: 5000 }).map((_, i) => ({
key: `${i}`,
value: `${i}`,
})),
},
],
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'Invalid vercel.json - `headers[1].headers` should NOT have more than 1024 items.'
);
t.deepEqual(
error.link,
'https://vercel.com/docs/configuration#project/headers'
);
});
test('[dev-validate] should error with "functions" and "builds"', async (t) => {
const config = {
builds: [
{
src: 'index.html',
use: '@vercel/static',
},
],
functions: {
'api/test.js': {
memory: 1024,
},
},
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.'
);
t.deepEqual(error.link, 'https://vercel.link/functions-and-builds');
});

View File

@@ -0,0 +1 @@
proof goes here

View File

@@ -3,7 +3,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build": "gridsome build", "build": "gridsome build",
"develop": "gridsome develop", "dev": "gridsome develop -p $PORT",
"explore": "gridsome explore" "explore": "gridsome explore"
}, },
"dependencies": { "dependencies": {

View File

@@ -0,0 +1,14 @@
<template>
<Layout>
<h1>Not Found</h1>
<p>This is a Custom Gridsome 404.</p>
</Layout>
</template>
<script>
export default {
metaInfo: {
title: 'Not Found'
}
}
</script>

View File

@@ -1,11 +1,11 @@
<template> <template>
<Layout> <Layout>
<!-- Learn how to use images here: https://gridsome.org/docs/images --> <!-- Learn how to use images here: https://gridsome.org/docs/images -->
<g-image alt="Example image" src="~/favicon.png" width="135" /> <g-image alt="Example image" src="~/favicon.png" width="135" />
<h1>Hello, world!</h1> <h1>Gridsome on Vercel</h1>
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Pariatur excepturi labore tempore expedita, et iste tenetur suscipit explicabo! Dolores, aperiam non officia eos quod asperiores Lorem ipsum dolor sit amet, consectetur adipisicing elit. Pariatur excepturi labore tempore expedita, et iste tenetur suscipit explicabo! Dolores, aperiam non officia eos quod asperiores
</p> </p>

View File

@@ -0,0 +1,3 @@
{
"redirects": [{ "source": "/support", "destination": "/about?ref=support" }]
}

View File

@@ -0,0 +1 @@
<h1>Contact Us</h1>

View File

@@ -0,0 +1,3 @@
{
"rewrites": [{ "source": "/support", "destination": "/contact.html" }]
}

View File

@@ -0,0 +1,3 @@
export default function Contact() {
return <h1>Contact Page</h1>;
}

View File

@@ -0,0 +1,3 @@
{
"rewrites": [{ "source": "/support", "destination": "/contact" }]
}

View File

@@ -1,7 +1,7 @@
{ {
"functions": { "functions": {
"api/user.sh": { "api/user.sh": {
"runtime": "vercel-bash@3.0.7" "runtime": "vercel-bash@3.0.8"
} }
} }
} }

View File

@@ -1 +1 @@
{ "env": { "FOO": "bar" } } {"env":{"FOO":"bar"}}

View File

@@ -0,0 +1,6 @@
module.exports = {
assetPrefix: '/blog',
env: {
ASSET_PREFIX: '/blog',
},
};

View File

@@ -0,0 +1,10 @@
{
"name": "with-zones-blog",
"version": "1.0.0",
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest"
},
"license": "ISC"
}

View File

@@ -0,0 +1,22 @@
import Link from 'next/link';
export default function Blog() {
return (
<div>
<h3>This is our blog</h3>
<ul>
<li>
<Link href="/blog/post/[id]" as="/blog/post/1">
<a>Post 1</a>
</Link>
</li>
<li>
<Link href="/blog/post/[id]" as="/blog/post/2">
<a>Post 2</a>
</Link>
</li>
</ul>
<a href="/">Home</a>
</div>
);
}

View File

@@ -0,0 +1,15 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
export default function Post() {
const router = useRouter()
return (
<div>
<h3>Post #{router.query.id}</h3>
<p>Lorem ipsum</p>
<Link href="/blog">
<a>Back to blog</a>
</Link>
</div>
)
}

View File

@@ -0,0 +1,7 @@
const Header = () => (
<div>
<h2>The Company</h2>
</div>
);
export default Header;

View File

@@ -0,0 +1,10 @@
{
"name": "with-zones-home",
"version": "1.0.0",
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest"
},
"license": "ISC"
}

View File

@@ -0,0 +1,21 @@
import Link from 'next/link';
const About = ({ id }) => {
return (
<div>
<p>This is the {id} page with static props.</p>
<div>
<Link href="/">
<a>Go Back</a>
</Link>
</div>
</div>
);
};
export const getStaticProps = ({ params }) => {
return { props: { id: params.id } };
};
export const getStaticPaths = () => ({ paths: ['/1/dynamic'], fallback: true });
export default About;

View File

@@ -0,0 +1,19 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
const About = () => {
const router = useRouter();
const { id } = router.query;
return (
<div>
<p>This is the {id} page without static props.</p>
<div>
<Link href="/">
<a>Go Back</a>
</Link>
</div>
</div>
);
};
export default About;

View File

@@ -0,0 +1,19 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
const About = () => {
const router = useRouter();
const { id } = router.query;
return (
<div>
<p>This is the about static page.</p>
<div>
<Link href="/">
<a>Go Back</a>
</Link>
</div>
</div>
);
};
export default About;

View File

@@ -0,0 +1,36 @@
import Link from 'next/link';
import dynamic from 'next/dynamic';
const Header = dynamic(import('../components/Header'));
export default function Home() {
return (
<div>
<Header />
<p>This is our homepage</p>
<div>
<a href="/blog">Blog</a>
</div>
<div>
<Link href="/about">
<a>About us</a>
</Link>
</div>
<div>
<Link href="/foo">
<a>foo</a>
</Link>
</div>
<div>
<Link href="/1/dynamic">
<a>1/dynamic</a>
</Link>
</div>
<div>
<Link href="/1/foo">
<a>1/foo</a>
</Link>
</div>
</div>
);
}

View File

@@ -0,0 +1,11 @@
{
"name": "with-zones-example",
"private": true,
"workspaces": [
"home",
"blog"
],
"devDependencies": {
"vercel": "canary"
}
}

View File

@@ -0,0 +1,12 @@
{
"version": 2,
"builds": [
{ "src": "blog/package.json", "use": "@vercel/next@canary" },
{ "src": "home/package.json", "use": "@vercel/next@canary" }
],
"routes": [
{ "src": "/blog/_next(.*)", "dest": "blog/_next$1" },
{ "src": "/blog(.*)", "dest": "blog/blog$1" },
{ "src": "(.*)", "dest": "home$1" }
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
{ "builds": [{ "src": "index.js", "use": "@vercel/node@canary" }] } {"builds":[{"src":"index.js","use":"@vercel/node@canary"}]}

View File

@@ -0,0 +1,3 @@
# Created by Vercel CLI
VERCEL_REGION=""
VERCEL_URL=""

View File

@@ -0,0 +1,4 @@
module.exports = (req, res) => {
const months = [...Array(12).keys()].map(month => month + 1);
res.json({ months });
};

View File

@@ -0,0 +1,6 @@
module.exports = (req, res) => {
res.send({
env: process.env,
headers: req.headers,
});
};

View File

@@ -0,0 +1,15 @@
const { PCRE } = require('pcre-to-regexp');
// `ts-node` default "target" is "es5" which transpiles `class` statements and
// is incompatible with dependencies that use ES6 native `class`. Setting the
// "target" to "es2018" or newer prevents the `class` transpilation.
//
// See: https://github.com/vercel/vercel/discussions/4724
// See: https://github.com/TypeStrong/ts-node/issues/903
class P extends PCRE {}
export default (req, res) => {
const p = new P('hi'); // This line should not throw an error
console.log(p);
res.send({ ok: true });
};

View File

@@ -0,0 +1,15 @@
{
"name": "node-ts-node-target",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"pcre-to-regexp": "1.1.0"
}
}

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