Compare commits

..

63 Commits

Author SHA1 Message Date
Steven
46c8cb1a68 Publish Stable
- @now/build-utils@1.3.2
 - @now/next@2.3.7
 - @now/node@1.3.3
 - @now/static-build@0.14.3
2020-01-06 18:44:26 -05:00
Steven
faeb053ea6 Publish Canary
- @now/build-utils@1.3.2-canary.1
 - now@16.7.2-canary.3
 - @now/next@2.3.7-canary.1
 - @now/node@1.3.3-canary.1
 - @now/static-build@0.14.3-canary.1
2020-01-06 18:30:34 -05:00
Steven
708a09b86a [now-build-utils] Add usage of package.json engines (#3512)
Add a better upgrade message that shows usage of `engines` in the `package.json` file.
2020-01-06 18:29:08 -05:00
Steven
89403e93e4 Publish Canary
- @now/build-utils@1.3.2-canary.0
 - @now/next@2.3.7-canary.0
 - @now/node@1.3.3-canary.0
 - @now/static-build@0.14.3-canary.0
2020-01-06 17:01:47 -05:00
Steven
ecb0c08fe2 [now-build-utils] Fix now dev to use system node (#3509)
This PR will use the system installed version of Node.js and avoid printing a warning or error if a discontinued version is selected.

This optimization was already in `@now/node` but for some reason it was never add to `@now/next`.

The reason why its relevant today is because the warnings turned into errors due to Node 8 deprecation and we don't have the "Project" in `now dev` so we don't know which version of node to select.

So instead of determining the version, `now dev` will always use `node` in the PATH and avoid printing warnings or errors. This also results in less FS reads since we no longer need to read package.json.
2020-01-06 17:00:46 -05:00
Andy Bitz
0b88c158b9 Publish Stable
- @now/frameworks@0.0.3
2020-01-06 20:15:04 +01:00
Andy
ec3a38107a [frameworks] Add settings to frameworks (#3506)
* [frameworks] Add `settings` to frameworks

* Fix svelte

* Add `outputDirectory`

* Update Next.js outputDirectory placeholder

* Type settings

Co-authored-by: Leo Lamprecht <mindrun@icloud.com>
2020-01-06 20:12:38 +01:00
Steven
0c0f1c6eb5 Publish Stable
- @now/build-utils@1.3.1
 - @now/next@2.3.6
 - @now/node@1.3.2
 - @now/static-build@0.14.2
2020-01-06 13:21:41 -05:00
Steven
ed296ef733 Publish Canary
- @now/build-utils@1.3.1-canary.1
 - now@16.7.2-canary.2
 - @now/static-build@0.14.2-canary.0
2020-01-06 13:10:05 -05:00
Steven
246f47ec95 [now-static-build] Fix distDir when zero config framework is detected (#3507)
There was a regression with framework detection that was preferring the frameworks output directory instead of the `distDir` defined in the `@now/static-build`.

The fix is to only run framework detection when `builds` is not defined (ie zero config).

Related to https://github.com/Ebury/chameleon/pull/63
2020-01-06 18:07:25 +00:00
Steven
9d95b99b72 [now-build-utils] Add support for dynamic handle: miss (#3457)
This extends the behavior of `featHandleMiss: true` flag to do the following:

- Reduce zero config API routes so that only dynamic path segment files (`api/[id].js`) get a route.
- Remove zero config out directory route (`public/`)—the files will be renamed instead.
- Use redirects for API routes when `cleanUrls: true` and use rewrites when `cleanUrls: false` from extension to the extension-less file.
- Normalize existing routes to begin with `^` and end with `$`.
2020-01-06 16:07:47 +00:00
Steven
3de8ae9d7e Publish Canary
- @now/build-utils@1.3.1-canary.0
 - @now/node@1.3.2-canary.0
2020-01-06 10:19:23 -05:00
Steven
44f6e1904e [now-build-utils][now-node] Throw new NowBuildError() (#3496)
This PR does the following

- Add and export class, `NowBuildError`, that can be thrown to stop the build and print an error but it will not print a stack trace.
- Improve logic for discontinued node versions and add more tests
- Remove hack (#3425) for undefined TERM, fixed by updating dependencies
- Rename `silent` variable to `isAuto` which means the node version was automatically selected, not defined in `package.json`
- Rename `test` deployments to `test2020` so that a fresh project is created with latest Node.js 12.x
2020-01-06 15:00:27 +00:00
Max Rovensky
d9c84fc4ce Publish Canary
- now@16.7.2-canary.1
 - @now/python@1.1.1-canary.0
2020-01-06 21:45:01 +08:00
Max
b5142d935b Fix event order in CLI (#3502)
This should fix [PRODUCT-893](https://zeit.atlassian.net/browse/PRODUCT-893) and [PRODUCT-941](https://zeit.atlassian.net/browse/PRODUCT-941)
2020-01-06 13:43:44 +00:00
luc
718a451110 Publish Canary
- now@16.7.2-canary.0
 - now-client@6.0.2-canary.0
2020-01-06 11:24:45 +01:00
Andy
9755847855 [now-client] Ignore .env.* files by default (#3436)
Ignore `.env.*` files by default.

[PRODUCT-804]

[PRODUCT-804]: https://zeit.atlassian.net/browse/PRODUCT-804
2020-01-06 10:19:55 +00:00
Luc
abc417b6b3 [now-client] Use @zeit/fetch instead of node-fetch (#3490)
We want to take advantage of the better defaults and retry policies (among others).

This bumps `@zeit/fetch` and replaces `node-fetch` with `@zeit/fetch` in `now-client`.

Also update retry policy for "uploading" files to:
- base 10ms
- 5 retries
- factor 6

which means the timeouts are 10, 60, 360, 2160, 12960
2020-01-06 09:01:17 +00:00
Steven
d6f71c8d7b Publish Stable
- @now/frameworks@0.0.2
 - @now/build-utils@1.3.0
 - now@16.7.1
 - now-client@6.0.1
 - @now/next@2.3.5
 - @now/node@1.3.1
 - @now/python@1.1.0
 - @now/routing-utils@1.5.0
 - @now/static-build@0.14.1
2020-01-03 12:39:08 -05:00
JJ Kasper
d90892dc9c Publish Stable
- @now/next@2.3.4
2020-01-03 08:45:34 -06:00
Tim Neutkens
60d2f8b96c Publish Canary
- @now/next@2.3.4-canary.1
2020-01-03 12:36:15 +01:00
JJ Kasper
2488adf80d [now-next] Add support for experimental basePath (#3478)
* Add support for experimental basePath

* Update base-path fixture

* Update type

* Update basePath route source and add invariants
2020-01-03 12:32:20 +01:00
Steven
9deb5b31d2 Publish Canary
- now@16.7.1-canary.5
2020-01-02 19:32:01 -05:00
Steven
ae55823c3c Publish Canary
- @now/frameworks@0.0.2-canary.3
 - @now/build-utils@1.2.1-canary.7
 - @now/static-build@0.14.1-canary.8
2020-01-02 18:09:49 -05:00
Andy
d3395553fe [frameworks][now-static-build] Add support for Scully (#3469)
Add support for Scully.
2020-01-02 22:25:57 +00:00
JJ Kasper
e742dd3a48 Publish Canary
- @now/next@2.3.4-canary.0
2020-01-02 13:24:06 -06:00
JJ Kasper
4f0f44e746 Publish Stable
- @now/next@2.3.3
2020-01-02 13:18:38 -06:00
JJ Kasper
0da98a7f5d Revert "[now-next] Implement handle: miss for custom-routes (#3456)" (#3488)
This reverts commit 40bbff9bee.
2020-01-02 13:15:16 -06:00
Steven
685989ae57 Publish Canary
- @now/build-utils@1.2.1-canary.6
 - @now/next@2.3.3-canary.0
 - @now/node@1.3.1-canary.1
 - @now/static-build@0.14.1-canary.7
2020-01-02 12:42:36 -05:00
Steven
6bc121e7b1 [now-build-utils] Add support for getLatestNodeVersion() and config.nodeVersion (#3483)
This PR does a few things:

1. Add functions `getLatestNodeVersion()` and `getOldestNodeVersion()` for use in api-project.
2. Add property `config.nodeVersion` which has precedence over default Node 8.

We want new projects to use the latest node version without setting any configuration but we don't want to break old projects. So during project creation, the value of `getLatestNodeVersion()` will be saved and then each deployment of that project will assign that value to `config.nodeVersion`. 

Implements [PRODUCT-837]

[PRODUCT-837]: https://zeit.atlassian.net/browse/PRODUCT-837
2020-01-02 17:34:31 +00:00
JJ Kasper
56d3fed954 Publish Stable
- @now/next@2.3.2
2019-12-31 15:56:54 -06:00
JJ Kasper
40bbff9bee [now-next] Implement handle: miss for custom-routes (#3456)
This implements `{ handle: 'miss' }` which will allow more efficiently checking dynamic routes after a matching rewrite. This is not fully available in production yet and the tests will fail for this PR until it is ready in Now

x-ref: https://github.com/zeit/next.js/pull/9568
2019-12-31 19:36:50 +00:00
Steven
66ab011f4a Publish Canary
- @now/next@2.3.2-canary.0
 - @now/node@1.3.1-canary.0
2019-12-30 13:48:36 -05:00
Steven
f4237d3db0 [now-node][now-next] Bump node-file-trace to 0.4.1 (#3481)
Bumps `node-file-trace` to version [0.4.1](https://github.com/zeit/node-file-trace/releases/tag/0.4.1).

- Add special case for semver@7: [#87](https://github.com/zeit/node-file-trace/pull/87)
- Handle babel/ts compiled imports: [#79](https://github.com/zeit/node-file-trace/pull/79)
2019-12-30 18:26:32 +00:00
JJ Kasper
6f9a083dba [now-next] Add handling for new routes-manifest version (#3482)
This handles the new `routes-manifest` version in the latest canary of Next.js since we throw an error if the version isn't handled by `@now/next`

unblocks: https://github.com/zeit/now/pull/3481
2019-12-30 17:58:19 +00:00
dependabot[bot]
688fcc6a5b Bump handlebars from 4.1.2 to 4.5.3 (#3476)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.1.2 to 4.5.3.
<details>
<summary>Changelog</summary>

*Sourced from [handlebars's changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md).*

> ## v4.5.3 - November 18th, 2019
> Bugfixes:
> 
> - fix: add "no-prototype-builtins" eslint-rule and fix all occurences - f7f05d7
> - fix: add more properties required to be enumerable - 1988878
> 
> Chores / Build:
> - fix: use !== 0 instead of != 0 - c02b05f
> - add chai and dirty-chai and sinon, for cleaner test-assertions and spies, 
>   deprecate old assertion-methods  - 93e284e, 886ba86, 0817dad, 93516a0
> 
> Security:
> 
> - The properties `__proto__`, `__defineGetter__`, `__defineSetter__` and `__lookupGetter__` 
>   have been added to the list of "properties that must be enumerable".
>   If a property by that name is found and not enumerable on its parent, 
>   it will silently evaluate to `undefined`. This is done in both the compiled template and the "lookup"-helper. 
>   This will prevent new Remote-Code-Execution exploits that have been
>   published recently.
> 
> Compatibility notes: 
> 
> - Due to the security-fixes. The semantics of the templates using
>   `__proto__`, `__defineGetter__`, `__defineSetter__` and `__lookupGetter__` in the respect that those expression now return 
>   `undefined` rather than their actual value from the proto.
> - The semantics have not changed in cases where the properties are  enumerable, as in:
> 
> ```js
> {
>   __proto__: 'some string'
> }
> ```
> 
> - The change may be breaking in that respect, but we still only 
>   increase the patch-version, because the incompatible use-cases
>   are not intended, undocumented and far less important than fixing
>   Remote-Code-Execution exploits on existing systems.
> 
> 
> 
> [Commits](https://github.com/wycats/handlebars.js/compare/v4.5.2...v4.5.3)
> 
> ## v4.5.2 - November 13th, 2019
> # Bugfixes
> 
> - fix: use String(field) in lookup when checking for "constructor" - d541378
> - test: add fluent API for testing Handlebars - c2ac79c
> 
> Compatibility notes:
> - no incompatibility are to be expected
></tr></table> ... (truncated)
</details>
<details>
<summary>Commits</summary>

- [`c819c8b`](c819c8b533) v4.5.3
- [`827c9d0`](827c9d0747) Update release notes
- [`f7f05d7`](f7f05d7558) fix: add "no-prototype-builtins" eslint-rule and fix all occurences
- [`1988878`](1988878087) fix: add more properties required to be enumerable
- [`886ba86`](886ba86c2f) test/chore: add chai/expect and sinon to "runtime"-environment
- [`0817dad`](0817dad7e7) test: add sinon as global variable to eslint in the specs
- [`93516a0`](93516a0b07) test: add sinon.js for spies, deprecate current assertions
- [`93e284e`](93e284ed9b) chore: add chai and dirty-chai for better test assertions
- [`c02b05f`](c02b05fa81) fix: use !== 0 instead of != 0
- [`8de121d`](8de121d21c) v4.5.2
- Additional commits viewable in [compare view](https://github.com/wycats/handlebars.js/compare/v4.1.2...v4.5.3)
</details>
<br />

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=handlebars&package-manager=npm_and_yarn&previous-version=4.1.2&new-version=4.5.3)](https://help.github.com/articles/configuring-automated-security-fixes)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot ignore this [patch|minor|major] version` will close this PR and stop Dependabot creating any more for this minor/major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language

You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/zeit/now/network/alerts).

</details>
2019-12-30 14:13:42 +00:00
Shu Ding
847102cf62 Publish Canary
- @now/frameworks@0.0.2-canary.2
2019-12-28 07:23:53 +08:00
Shu Ding
25d5b9c9cf add missing logos (#3475) 2019-12-28 07:22:47 +08:00
Nathan Rajlich
271bab786e Publish Canary
- now@16.7.1-canary.4
 - now-client@6.0.1-canary.1
 - @now/routing-utils@1.4.1-canary.3
2019-12-23 17:33:19 -08:00
Nathan Rajlich
028e023aba [now-routing-utils] Make status code be configurable (#3470)
Now v1 static deployments use 301 by default, so this change allows the routing utils to match that behavior.
2019-12-24 00:56:47 +00:00
Steven
39719eed20 [now-client] Fix single file deploy with new routing properties (#3465)
There is an undocumented feature that adds a route when a single file is deployed, for example a single image or a zip file. This was causing the legacy routes to be added even when `rewrites` or `redirects` was defined. This PR fixes that case when the user has a single file, `now.json`, with redirects defined.
2019-12-24 00:07:30 +00:00
Andy Bitz
438339258d Publish Canary
- @now/build-utils@1.2.1-canary.5
2019-12-23 22:21:11 +01:00
Andy
be445c987c [now-build-utils] Only add 404 route when there are no api routes (#3468)
* [now-build-utils] Only add 404 route when there are no api routes

* Fix startWith

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

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

* Import isHandler

Co-authored-by: Steven <steven@ceriously.com>
2019-12-23 22:20:10 +01:00
Andy Bitz
93fef7885b Publish Canary
- @now/static-build@0.14.1-canary.6
2019-12-23 18:32:49 +01:00
Andy
899c9962ad [now-static-build] Ensure outputDirectory is used when it exists (#3466) 2019-12-23 18:32:29 +01:00
Andy Bitz
2b601d2424 Publish Canary
- @now/static-build@0.14.1-canary.5
2019-12-23 17:15:20 +01:00
Andy
3e36b05434 [now-static-build] Fix outputDirectory and add test (#3464) 2019-12-23 17:14:19 +01:00
Andy Bitz
59c9665c3f Publish Canary
- @now/static-build@0.14.1-canary.4
2019-12-23 15:57:53 +01:00
Andy
901137c7f6 [now-static-build] Add missing frameworks (#3463)
* [now-static-build] Add missing frameworks

* Update test

* Revert "Update test"

This reverts commit 86b2169caf145200b164b0c2f860c22d50b495cb.
2019-12-23 15:55:41 +01:00
Andy Bitz
e594e7bbbb Publish Canary
- @now/build-utils@1.2.1-canary.4
2019-12-23 10:02:00 +01:00
Andy
a477b1c22e [now-build-utils] Add option to ignore build script (#3461) 2019-12-23 10:00:45 +01:00
Andy Bitz
22ac20d838 Publish Canary
- @now/frameworks@0.0.2-canary.1
 - @now/build-utils@1.2.1-canary.3
 - now@16.7.1-canary.3
 - now-client@6.0.1-canary.0
 - @now/static-build@0.14.1-canary.3
2019-12-20 16:30:42 +01:00
Andy
3794234d7a [now-build-utils][now-static-build][now-cli] Update detectors and undo previous changes (#3452)
* Revert detect-builders and detect-routes changes

* Add `projectSettings` and support non-api routes

* Start with framework detectors

* Update @now/static-build

* Update now-dev

* Update tests

* Use local builders fro testing with now-dev

* Add outputDirectory to @now/static builds

* Ignore update only for bundeled builders

* Revert now-dev builder changes

* Revert more changes

* Adjust tests further

* Update gridsome test

* Adjust hugo test

* Undo version change

* Read file instead of importing

* Ignore failed.page.txt

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

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

* Simplify outputDirectory

* Remove NODE_ENV

* Convert tests to typescript

* Remove console.log

Co-authored-by: Steven <steven@ceriously.com>
2019-12-20 16:29:39 +01:00
Andy Bitz
92a40db048 Publish Stable
- @now/next@2.3.1
2019-12-20 14:28:26 +01:00
Steven
502aad7c2b Publish Canary
- @now/build-utils@1.2.1-canary.2
 - now@16.7.1-canary.2
 - @now/next@2.3.1-canary.3
 - @now/python@1.0.2-canary.0
 - @now/static-build@0.14.1-canary.2
2019-12-19 13:33:02 -05:00
Steven
b49afb61a6 [now-build-utils] Add handle: miss route to zero config (#3403)
This PR adds the route `handle: miss` and a catch-all route for the api directory.

The plan is to rename files in `/api` and `/public` (in a future PR) to be extension-less and then the route is only used to rewrite the extension to the extension-less file (for example, `/api/user.go` => `/api/user`)

This reduces the routes needed for zero config (previously N routes for N files down to 1 route for N files).
2019-12-19 18:30:48 +00:00
Steven
d380902ad3 [all] Fix types for Route vs Source (#3450)
This PR deletes the incorrect `Route` type defined in `@now/build-utils` and instead relies on the correct type defined in `@now/routing-utils`. There is no change to runtime code since this is strictly a change to the typescript types. The one exception to this is I had to change the sort order for our build script so that `@now/routing-utils` is built first.

This is necessary for PR #3403
2019-12-18 23:10:26 +00:00
Andy
ffaed62094 [now-dev] Update tests with verbose logging (#3447)
Updates the tests for `now dev` to log more and make the hopefully more reliable.
2019-12-18 21:57:59 +00:00
Tommaso De Rossi
b0adeb68fe [now-python] Skip reinstalling user dependencies for now dev (#3352)
Same as #2926 but for python
Fixes #3351
2019-12-18 21:04:06 +00:00
JJ Kasper
2372832654 Publish Canary
- @now/next@2.3.1-canary.2
 - @now/routing-utils@1.4.1-canary.2
2019-12-18 13:57:42 -06:00
JJ Kasper
e6a9586b7e [now-routing-utils] Add replacing of multi-match characters (#3446)
This makes sure to replace multi-match characters used in `path-to-regexp` when converting redirects.

Fixes `/:path*/` being converted to `/$1*/` and now converts it to `/$1/`
2019-12-18 19:43:58 +00:00
Tim Neutkens
9687415eed Publish Canary
- @now/next@2.3.1-canary.1
2019-12-18 10:54:34 +01:00
JJ Kasper
49b375ed6a [now-next] Disable running next export from export-intent (#3448)
As requested by @timneutkens after investigating we're disabling auto running `next export` for the user during a build since most users who want their app exported will define `next export` in their `build` script
2019-12-18 03:01:33 +00:00
255 changed files with 8095 additions and 3702 deletions

1
.gitignore vendored
View File

@@ -19,4 +19,5 @@ packages/now-cli/test/dev/fixtures/08-hugo/hugo
packages/now-cli/test/dev/fixtures/**/dist
packages/now-cli/test/dev/fixtures/**/public
packages/now-cli/test/fixtures/integration
test/lib/deployment/failed-page.txt
.DS_Store

View File

@@ -4,70 +4,595 @@
"slug": "nextjs",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/next.svg",
"tagline": "Next.js makes you productive with React instantly — whether you want to build static or dynamic sites. ",
"website": "https://nextjs.org"
"website": "https://nextjs.org",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"next\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`next build` or `build` from `package.json`"
},
"devCommand": {
"value": "next dev --port $PORT"
},
"outputDirectory": {
"placeholder": "Next.js default"
}
}
},
{
"name": "Gatsby",
"slug": "gatsby",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/gatsby.svg",
"tagline": "Gatsby helps developers build blazing fast websites and apps with React.",
"website": "https://gatsbyjs.org"
"website": "https://gatsbyjs.org",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"gatsby\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`gatsby build` or `build` from `package.json`"
},
"devCommand": {
"value": "gatsby develop --port $PORT"
},
"outputDirectory": {
"value": "public"
}
}
},
{
"name": "Nuxt.js",
"slug": "nuxtjs",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/nuxt.svg",
"tagline": "Nuxt.js is the web comprehensive framework that lets you dream big with Vue.js.",
"website": "https://nuxtjs.org"
"name": "Hexo",
"slug": "hexo",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/hexo.svg",
"tagline": "Hexo is a fast, simple & powerful blog framework powered by Node.js.",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"hexo\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`hexo generate` or `build` from `package.json`"
},
"devCommand": {
"value": "hexo server --port $PORT"
},
"outputDirectory": {
"value": "public"
}
}
},
{
"name": "Create-React-App",
"slug": "create-react-app",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/react.svg",
"tagline": "Create React App allows you to get going with React in no time.",
"website": "https://create-react-app.dev"
"name": "Eleventy",
"slug": "eleventy",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/eleventy.svg",
"tagline": "11ty is a simpler static site generator written in JavaScript, created to be an alternative to Jekyll.",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@11ty\\/eleventy\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`npx @11ty/eleventy` or `build` from `package.json`"
},
"devCommand": {
"value": "npx @11ty/eleventy --serve --watch --port $PORT"
},
"outputDirectory": {
"value": "_site"
}
}
},
{
"name": "Svelte",
"slug": "svelte",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/svelte.svg",
"tagline": "Svelte lets you write high performance reactive apps with significantly less boilerplate. ",
"website": "https://svelte.dev"
"name": "Docusaurus",
"slug": "docusaurus",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/docusaurus.svg",
"tagline": "Docusaurus makes it easy to maintain Open Source documentation websites.",
"detectors": {
"some": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"docusaurus\":\\s*\".+?\"[^}]*}"
},
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@docusaurus\\/core\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`docusaurus-build` or `build` from `package.json`"
},
"devCommand": {
"value": "docusaurus-start --port $PORT"
},
"outputDirectory": {
"value": "build"
}
}
},
{
"name": "Preact",
"slug": "preact",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/preact.svg",
"tagline": "Preact is a fast 3kB alternative to React with the same modern API.",
"website": "https://preactjs.com",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"preact-cli\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`preact build` or `build` from `package.json`"
},
"devCommand": {
"value": "preact watch --port $PORT"
},
"outputDirectory": {
"value": "build"
}
}
},
{
"name": "Ember",
"slug": "ember",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ember.svg",
"tagline": "Ember.js helps webapp developers be more productive out of the box.",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"ember-cli\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`ember build` or `build` from `package.json`"
},
"devCommand": {
"value": "ember serve --port $PORT"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
"name": "Vue.js",
"slug": "vue",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/vue.svg",
"tagline": "Vue.js is a versatile JavaScript framework that is as approachable as it is performant.",
"website": "https://vuejs.org"
"website": "https://vuejs.org",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@vue\\/cli-service\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`vue-cli-service build` or `build` from `package.json`"
},
"devCommand": {
"value": "vue-cli-service serve --port $PORT"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
"name": "Scully",
"slug": "scully",
"tagline": "Scully is a static site generator for Angular.",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@scullyio\\/init\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`ng build && scully` or `build` from `package.json`"
},
"devCommand": {
"value": "ng serve --port $PORT"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
"name": "Angular",
"slug": "angular",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/angular.svg",
"tagline": "Angular is a TypeScript-based cross-platform framework from Google.",
"website": "https://angular.io"
"website": "https://angular.io",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@angular\\/cli\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`ng build` or `build` from `package.json`"
},
"devCommand": {
"value": "ng serve --port $PORT"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
"name": "Hugo",
"slug": "hugo",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/hugo.svg",
"tagline": "Hugo is the worlds fastest framework for building websites, written in Go.",
"website": "https://gohugo.io"
"name": "Polymer",
"slug": "polymer",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/polymer.svg",
"tagline": "Polymer is an open-source webapps library from Google, for building using Web Components.",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"polymer-cli\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`polymer build` or `build` from `package.json`"
},
"devCommand": {
"value": "polymer serve --port $PORT"
},
"outputDirectory": {
"value": "build"
}
}
},
{
"name": "Sapper",
"slug": "sapper",
"name": "Svelte",
"slug": "svelte",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/svelte.svg",
"tagline": "Sapper is a framework for building high-performance universal web apps with Svelte.",
"website": "https://sapper.svelte.dev"
"tagline": "Svelte lets you write high performance reactive apps with significantly less boilerplate. ",
"website": "https://svelte.dev",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"sirv-cli\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`rollup -c` or `build` from `package.json`"
},
"devCommand": {
"value": "sirv public --single --dev --port $PORT"
},
"outputDirectory": {
"value": "public"
}
}
},
{
"name": "Create-React-App",
"slug": "create-react-app",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/react.svg",
"tagline": "Create React App allows you to get going with React in no time.",
"website": "https://create-react-app.dev",
"detectors": {
"some": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"react-scripts\":\\s*\".+?\"[^}]*}"
},
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"react-dev-utils\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`react-scripts build` or `build` from `package.json`"
},
"devCommand": {
"value": "react-scripts start"
},
"outputDirectory": {
"value": "build"
}
}
},
{
"name": "Gridsome",
"slug": "gridsome",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/gridsome.svg",
"tagline": "Gridsome is a Vue.js-powered framework for building websites & apps that are fast by default.",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"gridsome\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`gridsome build` or `build` from `package.json`"
},
"devCommand": {
"value": "gridsome develop -p $PORT"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
"name": "UmiJS",
"slug": "umijs",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/umi.svg",
"tagline": "UmiJS is an extensible enterprise-level React application framework.",
"website": "https://umijs.org"
"website": "https://umijs.org",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"umi\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`umi build` or `build` from `package.json`"
},
"devCommand": {
"value": "umi dev --port $PORT"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
"name": "Sapper",
"slug": "sapper",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/svelte.svg",
"tagline": "Sapper is a framework for building high-performance universal web apps with Svelte.",
"website": "https://sapper.svelte.dev",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"sapper\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`sapper export` or `build` from `package.json`"
},
"devCommand": {
"value": "sapper dev --port $PORT"
},
"outputDirectory": {
"value": "__sapper__/export"
}
}
},
{
"name": "Saber",
"slug": "saber",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/saber.svg",
"tagline": "Saber is a framework for building static sites in Vue.js that supports data from any source.",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"saber\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`saber build` or `build` from `package.json`"
},
"devCommand": {
"value": "saber --port $PORT"
},
"outputDirectory": {
"value": "public"
}
}
},
{
"name": "Stencil",
"slug": "stencil",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/stencil.svg",
"tagline": "Stencil is a powerful toolchain for building Progressive Web Apps and Design Systems.",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@stencil\\/core\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`stencil build` or `build` from `package.json`"
},
"devCommand": {
"value": "stencil build --dev --watch --serve --port $PORT"
},
"outputDirectory": {
"value": "www"
}
}
},
{
"name": "Nuxt.js",
"slug": "nuxtjs",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/nuxt.svg",
"tagline": "Nuxt.js is the web comprehensive framework that lets you dream big with Vue.js.",
"website": "https://nuxtjs.org",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"nuxt\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`nuxt build` or `build` from `package.json`"
},
"devCommand": {
"value": "nuxt"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
"name": "Hugo",
"slug": "hugo",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/hugo.svg",
"tagline": "Hugo is the worlds fastest framework for building websites, written in Go.",
"website": "https://gohugo.io",
"detectors": {
"some": [
{
"file": "config.yaml"
},
{
"file": "config.toml"
},
{
"file": "config.json"
}
]
},
"settings": {
"buildCommand": {
"value": "hugo"
},
"devCommand": {
"value": "hugo server -D -w -p $PORT"
},
"outputDirectory": {
"placeholder": "`public` or `publishDir` from the `config` file"
}
}
},
{
"name": "Jekyll",
"slug": "jekyll",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/jekyll.svg",
"tagline": "Jekyll makes it super easy to transform your plain text into static websites and blogs.",
"detectors": {
"every": [
{
"file": "_config.yml"
}
]
},
"settings": {
"buildCommand": {
"value": "jekyll build"
},
"devCommand": {
"value": "bundle exec jekyll serve --watch --port $PORT"
},
"outputDirectory": {
"placeholder": "`_site` or `destination` from `_config.yml`"
}
}
},
{
"name": "Brunch",
"slug": "brunch",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/brunch.svg",
"tagline": "Brunch is a fast and simple webapp build tool with seamless incremental compilation for rapid development.",
"detectors": {
"every": [
{
"file": "brunch-config.js"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`brunch build --production` or `build` from `package.json`"
},
"devCommand": {
"value": "brunch watch --server --port $PORT"
},
"outputDirectory": {
"value": "public"
}
}
},
{
"name": "Middleman",
"slug": "middleman",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/middleman.svg",
"tagline": "Middleman is a static site generator that uses all the shortcuts and tools in modern web development.",
"detectors": {
"every": [
{
"file": "config.rb"
}
]
},
"settings": {
"buildCommand": {
"value": "bundle exec middleman build"
},
"devCommand": {
"value": "bundle exec middleman server -p $PORT"
},
"outputDirectory": {
"value": "build"
}
}
},
{
"name": "Vanilla",
@@ -82,13 +607,6 @@
"tagline": "Storybook is an open source tool for developing UI components in isolation for React, Vue, and Angular.",
"website": "https://storybook.js.org"
},
{
"name": "Preact",
"slug": "preact",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/preact.svg",
"tagline": "Preact is a fast 3kB alternative to React with the same modern API.",
"website": "https://preactjs.com"
},
{
"name": "Docz",
"slug": "docz",
@@ -99,36 +617,20 @@
{
"name": "mdx-deck",
"slug": "mdx-deck",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/mdx-deck.svg",
"tagline": "MDX Deck allows you to swiftly create React MDX-based presentation decks.",
"website": "https://github.com/jxnblk/mdx-deck"
},
{
"name": "Gridsome",
"slug": "gridsome",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/gridsome.svg",
"tagline": "Gridsome is a Vue.js-powered framework for building websites & apps that are fast by default."
},
{
"name": "Aurelia",
"slug": "aurelia",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/aurelia.svg",
"tagline": "Aurelia is an all-in-one framework for building web, desktop, and mobile applications."
},
{
"name": "Ember",
"slug": "ember",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ember.svg",
"tagline": "Ember.js helps webapp developers be more productive out of the box."
},
{
"name": "Docusaurus",
"slug": "docusaurus",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/docusaurus.svg",
"tagline": "Docusaurus makes it easy to maintain Open Source documentation websites."
},
{
"name": "VuePress",
"slug": "vuepress",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/vuepress.png",
"tagline": "VuePress is the performant way to create static sites with Vue.js."
},
{
@@ -140,25 +642,15 @@
{
"name": "Riot.js",
"slug": "riot",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/riot.svg",
"tagline": "Riot.js lets you build user interfaces with custom tags using simple and enjoyable syntax."
},
{
"name": "Jekyll",
"slug": "jekyll",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/jekyll.svg",
"tagline": "Jekyll makes it super easy to transform your plain text into static websites and blogs."
},
{
"name": "Marko.js",
"slug": "marko",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/marko.png",
"tagline": "Marko is a super fast UI library that makes building web apps fun."
},
{
"name": "Hexo",
"slug": "hexo",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/hexo.svg",
"tagline": "Hexo is a fast, simple & powerful blog framework powered by Node.js."
},
{
"name": "Mithril.js",
"slug": "mithril",
@@ -168,6 +660,7 @@
{
"name": "Metalsmith",
"slug": "metalsmith",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/metalsmith.png",
"tagline": "Metalsmith is an extremely simple, extendable static site generator."
},
{
@@ -176,39 +669,16 @@
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/hyperapp.svg",
"tagline": "HyperApp is a low-footprint framework for building web interfaces without a learning curve."
},
{
"name": "Polymer",
"slug": "polymer",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/polymer.svg",
"tagline": "Polymer is an open-source webapps library from Google, for building using Web Components."
},
{
"name": "Brunch",
"slug": "brunch",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/brunch.svg",
"tagline": "Brunch is a fast and simple webapp build tool with seamless incremental compilation for rapid development."
},
{
"name": "Saber",
"slug": "saber",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/saber.svg",
"tagline": "Saber is a framework for building static sites in Vue.js that supports data from any source."
},
{
"name": "Eleventy",
"slug": "eleventy",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/eleventy.svg",
"tagline": "11ty is a simpler static site generator written in JavaScript, created to be an alternative to Jekyll."
},
{
"name": "Zola",
"slug": "zola",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/zola.svg",
"tagline": "Zola is a one-stop static site engine for all of your static needs. "
"tagline": "Zola is a one-stop static site engine for all of your static needs."
},
{
"name": "Pelican",
"slug": "pelican",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/pelican.png",
"tagline": "Pelican is a versatile static site generator, written in Python."
},
{
@@ -217,12 +687,6 @@
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/mkdocs.svg",
"tagline": "MkDocs is a fast, simple and downright gorgeous static site generator that's geared towards building project documentation."
},
{
"name": "Middleman",
"slug": "middleman",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/middleman.svg",
"tagline": "Middleman is a static site generator that uses all the shortcuts and tools in modern web development."
},
{
"name": "Assemble",
"slug": "assemble",
@@ -235,15 +699,10 @@
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic-react.svg",
"tagline": "Ionic React allows you to build mobile PWAs with React and the Ionic Framework."
},
{
"name": "Stencil",
"slug": "stencil",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/stencil.svg",
"tagline": "Stencil is a powerful toolchain for building Progressive Web Apps and Design Systems."
},
{
"name": "Foundation",
"slug": "foundation",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/foundation.svg",
"tagline": "Foundation is the most advanced responsive front-end framework in the world."
}
]

26
packages/frameworks/index.d.ts vendored Normal file
View File

@@ -0,0 +1,26 @@
export interface FrameworkDetectionItem {
file: string;
matchContent?: string;
}
interface Setting {
value?: string;
placeholder?: string;
}
export interface Framework {
name: string;
slug: string;
logo: string;
tagline: string;
website: string;
detectors?: {
every?: FrameworkDetectionItem[];
some?: FrameworkDetectionItem[];
};
settings?: {
buildCommand?: Setting;
devCommand?: Setting;
outputDirectory?: Setting;
};
}

View File

@@ -0,0 +1,73 @@
<svg xmlns="http://www.w3.org/2000/svg" width="483" height="400">
<path fill="#EAF7FE" d="M316 92l15-18c-10-11-30-9-30-9l12-14-20-8c3-9 13-17 13-17-37-14-64 3-64 3 2-9 14-22 14-22-37 2-65 32-65 32 0-5 4-19 4-19s-20 14-31 35c-4 7-19 61-21 73 0 0-5-2-10-1 0 0 7 10 8 14s-7 33-10 38-7 7-9 8c0 0 3 5 6 6 0 0-1 16-3 20s-8 9-10 11c0 0 6 7 10 7l-6 10c-2 2-5-2-5-2l1 8c1 3 0 7-1 9s-5 15-6 21 1 11 1 11 2-3 4-4c0 0 0 13 3 19l9 13s-1-4 1-6c0 0 3 11 18 13 33 5 56 11 94-11 35-21 31-96 42-144 5-23 17-45 27-62 7-8 18-14 18-14h-9z"/>
<ellipse cx="248" cy="377" fill="#E5E5E5" rx="230" ry="17"/>
<path fill="#A1D4E9" d="M83 369l1-5c0-5 1-11 5-16l-3 1c5-6 10-10 13-11l-3-1c3-2 11-3 18-4l-3-2 13 1 21-4-2-6c8 0 20 2 23 3 2 0 3 5 4 5 0 0 20-11 20 8l-1 36-16-1h-28l-43 1v-2H92v-2-2l-9 1z"/>
<path fill="#85C1CE" d="M92 368h-4c0-24 24-30 24-30-21 8-20 27-20 30zM97 363l6-9c8-11 22-12 22-12-25 5-23 30-23 30h-4l1-9h-2z"/>
<path fill="#4F4E51" d="M162 82c-22-8-34-30-34-30s-6 16 1 33c6 12 19 20 26 24l7-27z"/>
<path fill="#5C5B5D" d="M162 82c-22-8-34-30-34-30s2 30 30 43l4-13z"/>
<g fill="#A1D4E9">
<path d="M128 208l-8-8c-4-7-5-15-5-15-2 2-2 6-2 7a55 55 0 00-13-9l-8 1 3 61 21 10s20-31 12-47zM95 181l-12-5s4 3 3 6c0 2 3 2 6 2v-4l3 1z"/>
<path d="M95 181l-4-1 1 4 8-1-5-2z"/>
</g>
<path fill="#CDEAF5" d="M183 64c13-22 56-42 64-45l9-12c-37 2-65 32-65 32 0-5 4-19 4-19s-20 14-31 35c-4 7-19 61-21 73 0 0-5-2-10-1 0 0 7 10 8 14s-7 33-10 38-7 7-9 8c0 0 3 5 6 6 0 0-1 16-3 20s-8 9-10 11c0 0 5 7 9 8l-5 9c-2 2-5-2-5-2l1 8c1 3 0 7-1 9s-5 15-6 21 1 11 1 11 2-3 4-4c0 0 0 13 3 19l9 13s-1-2 1-4c0 0 4 9 19 14 12 4 22 5 36 1 9-3 15-13 15-13-62 20-80-22-73-49 4-13 19-35 19-35-7 2-11-5-11-5s3 0 9-6c5-5 4-34 4-34l-6 1c1 0 2-1 6-10l-3-1v-1c2-6 1-13 3-19l2-5h6l4-17c12-54 25-85 25-85-2 6 2 16 2 16z"/>
<path fill="#5D5C5E" d="M120 235c1 2 1 5-1 6l-38 28c-2 2-5 2-6 0l-56-74c-1-2-1-5 1-6l38-29c2-1 5-1 6 1l56 74z"/>
<path fill="none" stroke="#738083" stroke-miterlimit="10" stroke-width="2" d="M120 235c1 2 1 5-1 6l-38 28c-2 2-5 2-6 0l-56-74c-1-2-1-5 1-6l38-29c2-1 5-1 6 1l56 74z"/>
<path fill="#747F84" d="M109 229l-38 28-41-54 38-29z"/>
<path fill="#414141" d="M98 247c2 2 1 4 0 5-2 1-4 1-5-1v-5c2-1 4 0 5 1z"/>
<path fill="none" stroke="#414141" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M39 187l12-9"/>
<g fill="#E9F7FE" opacity=".5">
<path d="M91 208l-33 26-18-25 33-25zM75 236l-10 7-5-6 10-7zM95 214l-21 16-2-2 21-16zM99 218l-22 16-2-2 22-16z"/>
<g>
<path d="M82 245l-10 8-5-7 10-7zM102 223l-21 17-2-3 21-16zM105 227l-21 16-2-3 21-16z"/>
</g>
<path d="M70 182l-33 25-3-3 34-26z"/>
</g>
<path fill="#A1D4E9" d="M99 207c-19 15-41 3-41 3 3-6 21-30 34-30 0 0 19 8 7 27zM87 224c-25-7-47-1-47-1-10 2-7 14-2 23l4 14 3-3c5 7 15 12 28 4 16-10 14-37 14-37z"/>
<ellipse cx="214.2" cy="174" fill="#CDEAF5" rx="10.8" ry="13.7"/>
<ellipse cx="160.3" cy="165.8" fill="#CDEAF5" rx="10.8" ry="13.7"/>
<ellipse cx="213.2" cy="170.2" fill="#FFF" rx="10.7" ry="13.5"/>
<ellipse cx="160.2" cy="162.2" fill="#FFF" rx="10.7" ry="13.5"/>
<path fill="#4F4E51" d="M166 156v1c0 2-1 4-3 4l-2-3-2 4c0 4 3 7 5 7 3 0 6-3 6-7 0-3-2-5-4-6z"/>
<path fill="#5C5B5D" d="M255 165c-4-3-9-4-9-4-4-2-13-4-23-5l-31-1h-12a38 38 0 01-4-2l-9-4-13-2-20-1-2 1-2 4v4l2 4 1 2v11c1 3 3 6 10 8l5 1c12 3 17 2 21 0s6-10 8-14v-2l1-1 1-1 3 1 2 1v4l1 4v1c0 3 1 9 4 11 1 2 6 4 11 6l11 2h21c4-1 5-3 7-7l1-1v-1l5-17 9 3 1-5zm-82-4l-4 11c-2 5-2 5-4 6-2 0-9 0-17-2-6-1-10-4-10-6v-18-1h8c6 0 14 3 17 4h2c5 2 8 4 8 6zm66 8l-5 17c-1 2-3 2-10 3h-1a91 91 0 01-24-4c-6-2-7-3-8-6l-1-15c0-2 0-3 2-3h25a143 143 0 0120 4c1 0 3 2 2 4z"/>
<path fill="#4F4E51" d="M216 163v1c0 2-1 4-3 4l-2-3-2 4c0 4 3 7 5 7 3 0 6-3 6-7 0-3-2-5-4-6z"/>
<path fill="#A1D4E9" d="M207 121s19-8 28 1c0 0 0-9-12-9-8 0-12 4-16 8zM211 111s6-6 17-4c0 0-4-4-9-3-4 1-6 3-8 7zM154 127s10 3 14 10c0 0 1-6-4-9-5-2-6-2-10-1zM171 126s-2-4-7-4c0 0 4-2 6 0 0 0 2 2 1 4z"/>
<path fill="#5C5B5D" d="M176 198l25 5 2-4v5s-7 2-13 1l10-1s-13-1-24-6zM280 102l3 2c26 10 52-4 52-4s-4 16-20 27c-15 10-39 5-39 5-2 3-3 10-3 10s-4-9-1-24c2-10 7-16 8-16"/>
<path fill="#747F83" d="M280 102l3 2c26 10 52-4 52-4s-23 31-62 15c0 0 1-7 7-13"/>
<path fill="#CDEAF5" d="M125 231s10 2 16-2c0 0 2 9 10 15 0 0 7-12 14-14l1 11s13 1 25-9c0 0-3 14-30 15v-8s-6 7-10 16c0 0-14-10-16-19h-12l2-5z"/>
<path fill="#85C1CE" d="M76 222s-13 10-22 13c0 0 22-5 29-8 0 0-6 15-24 21 0 0 17-2 24-8 0 0-8 23-25 25 0 0 10 1 19-7 9-9 11-27 10-34l-11-2zM100 207l7-5s0 5-5 8l-2-3z"/>
<path fill="#EAF7FE" d="M331 263c3-2 5-1 4-10-1-8-6-17-6-17M279 221l-5 2 6 2zM334 228s12-3 16-10"/>
<path fill="#85C1CE" d="M130 330s16 10 39 7v-8s-13 1-26-4v3l-4-2v3l-9 1z"/>
<path fill="#CDEAF5" d="M222 347h-3c-11 0-19-2-30-1l-22 1 10-10 1 3 6-10 11-15-17 6-4 6v1c-2-2-4-3-4-1l-1 2c-3 5-17 14-17 14l-2 1 3 1v2l-1 1v5l-1 10c0 2-2 4-4 4 0 0 2 2 3 1v1s-1 8 6 9h21l21 1v-3l12 1 2-4 18 1c3-19-2-26-8-27z"/>
<path fill="#EAF7FE" d="M233 367v-6l1 1-3-8c-2-4-5-7-10-7l1-2c-4-1-7-2-14-1 4-1 8-4 12-7l2 1 2-5h-1l7-8 2 3 6-16-28 9-3 3-11-11-14 19-3 7c-1-1-1-3-2-2-4 4-8 8-12 10h8l-4 2 12-1c12-1 19 4 19 4v-1c1 1 4 3 6 8 0 1 2 8 1 10l2-1v9h8s1-10-1-18h2c-1-4-4-7-7-8l-1-1s16-2 16 23h3l7 1-1-1-2-6zm-27-8l2 4-2-4z"/>
<path fill="#EAF7FE" d="M194 358l2-1c-3-2-7-2-9-2h-2s7 6 6 24h7s3-14-4-21z"/>
<g>
<path fill="#5D5C5E" d="M452 372c0 5-5 9-10 9H237c-6 0-10-4-10-9V227c0-5 4-10 10-10h205c5 0 10 5 10 10v145z"/>
<path fill="none" stroke="#738083" stroke-miterlimit="10" stroke-width="2" d="M452 372c0 5-5 9-10 9H237c-6 0-10-4-10-9V227c0-5 4-10 10-10h205c5 0 10 5 10 10v145h0z"/>
<path fill="#747F84" d="M247 231h185v136H247z"/>
<path fill="#414141" d="M243 299c0 3-2 5-5 5-2 0-4-2-4-5 0-2 2-4 4-4 3 0 5 2 5 4z"/>
<path fill="none" stroke="#414141" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M440 290v18"/>
<g fill="#E9F7FE" opacity=".5">
<path d="M252 237h176v12H252zM252 252h176v58H252zM252 317h28v20h-28zM284 317h53v5h-53zM284 324h53v5h-53zM284 332h53v5h-53z"/>
<g>
<path d="M342 317h28v20h-28zM374 317h53v5h-53zM374 324h53v5h-53zM374 332h53v5h-53z"/>
</g>
<g>
<path d="M252 343h28v20h-28zM284 343h53v5h-53zM284 351h53v5h-53zM284 358h53v5h-53z"/>
</g>
<g>
<path d="M342 343h28v20h-28zM374 343h53v5h-53zM374 351h53v5h-53zM374 358h53v5h-53z"/>
</g>
</g>
</g>
<g>
<path fill="#EAF7FE" d="M382 187l8 3-10-11-15-7 2-2h-10l-30-4 2-3-39 2 1-4-6 3c0-3 5-9 5-9-6 1-11 9-11 9l-1 5-7 47h112c1-9 0-21-2-29h1zm-94-20z"/>
<path fill="#4F4E51" d="M330 260l-4 5-8-7-12 9-7-10c-2-3-2-14-2-14l-3 2 5-15s30-6 31 30z" opacity=".3"/>
<path fill="#EAF7FE" d="M333 246c1-4-1-8-1-12l2-5c1-2 3-2 5-3a385 385 0 017-4c10-5 37-8 37-8-39-15-95 0-95 0-11 7-17 30-17 30 9-7 35-22 35-22-1 11-16 11 25 41 0-6 2-11 3-17h-1z"/>
<path fill="#CDEAF5" d="M289 197c-7 5-13 12-18 19h15l13-10-15-1s21-5 29-9c10-8 17-13 26-16 0 0 1-1-1-1l4-1 10-3s-20-2-63 22z"/>
<path fill="#4F4E51" d="M285 229h-19l-2 6-1 9 22-15z" opacity=".3"/>
<path fill="#EAF7FE" d="M299 206s-26 15-27 29l-1 9 28-21v-17zM305 224c-5 16-5 16-4 24l2 8 11 11s10-7 9-14c-3-23-18-29-18-29z"/>
<path fill="#EAF7FE" d="M293 230s4-5 14-6c7 0 0-5 0-5l-13 1"/>
<path fill="#EAF7FE" d="M302 252l-2-12-2 2c3-15 2-15 5-19 3-7 3 21 3 21"/>
<path fill="#CDEAF5" d="M317 240c1-5 1-13 3-18l-2 3c-2 4-4 10-4 17 0 8 7 17 7 17l2-2-4-8s-2-3-2-9z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="138" height="57">
<g fill="none" fill-rule="evenodd">
<rect width="136.5" height="55.5" x=".8" y=".8" fill="#FFF" stroke="#EAEAEA" stroke-width="1.5" rx="4.5"/>
<g stroke="#000" stroke-width="6">
<path stroke-linecap="square" d="M70.5 36V13.8"/>
<path d="M57 27.2L70.6 41 84 27.4"/>
</g>
<path stroke="#000" stroke-width="6" d="M16.4 44V19l13.9 13.8 14-14v25"/>
<path stroke="#F9AC00" stroke-width="6" d="M122.4 41.3L93.2 12m.4 29.3L122.8 12"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,4 @@
<svg width="111" height="116" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 2l2-2h73c20 0 36 16 36 36v6l-2 2H89l-2-2v-6c0-7-5-12-12-12H25l-1 2v80l-2 1C10 106 0 96 0 84V2z" fill="#ED1846"/>
<path d="M45 48l-1 1c1 13 11 23 23 23h16c2 0 4 1 4 4v30l2 1c12-1 22-11 22-23v-8c0-16-12-28-28-28H45z" fill="#ED1846"/>
</svg>

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "@now/frameworks",
"version": "0.0.2-canary.0",
"version": "0.0.3",
"main": "frameworks.json",
"license": "UNLICENSED"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@now/build-utils",
"version": "1.2.1-canary.1",
"version": "1.3.2",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",

View File

@@ -1,432 +0,0 @@
import minimatch from 'minimatch';
import { valid as validSemver } from 'semver';
import { PackageJson, Builder, Config, BuilderFunctions } from './types';
interface ErrorResponse {
code: string;
message: string;
}
interface Options {
tag?: 'canary' | 'latest' | string;
functions?: BuilderFunctions;
}
const src = 'package.json';
const config: Config = { zeroConfig: true };
const MISSING_BUILD_SCRIPT_ERROR: ErrorResponse = {
code: 'missing_build_script',
message:
'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
'\nMore details: https://zeit.co/docs/v2/platform/frequently-asked-questions#missing-build-script',
};
// Static builders are special cased in `@now/static-build`
function getBuilders({ tag }: Options = {}): Map<string, Builder> {
const withTag = tag ? `@${tag}` : '';
const config = { zeroConfig: true };
return new Map<string, Builder>([
['next', { src, use: `@now/next${withTag}`, config }],
]);
}
// Must be a function to ensure that the returned
// object won't be a reference
function getApiBuilders({ tag }: Pick<Options, 'tag'> = {}): Builder[] {
const withTag = tag ? `@${tag}` : '';
const config = { zeroConfig: true };
return [
{ src: 'api/**/*.js', use: `@now/node${withTag}`, config },
{ src: 'api/**/*.ts', use: `@now/node${withTag}`, config },
{ src: 'api/**/*.go', use: `@now/go${withTag}`, config },
{ src: 'api/**/*.py', use: `@now/python${withTag}`, config },
{ src: 'api/**/*.rb', use: `@now/ruby${withTag}`, config },
];
}
function hasPublicDirectory(files: string[]) {
return files.some(name => name.startsWith('public/'));
}
function hasBuildScript(pkg: PackageJson | undefined) {
const { scripts = {} } = pkg || {};
return Boolean(scripts && scripts['build']);
}
function getApiFunctionBuilder(
file: string,
prevBuilder: Builder | undefined,
{ functions = {} }: Pick<Options, 'functions'>
) {
const key = Object.keys(functions).find(
k => file === k || minimatch(file, k)
);
const fn = key ? functions[key] : undefined;
if (!fn || (!fn.runtime && !prevBuilder)) {
return prevBuilder;
}
const src = (prevBuilder && prevBuilder.src) || file;
const use = fn.runtime || (prevBuilder && prevBuilder.use);
const config: Config = { zeroConfig: true };
if (key) {
Object.assign(config, {
functions: {
[key]: fn,
},
});
}
const { includeFiles, excludeFiles } = fn;
if (includeFiles) Object.assign(config, { includeFiles });
if (excludeFiles) Object.assign(config, { excludeFiles });
return use ? { use, src, config } : prevBuilder;
}
async function detectFrontBuilder(
pkg: PackageJson,
builders: Builder[],
options: Options
): Promise<Builder> {
const { tag } = options;
const withTag = tag ? `@${tag}` : '';
for (const [dependency, builder] of getBuilders(options)) {
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
// Return the builder when a dependency matches
if (deps[dependency]) {
if (options.functions) {
Object.entries(options.functions).forEach(([key, func]) => {
// When the builder is not used yet we'll use it for the frontend
if (
builders.every(
b => !(b.config && b.config.functions && b.config.functions[key])
)
) {
if (!builder.config) builder.config = {};
if (!builder.config.functions) builder.config.functions = {};
builder.config.functions[key] = { ...func };
}
});
}
return builder;
}
}
// By default we'll choose the `static-build` builder
return { src, use: `@now/static-build${withTag}`, config };
}
// Files that match a specific pattern will get ignored
export function getIgnoreApiFilter(optionsOrBuilders: Options | Builder[]) {
const possiblePatterns: string[] = getApiBuilders().map(b => b.src);
if (Array.isArray(optionsOrBuilders)) {
optionsOrBuilders.forEach(({ src }) => possiblePatterns.push(src));
} else if (optionsOrBuilders.functions) {
Object.keys(optionsOrBuilders.functions).forEach(p =>
possiblePatterns.push(p)
);
}
return (file: string) => {
if (!file.startsWith('api/')) {
return false;
}
if (file.includes('/.')) {
return false;
}
if (file.includes('/_')) {
return false;
}
if (file.endsWith('.d.ts')) {
return false;
}
if (possiblePatterns.every(p => !(file === p || minimatch(file, p)))) {
return false;
}
return true;
};
}
// We need to sort the file paths by alphabet to make
// sure the routes stay in the same order e.g. for deduping
export function sortFiles(fileA: string, fileB: string) {
return fileA.localeCompare(fileB);
}
async function detectApiBuilders(
files: string[],
options: Options
): Promise<Builder[]> {
const builds = files
.sort(sortFiles)
.filter(getIgnoreApiFilter(options))
.map(file => {
const apiBuilders = getApiBuilders(options);
const apiBuilder = apiBuilders.find(b => minimatch(file, b.src));
const fnBuilder = getApiFunctionBuilder(file, apiBuilder, options);
return fnBuilder ? { ...fnBuilder, src: file } : null;
});
return builds.filter(Boolean) as Builder[];
}
// When a package has files that conflict with `/api` routes
// e.g. Next.js pages/api we'll check it here and return an error.
async function checkConflictingFiles(
files: string[],
builders: Builder[]
): Promise<ErrorResponse | null> {
// For Next.js
if (builders.some(b => b.use.startsWith('@now/next'))) {
const hasApiPages = files.some(file => file.startsWith('pages/api/'));
const hasApiBuilders = builders.some(b => b.src.startsWith('api/'));
if (hasApiPages && hasApiBuilders) {
return {
code: 'conflicting_files',
message:
'It is not possible to use `api` and `pages/api` at the same time, please only use one option',
};
}
}
return null;
}
// When e.g. Next.js receives a `functions` property it has to make sure,
// that it can handle those files, otherwise there are unused functions.
async function checkUnusedFunctionsOnFrontendBuilder(
files: string[],
builder: Builder
): Promise<ErrorResponse | null> {
const { config: { functions = undefined } = {} } = builder;
if (!functions) return null;
if (builder.use.startsWith('@now/next')) {
const matchingFiles = files.filter(file =>
Object.keys(functions).some(key => file === key || minimatch(file, key))
);
for (const matchedFile of matchingFiles) {
if (
!matchedFile.startsWith('src/pages/') &&
!matchedFile.startsWith('pages/')
) {
return {
code: 'unused_function',
message: `The function for ${matchedFile} can't be handled by any builder`,
};
}
}
}
return null;
}
function validateFunctions(files: string[], { functions = {} }: Options) {
const apiBuilders = getApiBuilders();
for (const [path, func] of Object.entries(functions)) {
if (path.length > 256) {
return {
code: 'invalid_function_glob',
message: 'Function globs must be less than 256 characters long.',
};
}
if (!func || typeof func !== 'object') {
return {
code: 'invalid_function',
message: 'Function must be an object.',
};
}
if (Object.keys(func).length === 0) {
return {
code: 'invalid_function',
message: 'Function must contain at least one property.',
};
}
if (
func.maxDuration !== undefined &&
(func.maxDuration < 1 ||
func.maxDuration > 900 ||
!Number.isInteger(func.maxDuration))
) {
return {
code: 'invalid_function_duration',
message: 'Functions must have a duration between 1 and 900.',
};
}
if (
func.memory !== undefined &&
(func.memory < 128 || func.memory > 3008 || func.memory % 64 !== 0)
) {
return {
code: 'invalid_function_memory',
message:
'Functions must have a memory value between 128 and 3008 in steps of 64.',
};
}
if (path.startsWith('/')) {
return {
code: 'invalid_function_source',
message: `The function path "${path}" is invalid. The path must be relative to your project root and therefore cannot start with a slash.`,
};
}
if (files.some(f => f === path || minimatch(f, path)) === false) {
return {
code: 'invalid_function_source',
message: `No source file matched the function for ${path}.`,
};
}
if (func.runtime !== undefined) {
const tag = `${func.runtime}`.split('@').pop();
if (!tag || !validSemver(tag)) {
return {
code: 'invalid_function_runtime',
message:
'Function Runtimes must have a valid version, for example `now-php@1.0.0`.',
};
}
if (
apiBuilders.some(b => func.runtime && func.runtime.startsWith(b.use))
) {
return {
code: 'invalid_function_runtime',
message: `The function Runtime ${func.runtime} is not a Community Runtime and must not be specified.`,
};
}
}
if (func.includeFiles !== undefined) {
if (typeof func.includeFiles !== 'string') {
return {
code: 'invalid_function_property',
message: `The property \`includeFiles\` must be a string.`,
};
}
}
if (func.excludeFiles !== undefined) {
if (typeof func.excludeFiles !== 'string') {
return {
code: 'invalid_function_property',
message: `The property \`excludeFiles\` must be a string.`,
};
}
}
}
return null;
}
// When zero config is used we can call this function
// to determine what builders to use
export async function detectBuildersLegacy(
files: string[],
pkg?: PackageJson | undefined | null,
options: Options = {}
): Promise<{
builders: Builder[] | null;
errors: ErrorResponse[] | null;
warnings: ErrorResponse[];
}> {
const errors: ErrorResponse[] = [];
const warnings: ErrorResponse[] = [];
const functionError = validateFunctions(files, options);
if (functionError) {
return {
builders: null,
errors: [functionError],
warnings,
};
}
// Detect all builders for the `api` directory before anything else
const builders = await detectApiBuilders(files, options);
if (pkg && hasBuildScript(pkg)) {
const frontendBuilder = await detectFrontBuilder(pkg, builders, options);
builders.push(frontendBuilder);
const conflictError = await checkConflictingFiles(files, builders);
if (conflictError) {
warnings.push(conflictError);
}
const unusedFunctionError = await checkUnusedFunctionsOnFrontendBuilder(
files,
frontendBuilder
);
if (unusedFunctionError) {
return {
builders: null,
errors: [unusedFunctionError],
warnings,
};
}
} else {
if (pkg && builders.length === 0) {
// We only show this error when there are no api builders
// since the dependencies of the pkg could be used for those
errors.push(MISSING_BUILD_SCRIPT_ERROR);
return { errors, warnings, builders: null };
}
// We allow a `public` directory
// when there are no build steps
if (hasPublicDirectory(files)) {
builders.push({
use: '@now/static',
src: 'public/**/*',
config,
});
} else if (
builders.length > 0 &&
files.some(f => !f.startsWith('api/') && f !== 'package.json')
) {
// Everything besides the api directory
// and package.json can be served as static files
builders.push({
use: '@now/static',
src: '!{api/**,package.json}',
config,
});
}
}
return {
builders: builders.length ? builders : null,
errors: errors.length ? errors : null,
warnings,
};
}

View File

@@ -1,12 +1,6 @@
import minimatch from 'minimatch';
import { valid as validSemver } from 'semver';
import {
Builder,
Config,
BuilderFunctions,
DetectorResult,
DetectorOutput,
} from './types';
import { PackageJson, Builder, Config, BuilderFunctions } from './types';
interface ErrorResponse {
code: string;
@@ -16,6 +10,13 @@ interface ErrorResponse {
interface Options {
tag?: 'canary' | 'latest' | string;
functions?: BuilderFunctions;
ignoreBuildScript?: boolean;
projectSettings?: {
framework?: string | null;
devCommand?: string | null;
buildCommand?: string | null;
outputDirectory?: string | null;
};
}
// Must be a function to ensure that the returned
@@ -33,8 +34,13 @@ function getApiBuilders({ tag }: Pick<Options, 'tag'> = {}): Builder[] {
];
}
function hasDirectory(fileName: string, files: string[]) {
return files.some(name => name.startsWith(`${fileName}/`));
function hasDirectory(name: string, files: string[]) {
return files.some(file => file.startsWith(`${name}/`));
}
function hasBuildScript(pkg: PackageJson | undefined | null) {
const { scripts = {} } = pkg || {};
return Boolean(scripts && scripts['build']);
}
function getApiFunctionBuilder(
@@ -73,22 +79,14 @@ function getApiFunctionBuilder(
}
function detectFrontBuilder(
detectorResult: Partial<DetectorOutput>,
pkg: PackageJson | null | undefined,
builders: Builder[],
files: string[],
options: Options
): Builder {
const { tag } = options;
const { tag, projectSettings = {} } = options;
const withTag = tag ? `@${tag}` : '';
const {
framework,
buildCommand,
outputDirectory,
devCommand,
} = detectorResult;
const frameworkSlug = framework ? framework.slug : null;
let { framework } = projectSettings;
const config: Config = {
zeroConfig: true,
@@ -98,45 +96,63 @@ function detectFrontBuilder(
config.framework = framework;
}
if (devCommand) {
config.devCommand = devCommand;
if (projectSettings.devCommand) {
config.devCommand = projectSettings.devCommand;
}
if (buildCommand) {
config.buildCommand = buildCommand;
if (projectSettings.buildCommand) {
config.buildCommand = projectSettings.buildCommand;
}
if (outputDirectory) {
config.outputDirectory = outputDirectory;
if (projectSettings.outputDirectory) {
config.outputDirectory = projectSettings.outputDirectory;
}
if (pkg) {
const deps: PackageJson['dependencies'] = {
...pkg.dependencies,
...pkg.devDependencies,
};
if (deps['next']) {
framework = 'nextjs';
}
}
// All unused functions will be used for the frontend
if (options.functions) {
Object.entries(options.functions).forEach(([key, func]) => {
// When the builder is not used yet we'll use it for the frontend
if (
builders.every(
b => !(b.config && b.config.functions && b.config.functions[key])
)
) {
config.functions = config.functions || {};
if (!config.functions) config.functions = {};
config.functions[key] = { ...func };
}
});
}
if (frameworkSlug === 'next') {
if (framework === 'nextjs') {
return { src: 'package.json', use: `@now/next${withTag}`, config };
}
if (frameworkSlug === 'hugo') {
const configFiles = new Set(['config.yaml', 'config.toml', 'config.json']);
const source = files.find(file => configFiles.has(file));
if (source) {
return { src: source, use: `@now/static-build${withTag}`, config };
}
}
// Entrypoints for other frameworks
const entrypoints = new Set([
'package.json',
'config.yaml',
'config.toml',
'config.json',
'_config.yml',
'config.yml',
'config.rb',
]);
return { src: 'package.json', use: `@now/static-build${withTag}`, config };
const source = pkg
? 'package.json'
: files.find(file => entrypoints.has(file)) || 'package.json';
return { src: source, use: `@now/static-build${withTag}`, config };
}
// Files that match a specific pattern will get ignored
@@ -237,9 +253,7 @@ function checkUnusedFunctionsOnFrontendBuilder(
) {
return {
code: 'unused_function',
message:
`The function for "${matchedFile}" can't be handled by any runtime. ` +
`Please provide one with the "runtime" option.`,
message: `The function for ${matchedFile} can't be handled by any builder`,
};
}
}
@@ -346,7 +360,7 @@ function validateFunctions(files: string[], { functions = {} }: Options) {
// to determine what builders to use
export async function detectBuilders(
files: string[],
detectorResult: Partial<DetectorResult> | null = null,
pkg?: PackageJson | undefined | null,
options: Options = {}
): Promise<{
builders: Builder[] | null;
@@ -368,14 +382,11 @@ export async function detectBuilders(
// Detect all builders for the `api` directory before anything else
const builders = detectApiBuilders(files, options);
const { projectSettings = {} } = options;
const { outputDirectory, buildCommand, framework } = projectSettings;
if (detectorResult && detectorResult.buildCommand) {
const frontendBuilder = detectFrontBuilder(
detectorResult,
builders,
files,
options
);
if (hasBuildScript(pkg) || buildCommand || framework) {
const frontendBuilder = detectFrontBuilder(pkg, builders, files, options);
builders.push(frontendBuilder);
const conflictError = checkConflictingFiles(files, builders);
@@ -396,35 +407,46 @@ export async function detectBuilders(
warnings,
};
}
} else if (
detectorResult &&
detectorResult.outputDirectory &&
hasDirectory(detectorResult.outputDirectory, files)
) {
builders.push({
use: '@now/static',
src: [...detectorResult.outputDirectory.split('/'), '**', '*']
.filter(Boolean)
.join('/'),
config: { zeroConfig: true },
});
} else if (hasDirectory('public', files)) {
builders.push({
use: '@now/static',
src: 'public/**/*',
config: { zeroConfig: true },
});
} else if (
builders.length > 0 &&
files.some(f => !f.startsWith('api/') && f !== 'package.json')
) {
// Everything besides the api directory
// and package.json can be served as static files
builders.push({
use: '@now/static',
src: '!{api/**,package.json}',
config: { zeroConfig: true },
});
} else {
if (!options.ignoreBuildScript && pkg && builders.length === 0) {
// We only show this error when there are no api builders
// since the dependencies of the pkg could be used for those
errors.push({
code: 'missing_build_script',
message:
'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
'\nMore details: https://zeit.co/docs/v2/platform/frequently-asked-questions#missing-build-script',
});
return { errors, warnings, builders: null };
}
// We allow a `public` directory
// when there are no build steps
const outDir = outputDirectory || 'public';
if (hasDirectory(outDir, files)) {
builders.push({
use: '@now/static',
src: `${outDir}/**/*`,
config: {
zeroConfig: true,
outputDirectory: outDir,
},
});
} else if (
builders.length > 0 &&
files.some(f => !f.startsWith('api/') && f !== 'package.json')
) {
// Everything besides the api directory
// and package.json can be served as static files
builders.push({
use: '@now/static',
src: '!{api/**,package.json}',
config: {
zeroConfig: true,
},
});
}
}
return {

View File

@@ -0,0 +1,81 @@
import { Framework, FrameworkDetectionItem } from '@now/frameworks';
import { DetectorFilesystem } from './detectors/filesystem';
export interface DetectFrameworkOptions {
fs: DetectorFilesystem;
frameworkList: Framework[];
}
async function matches(fs: DetectorFilesystem, framework: Framework) {
const { detectors } = framework;
if (!detectors) {
return false;
}
const { every, some } = detectors;
if (every !== undefined && !Array.isArray(every)) {
return false;
}
if (some !== undefined && !Array.isArray(some)) {
return false;
}
const check = async ({ file, matchContent }: FrameworkDetectionItem) => {
if (!file) {
return false;
}
if ((await fs.exists(file)) === false) {
return false;
}
if (matchContent) {
const regex = new RegExp(matchContent, 'gm');
const content = await fs.readFile(file);
if (!regex.test(content.toString())) {
return false;
}
}
return true;
};
const result: boolean[] = [];
if (every) {
const everyResult = await Promise.all(every.map(item => check(item)));
result.push(...everyResult);
}
if (some) {
let someResult = false;
for (const item of some) {
if (await check(item)) {
someResult = true;
break;
}
}
result.push(someResult);
}
return result.every(res => res === true);
}
export async function detectFramework({
fs,
frameworkList,
}: DetectFrameworkOptions): Promise<string | null> {
for (const framework of frameworkList) {
if (await matches(fs, framework)) {
return framework.slug;
}
}
return null;
}

View File

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

View File

@@ -1,5 +1,6 @@
import { parse as parsePath } from 'path';
import { Route, Builder } from './types';
import { Route, Source } from '@now/routing-utils';
import { Builder } from './types';
import { getIgnoreApiFilter, sortFiles } from './detect-builders';
function escapeName(name: string) {
@@ -40,20 +41,26 @@ function getSegmentName(segment: string): string | null {
return null;
}
function createRouteFromPath(filePath: string): Route {
function createRouteFromPath(
filePath: string,
featHandleMiss: boolean,
cleanUrls: boolean
): { route: Source; isDynamic: boolean } {
const parts = filePath.split('/');
let counter = 1;
const query: string[] = [];
let isDynamic = false;
const srcParts = parts.map((segment, index): string => {
const srcParts = parts.map((segment, i): string => {
const name = getSegmentName(segment);
const isLast = index === parts.length - 1;
const isLast = i === parts.length - 1;
if (name !== null) {
// We can't use `URLSearchParams` because `$` would get escaped
query.push(`${name}=$${counter++}`);
return `([^\\/]+)`;
isDynamic = true;
return `([^/]+)`;
} else if (isLast) {
const { name: fileName, ext } = parsePath(segment);
const isIndex = fileName === 'index';
@@ -62,27 +69,43 @@ function createRouteFromPath(filePath: string): Route {
const names = [
isIndex ? prefix : `${fileName}\\/`,
prefix + escapeName(fileName),
prefix + escapeName(fileName) + escapeName(ext),
featHandleMiss && cleanUrls
? ''
: prefix + escapeName(fileName) + escapeName(ext),
].filter(Boolean);
// Either filename with extension, filename without extension
// or nothing when the filename is `index`
// or nothing when the filename is `index`.
// When `cleanUrls: true` then do *not* add the filename with extension.
return `(${names.join('|')})${isIndex ? '?' : ''}`;
}
return segment;
});
const { name: fileName } = parsePath(filePath);
const { name: fileName, ext } = parsePath(filePath);
const isIndex = fileName === 'index';
const queryString = `${query.length ? '?' : ''}${query.join('&')}`;
const src = isIndex
? `^/${srcParts.slice(0, -1).join('/')}${srcParts.slice(-1)[0]}$`
: `^/${srcParts.join('/')}$`;
const dest = `/${filePath}${query.length ? '?' : ''}${query.join('&')}`;
return { src, dest };
let route: Source;
if (featHandleMiss) {
const extensionless = ext ? filePath.slice(0, -ext.length) : filePath;
route = {
src,
dest: `/${extensionless}${queryString}`,
check: true,
};
} else {
route = {
src,
dest: `/${filePath}${queryString}`,
};
}
return { route, isDynamic };
}
// Check if the path partially matches and has the same
@@ -192,17 +215,30 @@ function sortFilesBySegmentCount(fileA: string, fileB: string): number {
return 0;
}
interface ApiRoutesResult {
defaultRoutes: Source[] | null;
dynamicRoutes: Source[] | null;
error: { [key: string]: string } | null;
}
interface RoutesResult {
defaultRoutes: Route[] | null;
redirectRoutes: Route[] | null;
error: { [key: string]: string } | null;
}
async function detectApiRoutes(
files: string[],
builders: Builder[]
): Promise<RoutesResult> {
builders: Builder[],
featHandleMiss: boolean,
cleanUrls: boolean
): Promise<ApiRoutesResult> {
if (!files || files.length === 0) {
return { defaultRoutes: null, error: null };
return {
defaultRoutes: null,
dynamicRoutes: null,
error: null,
};
}
// The deepest routes need to be
@@ -212,14 +248,15 @@ async function detectApiRoutes(
.sort(sortFiles)
.sort(sortFilesBySegmentCount);
const defaultRoutes: Route[] = [];
const defaultRoutes: Source[] = [];
const dynamicRoutes: Source[] = [];
for (const file of sortedFiles) {
// We only consider every file in the api directory
// as we will strip extensions as well as resolving "[segments]"
if (
!file.startsWith('api/') &&
!builders.some(b => b.src === file && b.config!.functions)
!builders.some(b => b.src === file && b.config && b.config.functions)
) {
continue;
}
@@ -229,6 +266,7 @@ async function detectApiRoutes(
if (conflictingSegment) {
return {
defaultRoutes: null,
dynamicRoutes: null,
error: {
code: 'conflicting_path_segment',
message:
@@ -250,6 +288,7 @@ async function detectApiRoutes(
return {
defaultRoutes: null,
dynamicRoutes: null,
error: {
code: 'conflicting_file_path',
message:
@@ -260,18 +299,14 @@ async function detectApiRoutes(
};
}
defaultRoutes.push(createRouteFromPath(file));
const out = createRouteFromPath(file, featHandleMiss, cleanUrls);
if (out.isDynamic) {
dynamicRoutes.push(out.route);
}
defaultRoutes.push(out.route);
}
// 404 Route to disable directory listing
if (defaultRoutes.length) {
defaultRoutes.push({
status: 404,
src: '/api(\\/.*)?$',
});
}
return { defaultRoutes, error: null };
return { defaultRoutes, dynamicRoutes, error: null };
}
function getPublicBuilder(builders: Builder[]): Builder | null {
@@ -286,21 +321,91 @@ function getPublicBuilder(builders: Builder[]): Builder | null {
return builder || null;
}
export function detectOutputDirectory(builders: Builder[]): string | null {
// TODO: We eventually want to save the output directory to
// builder.config.outputDirectory so it is only detected once
const publicBuilder = getPublicBuilder(builders);
return publicBuilder ? publicBuilder.src.replace('/**/*', '') : null;
}
export async function detectRoutes(
files: string[],
builders: Builder[]
builders: Builder[],
featHandleMiss = false,
cleanUrls = false,
trailingSlash?: boolean
): Promise<RoutesResult> {
const routesResult = await detectApiRoutes(files, builders);
const publicBuilder = getPublicBuilder(builders);
const result = await detectApiRoutes(
files,
builders,
featHandleMiss,
cleanUrls
);
const { dynamicRoutes, defaultRoutes: allRoutes, error } = result;
if (error) {
return { defaultRoutes: null, redirectRoutes: null, error };
}
const directory = detectOutputDirectory(builders);
const defaultRoutes: Route[] = [];
const redirectRoutes: Route[] = [];
if (allRoutes && allRoutes.length > 0) {
const hasApiRoutes = allRoutes.some(
r => r.dest && r.dest.startsWith('/api/')
);
if (featHandleMiss) {
defaultRoutes.push({ handle: 'miss' });
if (cleanUrls) {
const extensions = builders
.map(b => parsePath(b.src).ext)
.filter(Boolean);
if (extensions.length > 0) {
const exts = extensions.map(ext => ext.slice(1)).join('|');
const group = `(?:\\.(?:${exts}))`;
redirectRoutes.push({
src: `^/(api(?:.+)?)/index${group}?/?$`,
headers: { Location: trailingSlash ? '/$1/' : '/$1' },
status: 308,
});
redirectRoutes.push({
src: `^/api/(.+)${group}/?$`,
headers: { Location: trailingSlash ? '/api/$1/' : '/api/$1' },
status: 308,
});
}
} else {
defaultRoutes.push({
src: '^/api/(.+)\\.\\w+$',
dest: '/api/$1',
check: true,
});
}
if (dynamicRoutes) {
defaultRoutes.push(...dynamicRoutes);
}
if (hasApiRoutes) {
defaultRoutes.push({
src: '^/api(/.*)?$',
status: 404,
continue: true,
});
}
} else {
defaultRoutes.push(...allRoutes);
if (hasApiRoutes) {
defaultRoutes.push({
status: 404,
src: '^/api(/.*)?$',
});
}
}
}
if (routesResult.defaultRoutes && publicBuilder) {
const directory = publicBuilder.src.replace('/**/*', '');
routesResult.defaultRoutes.push({
if (!featHandleMiss && directory) {
defaultRoutes.push({
src: '/(.*)',
dest: `/${directory}/$1`,
});
}
return routesResult;
return { defaultRoutes, redirectRoutes, error };
}

View File

@@ -1,27 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectAngular({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('@angular/cli');
if (!version) return false;
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'ng build',
outputDirectory: 'dist',
devCommand: 'ng serve --port $PORT',
framework: {
slug: '@angular/cli',
version,
},
minNodeRange: '10.x',
routes: [
{
handle: 'filesystem',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
};
}

View File

@@ -1,22 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectBrunch({
fs: { exists, getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('brunch');
if (!version) return false;
const hasConfig = await exists('brunch-config.js');
if (!hasConfig) return false;
return {
buildCommand:
(await getPackageJsonBuildCommand()) || 'brunch build --production',
outputDirectory: 'public',
devCommand: 'brunch watch --server --port $PORT',
framework: {
slug: 'brunch',
version,
},
};
}

View File

@@ -1,44 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectCreateReactAppEjected({
fs: { getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('react-dev-utils');
if (!version) {
return false;
}
return {
buildCommand: 'node scripts/build.js',
outputDirectory: 'build',
devCommand: 'node scripts/start.js',
framework: {
slug: 'react-dev-utils',
version,
},
devVariables: { BROWSER: 'none' },
routes: [
{
src: '/static/(.*)',
headers: { 'cache-control': 's-maxage=31536000, immutable' },
continue: true,
},
{
src: '/service-worker.js',
headers: { 'cache-control': 's-maxage=0' },
continue: true,
},
{
src: '/sockjs-node/(.*)',
dest: '/sockjs-node/$1',
},
{
handle: 'filesystem',
},
{
src: '/(.*)',
headers: { 'cache-control': 's-maxage=0' },
dest: '/index.html',
},
],
};
}

View File

@@ -1,44 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectCreateReactApp({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('react-scripts');
if (!version) {
return false;
}
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'react-scripts build',
outputDirectory: 'build',
devCommand: 'react-scripts start',
devVariables: { BROWSER: 'none' },
framework: {
slug: 'react-scripts',
version,
},
routes: [
{
src: '/static/(.*)',
headers: { 'cache-control': 's-maxage=31536000, immutable' },
continue: true,
},
{
src: '/service-worker.js',
headers: { 'cache-control': 's-maxage=0' },
continue: true,
},
{
src: '/sockjs-node/(.*)',
dest: '/sockjs-node/$1',
},
{
handle: 'filesystem',
},
{
src: '/(.*)',
headers: { 'cache-control': 's-maxage=0' },
dest: '/index.html',
},
],
};
}

View File

@@ -1,17 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectDocusaurus({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('docusaurus');
if (!version) return false;
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'docusaurus-build',
outputDirectory: 'build',
devCommand: 'docusaurus-start --port $PORT',
framework: {
slug: 'docusaurus',
version,
},
};
}

View File

@@ -1,17 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectEleventy({
fs: { getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('@11ty/eleventy');
if (!version) return false;
return {
buildCommand: 'npx @11ty/eleventy',
outputDirectory: '_site',
devCommand: 'npx @11ty/eleventy --serve --watch --port $PORT',
framework: {
slug: '@11ty/eleventy',
version,
},
};
}

View File

@@ -1,26 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectEmber({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('ember-cli');
if (!version) return false;
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'ember build',
outputDirectory: 'dist',
devCommand: 'ember serve --port $PORT',
framework: {
slug: 'ember-cli',
version,
},
routes: [
{
handle: 'filesystem',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
};
}

View File

@@ -1,7 +1,3 @@
import yaml from 'js-yaml';
import toml from '@iarna/toml';
import { PackageJson } from '../types';
/**
* `DetectorFilesystem` is an abstract class that represents a virtual filesystem
* to perform read-only operations on in order to detect which framework is being
@@ -27,18 +23,16 @@ import { PackageJson } from '../types';
* functions. The easiest way to do this is to use the `=` syntax when defining
* methods in this class definition.
*/
export default abstract class DetectorFilesystem {
export abstract class DetectorFilesystem {
protected abstract _readFile(name: string): Promise<Buffer>;
protected abstract _exists(name: string): Promise<boolean>;
private existsCache: Map<string, Promise<boolean>>;
private readFileCache: Map<string, Promise<Buffer>>;
private readJsonCache: Map<string, Promise<any>>;
constructor() {
this.existsCache = new Map();
this.readFileCache = new Map();
this.readJsonCache = new Map();
}
public exists = async (name: string): Promise<boolean> => {
@@ -58,84 +52,4 @@ export default abstract class DetectorFilesystem {
}
return p;
};
public readJson = async <T>(name: string): Promise<T> => {
let p = this.readJsonCache.get(name);
if (!p) {
p = this.readFile(name).then(d => JSON.parse(d.toString('utf8')));
this.readJsonCache.set(name, p);
}
return p;
};
public readFileOrNull = async (name: string): Promise<Buffer | null> => {
return nullEnoent(this.readFile(name));
};
public readJsonOrNull = async <T>(name: string): Promise<T | null> => {
return nullEnoent(this.readJson<T>(name));
};
public readPackageJson = async (): Promise<PackageJson | null> => {
return await this.readJsonOrNull<PackageJson>('package.json');
};
public readConfigFile = async <T>(...names: string[]): Promise<T | null> => {
for (const name of names) {
const data = await this.readFileOrNull(name);
if (data) {
const str = data.toString('utf8');
if (name.endsWith('.json')) {
return JSON.parse(str);
} else if (name.endsWith('.toml')) {
return (toml.parse(str) as unknown) as T;
} else if (name.endsWith('.yaml') || name.endsWith('.yml')) {
return yaml.safeLoad(str, { filename: name });
}
}
}
return null;
};
public hasDependency = async (name: string): Promise<boolean> => {
const pkg = await this.readPackageJson();
const { dependencies = {}, devDependencies = {} } = pkg || {};
return name in dependencies || name in devDependencies;
};
public isNpm = async (): Promise<boolean> => {
return this.exists('package-lock.json');
};
public getPackageJsonCommand = async (
name: string
): Promise<string | null> => {
const pkg = await this.readPackageJson();
const { scripts = {} } = pkg || {};
return scripts[name] || null;
};
public getPackageJsonBuildCommand = async (): Promise<string | null> => {
const buildCommand = (await this.isNpm())
? 'npm run build'
: 'yarn run build';
return (await this.getPackageJsonCommand('build')) ? buildCommand : null;
};
public getDependencyVersion = async (name: string): Promise<string> => {
const pkg = await this.readPackageJson();
const { dependencies = {}, devDependencies = {} } = pkg || {};
return dependencies[name] || devDependencies[name];
};
}
async function nullEnoent<T>(p: Promise<T>): Promise<T | null> {
try {
return await p;
} catch (err) {
if (err.code === 'ENOENT') {
return null;
}
throw err;
}
}

View File

@@ -1,20 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectGatsby({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('gatsby');
if (!version) {
return false;
}
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'gatsby build',
outputDirectory: 'public',
devCommand: 'gatsby develop -p $PORT',
framework: {
slug: 'gatsby',
version,
},
cachePattern: '.cache/**',
};
}

View File

@@ -1,19 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectGenericNodeProject({
fs: { isNpm, getPackageJsonCommand },
}: DetectorParameters): Promise<DetectorResult> {
const useNpm = await isNpm();
const devCommand = await getPackageJsonCommand('dev');
const buildCommand = await getPackageJsonCommand('build');
if (!buildCommand) {
return false;
}
return {
buildCommand: `${useNpm ? 'npm' : 'yarn'} run build`,
devCommand: useNpm && devCommand ? `yarn run ${devCommand}` : undefined,
outputDirectory: 'public',
};
}

View File

@@ -1,19 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectGridsome({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('gridsome');
if (!version) {
return false;
}
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'gridsome build',
outputDirectory: 'dist',
devCommand: 'gridsome develop -p $PORT',
framework: {
slug: 'gridsom',
version,
},
};
}

View File

@@ -1,17 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectHexo({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('hexo');
if (!version) return false;
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'hexo generate',
outputDirectory: 'public',
devCommand: 'hexo server --port $PORT',
framework: {
slug: 'hexo',
version,
},
};
}

View File

@@ -1,30 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
/**
* https://gohugo.io/getting-started/configuration/#configuration-file
*/
interface HugoConfig {
publishDir?: string;
}
export default async function detectHugo({
fs: { readConfigFile },
}: DetectorParameters): Promise<DetectorResult> {
const config = await readConfigFile<HugoConfig>(
'config.toml',
'config.yaml',
'config.json'
);
if (!config) {
return false;
}
return {
buildCommand: 'hugo',
outputDirectory: config.publishDir || 'public',
devCommand: 'hugo server -D -w -p $PORT',
framework: {
slug: 'hugo',
version: 'latest',
},
};
}

View File

@@ -1,89 +0,0 @@
import AggregateError from 'aggregate-error';
import { Detector, DetectorParameters, DetectorResult } from '../types';
import angular from './angular';
import brunch from './brunch';
import createReactApp from './create-react-app';
import createReactAppEjected from './create-react-app-ejected';
import docusaurus from './docusaurus';
import eleventy from './eleventy';
import ember from './ember';
import gatsby from './gatsby';
import genericNodeProject from './generic-node-project';
import gridsome from './gridsome';
import hexo from './hexo';
import hugo from './hugo';
import jekyll from './jekyll';
import middleman from './middleman';
import next from './next';
import polymer from './polymer';
import preact from './preact';
import saber from './saber';
import sapper from './sapper';
import stencil from './stencil';
import svelte from './svelte';
import umi from './umi';
import vue from './vue';
export const pkgDetectors: Detector[] = [
angular,
brunch,
createReactApp,
createReactAppEjected,
docusaurus,
eleventy,
ember,
gatsby,
gridsome,
hexo,
next,
polymer,
preact,
saber,
sapper,
stencil,
svelte,
umi,
vue,
];
export const detectors: Detector[] = [hugo, jekyll, middleman];
export function firstTruthy<T>(promises: Promise<T>[]) {
return new Promise<T>((resolve, reject) => {
const errors: Array<Error> = [];
let unresolved = promises.length;
for (const p of promises) {
p.then(v => {
if (v || --unresolved === 0) {
resolve(v);
}
}).catch(err => {
errors.push(err);
if (--unresolved === 0) {
reject(new AggregateError(errors));
}
});
}
});
}
export async function detectDefaults(
params: DetectorParameters
): Promise<DetectorResult> {
// The `package.json` detectors are run first, since they share the common
// file read of `package.json` and are the most popular frameworks
let d: Detector[] = params.pkgDetectors || pkgDetectors;
let result: DetectorResult = await firstTruthy(
d.map(detector => detector(params))
);
if (!result) {
// If no `package.json` framework was detected then check the non-pkg ones
d = params.detectors || detectors;
result = await firstTruthy(d.map(detector => detector(params)));
}
if (!result) {
result = await genericNodeProject(params);
}
return result;
}

View File

@@ -1,26 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
/**
* https://jekyllrb.com/docs/configuration/options/
*/
interface JekyllConfig {
destination?: string;
}
export default async function detectJekyll({
fs: { readConfigFile },
}: DetectorParameters): Promise<DetectorResult> {
const config = await readConfigFile<JekyllConfig>('_config.yml');
if (!config) {
return false;
}
return {
buildCommand: 'jekyll build',
outputDirectory: config.destination || '_site',
devCommand: 'bundle exec jekyll serve --watch --port $PORT',
framework: {
slug: 'jekyll',
version: 'latest',
},
};
}

View File

@@ -1,18 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectMiddleman({
fs: { exists },
}: DetectorParameters): Promise<DetectorResult> {
const hasConfig = await exists('config.rb');
if (!hasConfig) return false;
return {
buildCommand: 'bundle exec middleman build',
outputDirectory: 'build',
devCommand: 'bundle exec middleman server -p $PORT',
framework: {
slug: 'middleman',
version: 'latest',
},
};
}

View File

@@ -1,17 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectNext({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('next');
if (!version) return false;
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'next build',
outputDirectory: '.next/static',
devCommand: 'next -p $PORT',
framework: {
slug: 'next',
version,
},
};
}

View File

@@ -1,26 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectPolymer({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('polymer-cli');
if (!version) return false;
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'polymer build',
outputDirectory: 'build',
devCommand: 'polymer serve --port $PORT',
framework: {
slug: 'polymer-cli',
version,
},
routes: [
{
handle: 'filesystem',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
};
}

View File

@@ -1,26 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectPreact({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('preact-cli');
if (!version) return false;
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'preact build',
outputDirectory: 'build',
devCommand: 'preact watch --port $PORT',
framework: {
slug: 'preact-cli',
version,
},
routes: [
{
handle: 'filesystem',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
};
}

View File

@@ -1,31 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectSaber({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('saber');
if (!version) return false;
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'saber build',
outputDirectory: 'public',
devCommand: 'saber --port $PORT',
routes: [
{
src: '/_saber/.*',
headers: { 'cache-control': 'max-age=31536000, immutable' },
},
{
handle: 'filesystem',
},
{
src: '.*',
status: 404,
dest: '404.html',
},
],
framework: {
slug: 'saber',
version,
},
};
}

View File

@@ -1,17 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectSapper({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('sapper');
if (!version) return false;
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'sapper export',
outputDirectory: '__sapper__/export',
devCommand: 'sapper dev --port $PORT',
framework: {
slug: 'sapper',
version,
},
};
}

View File

@@ -1,26 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectStencil({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('@stencil/core');
if (!version) return false;
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'stencil build',
outputDirectory: 'www',
devCommand: 'stencil build --dev --watch --serve --port $PORT',
framework: {
slug: '@stencil/core',
version,
},
routes: [
{
handle: 'filesystem',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
};
}

View File

@@ -1,26 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectSvelte({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('sirv-cli');
if (!version) return false;
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'rollup -c',
outputDirectory: 'public',
devCommand: 'sirv public --single --dev --port $PORT',
framework: {
slug: 'sirv-cli',
version,
},
routes: [
{
handle: 'filesystem',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
};
}

View File

@@ -1,26 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectUmiJS({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('umi');
if (!version) return false;
return {
buildCommand: (await getPackageJsonBuildCommand()) || 'umi build',
outputDirectory: 'dist',
devCommand: 'umi dev --port $PORT',
framework: {
slug: 'umi',
version,
},
routes: [
{
handle: 'filesystem',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
};
}

View File

@@ -1,37 +0,0 @@
import { DetectorParameters, DetectorResult } from '../types';
export default async function detectVue({
fs: { getPackageJsonBuildCommand, getDependencyVersion },
}: DetectorParameters): Promise<DetectorResult> {
const version = await getDependencyVersion('@vue/cli-service');
if (!version) return false;
return {
buildCommand:
(await getPackageJsonBuildCommand()) || 'vue-cli-service build',
outputDirectory: 'dist',
devCommand: 'vue-cli-service serve --port $PORT',
framework: {
slug: '@vue/cli-service',
version,
},
routes: [
{
src: '^/[^/]*\\.(js|txt|ico|json)',
headers: { 'cache-control': 'max-age=300' },
continue: true,
},
{
src: '^/(img|js|css|fonts|media)/.*',
headers: { 'cache-control': 'max-age=31536000, immutable' },
continue: true,
},
{
handle: 'filesystem',
},
{
src: '^.*',
dest: '/index.html',
},
],
};
}

View File

@@ -0,0 +1,18 @@
/**
* This error should be thrown from a Builder in
* order to stop the build and print a message.
* This is necessary to avoid printing a stack trace.
*/
export class NowBuildError extends Error {
public code: string;
constructor({ message, code }: Props) {
super(message);
this.code = code;
}
}
interface Props {
message: string;
code: string;
}

View File

@@ -1,6 +1,7 @@
import { intersects } from 'semver';
import boxen from 'boxen';
import { NodeVersion } from '../types';
import { NowBuildError } from '../errors';
import debug from '../debug';
const allOptions: NodeVersion[] = [
@@ -14,87 +15,90 @@ const allOptions: NodeVersion[] = [
},
];
const supportedOptions = allOptions.filter(o => !isDiscontinued(o));
const pleaseSet =
'Please set "engines": { "node": "' +
getLatestNodeVersion().range +
'" } in your `package.json` file to upgrade to Node.js ' +
getLatestNodeVersion().major;
const upstreamProvider =
'This change is the result of a decision made by an upstream infrastructure provider (AWS).' +
'\nRead more: https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html';
// This version should match Fargate's default in the PATH
// Today that is Node 8
export const defaultSelection = supportedOptions.find(
o => o.major === 8
) as NodeVersion;
export function getOldestNodeVersion(): NodeVersion {
return allOptions[allOptions.length - 1];
}
export function getLatestNodeVersion(): NodeVersion {
return allOptions[0];
}
export async function getSupportedNodeVersion(
engineRange?: string,
silent?: boolean
isAuto?: boolean
): Promise<NodeVersion> {
let selection = defaultSelection;
let selection = getOldestNodeVersion();
if (!engineRange) {
if (!silent) {
debug(
'Missing `engines` in `package.json`, using default range: ' +
selection.range
);
}
} else {
if (engineRange) {
const found = allOptions.some(o => {
// the array is already in order so return the first
// match which will be the newest version of node
selection = o;
return intersects(o.range, engineRange);
});
const discontinued = isDiscontinued(selection);
if (found && !discontinued) {
if (!silent) {
debug(
'Found `engines` in `package.json`, selecting range: ' +
selection.range
);
}
} else {
throw new Error(
'Found `engines` in `package.json` with an unsupported Node.js version range: ' +
engineRange +
'\nPlease use one of the following supported ranges: ' +
JSON.stringify(supportedOptions.map(o => o.range)) +
(discontinued
? '\nThis change is the result of a decision made by an upstream infrastructure provider (AWS).' +
'\nRead more: https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html'
: '')
);
if (!found) {
const intro =
isAuto || !engineRange
? 'This project is using an invalid version of Node.js and must be changed.'
: 'Found `engines` in `package.json` with an invalid Node.js version range: ' +
engineRange;
throw new NowBuildError({
code: 'NOW_BUILD_UTILS_NODE_VERSION_INVALID',
message: intro + '\n' + pleaseSet,
});
}
}
const { range, discontinueDate } = selection;
if (discontinueDate && !isDiscontinued(selection)) {
const d = discontinueDate.toISOString().split('T')[0];
const validRanges = supportedOptions
.filter(o => !o.discontinueDate)
.map(o => o.range);
const prevTerm = process.env.TERM;
if (!prevTerm) {
// workaround for https://github.com/sindresorhus/term-size/issues/13
process.env.TERM = 'xterm';
}
if (isDiscontinued(selection)) {
const intro =
isAuto || !engineRange
? 'This project is using a discontinued version of Node.js and must be upgraded.'
: 'Found `engines` in `package.json` with a discontinued Node.js version range: ' +
engineRange;
throw new NowBuildError({
code: 'NOW_BUILD_UTILS_NODE_VERSION_DISCONTINUED',
message: intro + '\n' + pleaseSet + '\n' + upstreamProvider,
});
}
debug(
isAuto || !engineRange
? 'Using default Node.js range: ' + selection.range
: (engineRange ? 'Found' : 'Missing') +
' `engines` in `package.json`, selecting range: ' +
selection.range
);
if (selection.discontinueDate) {
const d = selection.discontinueDate.toISOString().split('T')[0];
console.warn(
boxen(
'NOTICE' +
'\n' +
`\nNode.js version ${range} has reached end-of-life.` +
`\nNode.js version ${selection.range} has reached end-of-life.` +
`\nAs a result, deployments created on or after ${d} will fail to build.` +
'\nPlease use one of the following supported `engines` in `package.json`: ' +
JSON.stringify(validRanges) +
'\nThis change is the result of a decision made by an upstream infrastructure provider (AWS).' +
'\nRead more: https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html',
'\n' +
pleaseSet +
'\n' +
upstreamProvider,
{ padding: 1 }
)
);
process.env.TERM = prevTerm;
}
return selection;
}
function isDiscontinued({ discontinueDate }: NodeVersion): boolean {
const today = new Date();
return discontinueDate !== undefined && discontinueDate <= today;
const today = Date.now();
return discontinueDate !== undefined && discontinueDate.getTime() <= today;
}

View File

@@ -0,0 +1,37 @@
import yaml from 'js-yaml';
import toml from '@iarna/toml';
import { readFile } from 'fs-extra';
async function readFileOrNull(file: string) {
try {
const data = await readFile(file);
return data;
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}
return null;
}
export async function readConfigFile<T>(files: string | string[]) {
files = Array.isArray(files) ? files : [files];
for (const name of files) {
const data = await readFileOrNull(name);
if (data) {
const str = data.toString('utf8');
if (name.endsWith('.json')) {
return JSON.parse(str);
} else if (name.endsWith('.toml')) {
return (toml.parse(str) as unknown) as T;
} else if (name.endsWith('.yaml') || name.endsWith('.yml')) {
return yaml.safeLoad(str, { filename: name });
}
}
}
return null;
}

View File

@@ -7,7 +7,7 @@ import { SpawnOptions } from 'child_process';
import { deprecate } from 'util';
import { cpus } from 'os';
import { Meta, PackageJson, NodeVersion, Config } from '../types';
import { getSupportedNodeVersion } from './node-version';
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
export function spawnAsync(
command: string,
@@ -140,22 +140,30 @@ export function getSpawnOptions(
export async function getNodeVersion(
destPath: string,
minNodeVersion?: string,
config?: Config
config?: Config,
meta?: Meta
): Promise<NodeVersion> {
if (meta && meta.isDev) {
// Use the system-installed version of `node` in PATH for `now dev`
const latest = getLatestNodeVersion();
return { ...latest, runtime: 'nodejs' };
}
const { packageJson } = await scanParentDirs(destPath, true);
let range: string | undefined;
let silent = false;
let isAuto = false;
if (packageJson && packageJson.engines && packageJson.engines.node) {
range = packageJson.engines.node;
} else if (minNodeVersion) {
range = minNodeVersion;
silent = true;
isAuto = true;
} else if (config && config.nodeVersion) {
range = config.nodeVersion;
isAuto = true;
} else if (config && config.zeroConfig) {
// Use latest node version zero config detected
range = '10.x';
silent = true;
isAuto = true;
}
return getSupportedNodeVersion(range, silent);
return getSupportedNodeVersion(range, isAuto);
}
async function scanParentDirs(destPath: string, readPackageJson = false) {

View File

@@ -21,11 +21,9 @@ import {
getNodeVersion,
getSpawnOptions,
} from './fs/run-user-scripts';
import { getLatestNodeVersion } from './fs/node-version';
import streamToBuffer from './fs/stream-to-buffer';
import shouldServe from './should-serve';
import { detectBuilders } from './detect-builders';
import { detectRoutes } from './detect-routes';
import DetectorFilesystem from './detectors/filesystem';
import debug from './debug';
export {
@@ -33,7 +31,6 @@ export {
FileFsRef,
FileRef,
Lambda,
DetectorFilesystem,
createLambda,
Prerender,
download,
@@ -52,19 +49,21 @@ export {
runPipInstall,
runShellScript,
getNodeVersion,
getLatestNodeVersion,
getSpawnOptions,
streamToBuffer,
shouldServe,
detectBuilders,
detectRoutes,
debug,
isSymbolicLink,
getLambdaOptionsFromFunction,
};
export { detectBuildersLegacy } from './detect-builders-legacy';
export { detectRoutesLegacy } from './detect-routes-legacy';
export { detectRoutes, detectOutputDirectory } from './detect-routes';
export { detectBuilders } from './detect-builders';
export { detectFramework } from './detect-framework';
export { DetectorFilesystem } from './detectors/filesystem';
export { readConfigFile } from './fs/read-config-file';
export { detectDefaults } from './detectors';
export * from './schemas';
export * from './types';
export * from './errors';

View File

@@ -1,6 +1,5 @@
import FileRef from './file-ref';
import FileFsRef from './file-fs-ref';
import DetectorFilesystem from './detectors/filesystem';
export interface Env {
[name: string]: string | undefined;
@@ -21,18 +20,6 @@ export interface Files {
[filePath: string]: File;
}
export interface Route {
src?: string;
dest?: string;
handle?: string;
type?: string;
headers?: {
[key: string]: string;
};
continue?: boolean;
status?: number;
}
export interface Config {
[key: string]:
| string
@@ -55,10 +42,8 @@ export interface Config {
outputDirectory?: string;
buildCommand?: string;
devCommand?: string;
framework?: {
slug: string;
version: string;
};
framework?: string;
nodeVersion?: string;
}
export interface Meta {
@@ -350,33 +335,3 @@ export interface NowHeaderKeyValue {
key: string;
value: string;
}
export type Detector = (params: DetectorParameters) => Promise<DetectorResult>;
export interface DetectorParameters {
fs: DetectorFilesystem;
detectors?: Detector[];
pkgDetectors?: Detector[];
}
export interface DetectorOutput {
buildCommand: string;
outputDirectory: string;
buildVariables?: Env;
devCommand?: string;
devVariables?: Env;
minNodeRange?: string;
cachePattern?: string;
routes?: Route[];
cleanUrls?: boolean;
rewrites?: NowRewrite[];
redirects?: NowRedirect[];
headers?: NowHeader[];
trailingSlash?: boolean;
framework?: {
slug: string;
version: string;
};
}
export type DetectorResult = DetectorOutput | false;

View File

@@ -4,34 +4,13 @@ const {
packAndDeploy,
testDeployment,
} = require('../../../test/lib/deployment/test-deployment');
const {
glob,
detectBuilders,
detectRoutes,
DetectorFilesystem,
detectDefaults,
} = require('../');
const { glob, detectBuilders, detectRoutes } = require('../');
jest.setTimeout(4 * 60 * 1000);
const builderUrl = '@canary';
let buildUtilsUrl;
class LocalFilesystem extends DetectorFilesystem {
constructor(dir) {
super();
this.dir = dir;
}
_exists(name) {
return fs.pathExists(path.join(this.dir, name));
}
_readFile(name) {
return fs.readFile(path.join(this.dir, name));
}
}
beforeAll(async () => {
const buildUtilsPath = path.resolve(__dirname, '..');
buildUtilsUrl = await packAndDeploy(buildUtilsPath);
@@ -88,9 +67,7 @@ for (const builder of buildersToTestWith) {
it('Test `detectBuilders` and `detectRoutes`', async () => {
const fixture = path.join(__dirname, 'fixtures', '01-zero-config-api');
const detectorResult = await detectDefaults({
fs: new LocalFilesystem(fixture),
});
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
const fileList = await glob('**', fixture);
const files = Object.keys(fileList);
@@ -133,7 +110,7 @@ it('Test `detectBuilders` and `detectRoutes`', async () => {
},
];
const { builders } = await detectBuilders(files, detectorResult);
const { builders } = await detectBuilders(files, pkg);
const { defaultRoutes } = await detectRoutes(files, builders);
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
@@ -151,9 +128,7 @@ it('Test `detectBuilders` and `detectRoutes`', async () => {
it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
const detectorResult = await detectDefaults({
fs: new LocalFilesystem(fixture),
});
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
const fileList = await glob('**', fixture);
const files = Object.keys(fileList);
@@ -217,7 +192,7 @@ it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
},
];
const { builders } = await detectBuilders(files, detectorResult);
const { builders } = await detectBuilders(files, pkg);
const { defaultRoutes } = await detectRoutes(files, builders);
const nowConfig = { builds: builders, routes: defaultRoutes, probes };

File diff suppressed because it is too large Load Diff

View File

@@ -1,108 +0,0 @@
import assert from 'assert';
import { join } from 'path';
import { readFile, pathExists } from 'fs-extra';
import { detectDefaults, DetectorFilesystem } from '../src';
import { firstTruthy } from '../src/detectors';
class LocalFilesystem extends DetectorFilesystem {
private dir: string;
constructor(dir: string) {
super();
this.dir = dir;
}
_exists(name: string): Promise<boolean> {
return pathExists(join(this.dir, name));
}
_readFile(name: string): Promise<Buffer> {
return readFile(join(this.dir, name));
}
}
test('firstTruthy() - truthy', async () => {
const result = await firstTruthy([(async () => 0)(), (async () => 1)()]);
assert.equal(result, 1);
});
test('firstTruthy() - falsy', async () => {
const result = await firstTruthy([(async () => 0)(), (async () => 0)()]);
assert.equal(result, 0);
});
test('firstTruthy() - one throws', async () => {
const result = await firstTruthy([
(async () => {
throw new Error('bad');
})(),
(async () => 1)(),
]);
assert.equal(result, 1);
});
test('firstTruthy() - all throws', async () => {
let errors;
try {
await firstTruthy([
(async () => {
throw new Error('bad 1');
})(),
(async () => {
throw new Error('bad 2');
})(),
]);
} catch (_err) {
errors = _err;
}
assert(errors);
assert.equal(errors.name, 'AggregateError');
const arr = Array.from(errors) as Error[];
assert.equal(arr[0].message, 'bad 1');
assert.equal(arr[1].message, 'bad 2');
});
test('detectDefaults() - angular', async () => {
const dir = join(__dirname, 'fixtures', '03-zero-config-angular');
const fs = new LocalFilesystem(dir);
const result = await detectDefaults({ fs });
if (!result) throw new Error('Expected result');
assert.equal(result.outputDirectory, 'dist');
assert.deepEqual(result.buildCommand, 'yarn run build');
});
test('detectDefaults() - brunch', async () => {
const dir = join(__dirname, 'fixtures', '04-zero-config-brunch');
const fs = new LocalFilesystem(dir);
const result = await detectDefaults({ fs });
if (!result) throw new Error('Expected result');
assert.equal(result.outputDirectory, 'public');
assert.deepEqual(result.buildCommand, 'yarn run build');
});
test('detectDefaults() - hugo', async () => {
const dir = join(__dirname, 'fixtures', '06-zero-config-hugo');
const fs = new LocalFilesystem(dir);
const result = await detectDefaults({ fs });
if (!result) throw new Error('Expected result');
assert.equal(result.outputDirectory, 'public');
assert.deepEqual(result.buildCommand, 'hugo');
});
test('detectDefaults() - jekyll', async () => {
const dir = join(__dirname, 'fixtures', '07-zero-config-jekyll');
const fs = new LocalFilesystem(dir);
const result = await detectDefaults({ fs });
if (!result) throw new Error('Expected result');
assert.equal(result.outputDirectory, '_site');
assert.deepEqual(result.buildCommand, 'jekyll build');
});
test('detectDefaults() - middleman', async () => {
const dir = join(__dirname, 'fixtures', '08-zero-config-middleman');
const fs = new LocalFilesystem(dir);
const result = await detectDefaults({ fs });
if (!result) throw new Error('Expected result');
assert.equal(result.outputDirectory, 'build');
assert.deepEqual(result.buildCommand, 'bundle exec middleman build');
});

View File

@@ -0,0 +1,141 @@
import path from 'path';
import { readFileSync } from 'fs-extra';
import { Framework } from '@now/frameworks';
import { detectFramework, DetectorFilesystem } from '../src';
const frameworkList = JSON.parse(
readFileSync(
path.join(__dirname, '..', '..', 'frameworks', 'frameworks.json')
).toString()
) as Framework[];
class VirtualFilesystem extends DetectorFilesystem {
private files: Map<string, Buffer>;
constructor(files: { [key: string]: string | Buffer }) {
super();
this.files = new Map();
Object.entries(files).map(([key, value]) => {
const buffer = typeof value === 'string' ? Buffer.from(value) : value;
this.files.set(key, buffer);
});
}
async _exists(name: string): Promise<boolean> {
return this.files.has(name);
}
async _readFile(name: string): Promise<Buffer> {
const file = this.files.get(name);
if (file === undefined) {
throw new Error('File does not exist');
}
if (typeof file === 'string') {
return Buffer.from(file);
}
return file;
}
}
describe('#detectFramework', () => {
it('Do not detect anything', async () => {
const fs = new VirtualFilesystem({
'README.md': '# hi',
'api/cheese.js': 'export default (req, res) => res.end("cheese");',
});
expect(await detectFramework({ fs, frameworkList })).toBe(null);
});
it('Detect Next.js', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({
dependencies: {
next: '9.0.0',
},
}),
});
expect(await detectFramework({ fs, frameworkList })).toBe('nextjs');
});
it('Detect Nuxt.js', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({
dependencies: {
nuxt: '1.0.0',
},
}),
});
expect(await detectFramework({ fs, frameworkList })).toBe('nuxtjs');
});
it('Detect Gatsby', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({
dependencies: {
gatsby: '1.0.0',
},
}),
});
expect(await detectFramework({ fs, frameworkList })).toBe('gatsby');
});
it('Detect Hugo #1', async () => {
const fs = new VirtualFilesystem({
'config.yaml': 'config',
});
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
});
it('Detect Hugo #2', async () => {
const fs = new VirtualFilesystem({
'config.json': 'config',
});
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
});
it('Detect Hugo #3', async () => {
const fs = new VirtualFilesystem({
'config.toml': 'config',
});
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
});
it('Detect Jekyll', async () => {
const fs = new VirtualFilesystem({
'_config.yml': 'config',
});
expect(await detectFramework({ fs, frameworkList })).toBe('jekyll');
});
it('Detect Middleman', async () => {
const fs = new VirtualFilesystem({
'config.rb': 'config',
});
expect(await detectFramework({ fs, frameworkList })).toBe('middleman');
});
it('Detect Scully', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({
dependencies: {
'@angular/cli': 'latest',
'@scullyio/init': 'latest',
},
}),
});
expect(await detectFramework({ fs, frameworkList })).toBe('scully');
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@
To install the latest version of Now CLI, visit [zeit.co/download](https://zeit.co/download) or run this command:
```bash
```sh
npm i -g now
```

View File

@@ -1,6 +1,6 @@
{
"name": "now",
"version": "16.7.1-canary.1",
"version": "16.7.2-canary.3",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Now",

View File

@@ -17,9 +17,7 @@ export default async function dev(
args: string[],
output: Output
) {
output.dim(
`Now CLI ${pkg.version} dev (beta) — https://zeit.co/feedback`
);
output.dim(`Now CLI ${pkg.version} dev (beta) — https://zeit.co/feedback`);
const [dir = '.'] = args;
const cwd = path.resolve(dir);

View File

@@ -18,7 +18,10 @@ import getMinFromArgs from '../util/scale/get-min-from-args';
import patchDeploymentScale from '../util/scale/patch-deployment-scale';
import waitVerifyDeploymentScale from '../util/scale/wait-verify-deployment-scale';
import { handleError } from '../util/error';
import { VerifyScaleTimeout, DeploymentTypeUnsupported } from '../util/errors-ts';
import {
VerifyScaleTimeout,
DeploymentTypeUnsupported,
} from '../util/errors-ts';
import {
DeploymentNotFound,
DeploymentPermissionDenied,
@@ -28,7 +31,7 @@ import {
InvalidMaxForScale,
InvalidMinForScale,
InvalidScaleMinMaxRelation,
NotSupportedMinScaleSlots
NotSupportedMinScaleSlots,
} from '../util/errors-ts';
import { InvalidAllForScale, InvalidRegionOrDCForScale } from '../util/errors';
import handleCertError from '../util/certs/handle-cert-error';
@@ -56,7 +59,9 @@ const help = () => {
${chalk.dim('Examples:')}
${chalk.gray('')} Enable your deployment in all datacenters (min: 0, max: auto)
${chalk.gray(
''
)} Enable your deployment in all datacenters (min: 0, max: auto)
${chalk.cyan('$ now scale my-deployment-123.now.sh all')}
@@ -87,7 +92,7 @@ export default async function main(ctx) {
argv = getArgs(ctx.argv.slice(2), {
'--verify-timeout': Number,
'--no-verify': Boolean,
'-n': '--no-verify'
'-n': '--no-verify',
});
} catch (err) {
handleError(err);
@@ -100,7 +105,10 @@ export default async function main(ctx) {
}
// Prepare the context
const { authConfig: { token }, config } = ctx;
const {
authConfig: { token },
config,
} = ctx;
const { currentTeam } = config;
const { apiUrl } = ctx;
const debug = argv['--debug'];
@@ -155,8 +163,7 @@ export default async function main(ctx) {
}
if (dcs instanceof InvalidRegionOrDCForScale) {
output.error(
`The value "${dcs.meta
.regionOrDC}" is not a valid region or DC identifier`
`The value "${dcs.meta.regionOrDC}" is not a valid region or DC identifier`
);
now.close();
return 1;
@@ -165,8 +172,7 @@ export default async function main(ctx) {
const min = getMinFromArgs(argv._);
if (min instanceof InvalidMinForScale) {
output.error(
`Invalid <min> parameter "${min.meta
.value}". A number or "auto" were expected`
`Invalid <min> parameter "${min.meta.value}". A number or "auto" were expected`
);
now.close();
return 1;
@@ -175,24 +181,21 @@ export default async function main(ctx) {
const max = getMaxFromArgs(argv._);
if (max instanceof InvalidMinForScale) {
output.error(
`Invalid <min> parameter "${max.meta
.value}". A number or "auto" were expected`
`Invalid <min> parameter "${max.meta.value}". A number or "auto" were expected`
);
now.close();
return 1;
}
if (max instanceof InvalidArgsForMinMaxScale) {
output.error(
`Invalid number of arguments: expected <min> ("${max.meta
.min}") and [max]`
`Invalid number of arguments: expected <min> ("${max.meta.min}") and [max]`
);
now.close();
return 1;
}
if (max instanceof InvalidMaxForScale) {
output.error(
`Invalid <max> parameter "${max.meta
.value}". A number or "auto" were expected`
`Invalid <max> parameter "${max.meta.value}". A number or "auto" were expected`
);
now.close();
return 1;
@@ -262,14 +265,15 @@ export default async function main(ctx) {
deployment.url
);
if (result instanceof ForbiddenScaleMinInstances) {
output.error(`You can't scale to more than ${result.meta.max} min instances with your current plan.`);
output.error(
`You can't scale to more than ${result.meta.max} min instances with your current plan.`
);
now.close();
return 1;
}
if (result instanceof ForbiddenScaleMaxInstances) {
output.error(
`You can't scale to more than ${result.meta
.max} max instances with your current plan.`
`You can't scale to more than ${result.meta.max} max instances with your current plan.`
);
now.close();
return 1;

View File

@@ -82,16 +82,6 @@ export default async function processDeployment({
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
);
if (!quiet) {
log(
`Synced ${pluralize(
'file',
event.payload.missing.length,
true
)} ${uploadStamp()}`
);
}
const missingSize = event.payload.missing
.map((sha: string) => event.payload.total.get(sha).data.length)
.reduce((a: number, b: number) => a + b, 0);
@@ -121,6 +111,13 @@ export default async function processDeployment({
now._host = event.payload.url;
if (!quiet) {
log(
`Synced ${pluralize(
'file',
event.payload.missing.length,
true
)} ${uploadStamp()}`
);
const version = isLegacy ? `${chalk.grey('[v1]')} ` : '';
log(`https://${event.payload.url} ${version}${deployStamp()}`);
} else {
@@ -128,7 +125,10 @@ export default async function processDeployment({
}
if (queuedSpinner === null) {
queuedSpinner = wait('Queued...');
queuedSpinner =
event.payload.readyState === 'QUEUED'
? wait('Queued...')
: wait('Building...');
}
}
@@ -200,15 +200,6 @@ export default async function processDeployment({
debug(
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
);
if (!quiet) {
log(
`Synced ${pluralize(
'file',
event.payload.missing.length,
true
)} ${uploadStamp()}`
);
}
const missingSize = event.payload.missing
.map((sha: string) => event.payload.total.get(sha).data.length)
@@ -239,6 +230,13 @@ export default async function processDeployment({
now._host = event.payload.url;
if (!quiet) {
log(
`Synced ${pluralize(
'file',
event.payload.missing.length,
true
)} ${uploadStamp()}`
);
const version = isLegacy ? `${chalk.grey('[v1]')} ` : '';
log(`${event.payload.url} ${version}${deployStamp()}`);
} else {

View File

@@ -101,7 +101,11 @@ export async function prepareBuilderDir() {
if (!hasBundledBuilders(dependencies)) {
const extractor = extract(builderDir);
await pipe(createReadStream(bundledTarballPath), createGunzip(), extractor);
await pipe(
createReadStream(bundledTarballPath),
createGunzip(),
extractor
);
}
return builderDir;
@@ -289,6 +293,7 @@ export async function updateBuilders(
if (!builderDir) {
builderDir = await builderDirPromise;
}
const packages = Array.from(packagesSet);
const yarnPath = join(yarnDir, 'yarn');
const buildersPkgPath = join(builderDir, 'package.json');

View File

@@ -22,8 +22,6 @@ import {
PackageJson,
detectBuilders,
detectRoutes,
detectDefaults,
DetectorFilesystem,
} from '@now/build-utils';
import { once } from '../once';
@@ -102,25 +100,6 @@ function sortBuilders(buildA: Builder, buildB: Builder) {
return 0;
}
class DevDetectorFilesystem extends DetectorFilesystem {
private dir: string;
private files: string[];
constructor(dir: string, files: string[]) {
super();
this.dir = dir;
this.files = files;
}
_exists(name: string): Promise<boolean> {
return Promise.resolve(this.files.includes(name));
}
_readFile(name: string): Promise<Buffer> {
return fs.readFile(join(this.dir, name));
}
}
export default class DevServer {
public cwd: string;
public debug: boolean;
@@ -498,6 +477,8 @@ export default class DevServer {
return this.cachedNowConfig;
}
const pkg = await this.getPackageJson();
// The default empty `now.json` is used to serve all files as static
// when no `now.json` is present
let config: NowConfig = this.cachedNowConfig || { version: 2 };
@@ -545,18 +526,13 @@ export default class DevServer {
// no builds -> zero config
if (!config.builds || config.builds.length === 0) {
const detectorResult = await detectDefaults({
fs: new DevDetectorFilesystem(this.cwd, files),
});
const { projectSettings } = config;
const { builders, warnings, errors } = await detectBuilders(
files,
detectorResult,
{
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest',
functions: config.functions,
}
);
const { builders, warnings, errors } = await detectBuilders(files, pkg, {
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest',
functions: config.functions,
...(projectSettings ? { projectSettings } : {}),
});
if (errors) {
this.output.error(errors[0].message);
@@ -568,10 +544,11 @@ export default class DevServer {
}
if (builders) {
const { defaultRoutes, error: routesError } = await detectRoutes(
files,
builders
);
const {
defaultRoutes,
redirectRoutes,
error: routesError,
} = await detectRoutes(files, builders);
config.builds = config.builds || [];
config.builds.push(...builders);
@@ -580,8 +557,12 @@ export default class DevServer {
this.output.error(routesError.message);
await this.exit();
} else {
config.routes = config.routes || [];
config.routes.push(...(defaultRoutes as RouteConfig[]));
const routes: RouteConfig[] = [];
const { routes: nowConfigRoutes } = config;
routes.push(...(redirectRoutes || []));
routes.push(...(nowConfigRoutes || []));
routes.push(...(defaultRoutes || []));
config.routes = routes;
}
}
}
@@ -602,6 +583,29 @@ export default class DevServer {
return config;
}
async getPackageJson(): Promise<PackageJson | null> {
const pkgPath = join(this.cwd, 'package.json');
let pkg: PackageJson | null = null;
this.output.debug('Reading `package.json` file');
try {
pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
} catch (err) {
if (err.code === 'ENOENT') {
this.output.debug('No `package.json` file present');
} else if (err.name === 'SyntaxError') {
this.output.warn(
`There is a syntax error in the \`package.json\` file: ${err.message}`
);
} else {
throw err;
}
}
return pkg;
}
async tryValidateOrExit(
config: NowConfig,
validate: (c: NowConfig) => string | null

View File

@@ -15,7 +15,7 @@ export default `.hg
.wafpicke-*
.lock-wscript
.env
.env.build
.env.*
.venv
npm-debug.log
config.gypi

View File

@@ -9,4 +9,4 @@
"dependencies": {
"gridsome": "^0.6.0"
}
}
}

View File

@@ -6698,21 +6698,21 @@ string.prototype.padstart@^3.0.0:
es-abstract "^1.4.3"
function-bind "^1.0.2"
string.prototype.trimleft@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.0.0.tgz#68b6aa8e162c6a80e76e3a8a0c2e747186e271ff"
integrity sha1-aLaqjhYsaoDnbjqKDC50cYbicf8=
string.prototype.trimleft@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634"
integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==
dependencies:
define-properties "^1.1.2"
function-bind "^1.0.2"
define-properties "^1.1.3"
function-bind "^1.1.1"
string.prototype.trimright@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.0.0.tgz#ab4a56d802a01fbe7293e11e84f24dc8164661dd"
integrity sha1-q0pW2AKgH75yk+EehPJNyBZGYd0=
string.prototype.trimright@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58"
integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==
dependencies:
define-properties "^1.1.2"
function-bind "^1.0.2"
define-properties "^1.1.3"
function-bind "^1.1.1"
string_decoder@^1.0.0, string_decoder@^1.1.1:
version "1.3.0"

View File

@@ -7307,21 +7307,21 @@ string.prototype.padstart@^3.0.0:
es-abstract "^1.4.3"
function-bind "^1.0.2"
string.prototype.trimleft@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.0.0.tgz#68b6aa8e162c6a80e76e3a8a0c2e747186e271ff"
integrity sha1-aLaqjhYsaoDnbjqKDC50cYbicf8=
string.prototype.trimleft@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634"
integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==
dependencies:
define-properties "^1.1.2"
function-bind "^1.0.2"
define-properties "^1.1.3"
function-bind "^1.1.1"
string.prototype.trimright@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.0.0.tgz#ab4a56d802a01fbe7293e11e84f24dc8164661dd"
integrity sha1-q0pW2AKgH75yk+EehPJNyBZGYd0=
string.prototype.trimright@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58"
integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==
dependencies:
define-properties "^1.1.2"
function-bind "^1.0.2"
define-properties "^1.1.3"
function-bind "^1.1.1"
string_decoder@^1.0.0, string_decoder@^1.1.1:
version "1.3.0"

View File

@@ -5746,7 +5746,7 @@ fsevents@^2.0.6:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a"
integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==
function-bind@^1.0.2, function-bind@^1.1.1:
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
@@ -11917,21 +11917,21 @@ string.prototype.trim@^1.1.2:
es-abstract "^1.13.0"
function-bind "^1.1.1"
string.prototype.trimleft@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.0.0.tgz#68b6aa8e162c6a80e76e3a8a0c2e747186e271ff"
integrity sha1-aLaqjhYsaoDnbjqKDC50cYbicf8=
string.prototype.trimleft@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634"
integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==
dependencies:
define-properties "^1.1.2"
function-bind "^1.0.2"
define-properties "^1.1.3"
function-bind "^1.1.1"
string.prototype.trimright@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.0.0.tgz#ab4a56d802a01fbe7293e11e84f24dc8164661dd"
integrity sha1-q0pW2AKgH75yk+EehPJNyBZGYd0=
string.prototype.trimright@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58"
integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==
dependencies:
define-properties "^1.1.2"
function-bind "^1.0.2"
define-properties "^1.1.3"
function-bind "^1.1.1"
string_decoder@^1.0.0, string_decoder@^1.1.1:
version "1.3.0"

View File

@@ -0,0 +1,2 @@
package.json
yarn.lock

File diff suppressed because it is too large Load Diff

View File

@@ -311,6 +311,13 @@ CMD ["node", "index.js"]`,
files: ['.gitignore', 'folder', 'index.js', 'test.html'],
}),
},
'redirects-v2': {
'now.json': JSON.stringify({
version: 2,
name: 'redirects-v2',
redirects: [{ source: `/(.*)`, destination: 'https://example.com/$1' }],
}),
},
'local-config-v2': {
[`main-${session}.html`]: '<h1>hello main</h1>',
[`test-${session}.html`]: '<h1>hello test</h1>',

View File

@@ -211,6 +211,26 @@ test('login', async t => {
t.is(typeof token, 'string');
});
test('deploy using only now.json with `redirects` defined', async t => {
const target = fixture('redirects-v2');
const { exitCode, stderr, stdout } = await execa(
binaryPath,
[target, ...defaultArgs],
{
reject: false,
}
);
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
const i = stdout.lastIndexOf('https://');
const url = stdout.slice(i);
const res = await fetch(`${url}/foo/bar`, { redirect: 'manual' });
const location = res.headers.get('location');
t.is(location, 'https://example.com/foo/bar');
});
test('deploy using --local-config flag v2', async t => {
const target = fixture('local-config-v2');
const configPath = path.join(target, 'now-test.json');

View File

@@ -1,6 +1,6 @@
{
"name": "now-client",
"version": "6.0.0",
"version": "6.0.2-canary.0",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://zeit.co",
@@ -38,7 +38,7 @@
]
},
"dependencies": {
"@zeit/fetch": "5.1.0",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",
"fs-extra": "8.0.1",

View File

@@ -115,8 +115,13 @@ export async function* deploy(
if (
files.size === 1 &&
!deploymentOptions.builds &&
!deploymentOptions.routes
deploymentOptions.builds === undefined &&
deploymentOptions.routes === undefined &&
deploymentOptions.cleanUrls === undefined &&
deploymentOptions.rewrites === undefined &&
deploymentOptions.redirects === undefined &&
deploymentOptions.headers === undefined &&
deploymentOptions.trailingSlash === undefined
) {
debug(`Assigning '/' route for single file deployment`);
const filePath = Array.from(files.values())[0].names[0];

View File

@@ -116,6 +116,12 @@ export interface NowConfig extends LegacyNowConfig {
github?: DeploymentGithubData;
scope?: string;
alias?: string | string[];
projectSettings?: {
devCommand?: string | null;
buildCommand?: string | null;
outputDirectory?: string | null;
framework?: string | null;
};
}
interface LegacyDeploymentOptions {
@@ -147,6 +153,11 @@ export interface DeploymentOptions extends LegacyDeploymentOptions {
version?: number;
regions?: string[];
routes?: Route[];
cleanUrls?: boolean;
rewrites?: NowRewrite[];
redirects?: NowRedirect[];
headers?: NowHeader[];
trailingSlash?: boolean;
builds?: Builder[];
functions?: BuilderFunctions;
env?: Dictionary<string>;

View File

@@ -115,7 +115,8 @@ export async function* upload(
apiUrl,
userAgent,
},
clientOptions.debug
clientOptions.debug,
true
);
if (res.status === 200) {
@@ -168,8 +169,9 @@ export async function* upload(
return result;
},
{
retries: 3,
factor: 2,
retries: 5,
factor: 6,
minTimeout: 10,
}
);
});

View File

@@ -0,0 +1,6 @@
import nodeFetch from 'node-fetch';
import setupZeitFetch from '@zeit/fetch';
const zeitFetch = setupZeitFetch(nodeFetch);
export { zeitFetch, nodeFetch };

View File

@@ -1,6 +1,7 @@
import { DeploymentFile } from './hashes';
import { parse as parseUrl } from 'url';
import fetch_, { RequestInit } from 'node-fetch';
import { RequestInit } from 'node-fetch';
import { nodeFetch, zeitFetch } from './fetch';
import { join, sep } from 'path';
import qs from 'querystring';
import ignore from 'ignore';
@@ -85,7 +86,7 @@ export async function getNowIgnore(path: string | string[]): Promise<any> {
'.wafpicke-*',
'.lock-wscript',
'.env',
'.env.build',
'.env.*',
'.venv',
'npm-debug.log',
'config.gypi',
@@ -122,7 +123,8 @@ export const fetch = async (
url: string,
token: string,
opts: FetchOpts = {},
debugEnabled?: boolean
debugEnabled?: boolean,
useNodeFetch?: boolean
): Promise<any> => {
semaphore.acquire();
const debug = createDebug(debugEnabled);
@@ -152,7 +154,9 @@ export const fetch = async (
debug(`${opts.method || 'GET'} ${url}`);
time = Date.now();
const res = await fetch_(url, opts);
const res = useNodeFetch
? await nodeFetch(url, opts)
: await zeitFetch(url, opts);
debug(`DONE in ${Date.now() - time}ms: ${opts.method || 'GET'} ${url}`);
semaphore.release();

View File

@@ -1,6 +1,6 @@
{
"name": "@now/next",
"version": "2.3.1-canary.0",
"version": "2.3.7",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/next-js",
@@ -25,7 +25,7 @@
"@types/resolve-from": "5.0.1",
"@types/semver": "6.0.0",
"@types/yazl": "2.4.1",
"@zeit/node-file-trace": "0.4.0",
"@zeit/node-file-trace": "0.4.1",
"async-sema": "3.0.1",
"buffer-crc32": "0.2.13",
"execa": "2.0.4",

View File

@@ -15,10 +15,10 @@ import {
PackageJson,
PrepareCacheOptions,
Prerender,
Route,
runNpmInstall,
runPackageJsonScript,
} from '@now/build-utils';
import { Route } from '@now/routing-utils';
import {
convertRedirects,
convertRewrites,
@@ -202,7 +202,7 @@ export const build = async ({
const pkg = await readPackageJson(entryPath);
const nextVersion = getNextVersion(pkg);
const nodeVersion = await getNodeVersion(entryPath, undefined, config);
const nodeVersion = await getNodeVersion(entryPath, undefined, config, meta);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
if (!nextVersion) {
@@ -336,12 +336,35 @@ export const build = async ({
const routesManifest = await getRoutesManifest(entryPath, realNextVersion);
const rewrites: Route[] = [];
const redirects: Route[] = [];
const nextBasePathRoute: Route[] = [];
let nextBasePath: string | undefined;
if (routesManifest) {
switch (routesManifest.version) {
case 1: {
case 1:
case 2: {
redirects.push(...convertRedirects(routesManifest.redirects));
rewrites.push(...convertRewrites(routesManifest.rewrites));
if (routesManifest.basePath && routesManifest.basePath !== '/') {
nextBasePath = routesManifest.basePath;
if (!nextBasePath.startsWith('/')) {
throw new Error(
'basePath must start with `/`. Please upgrade your `@now/next` builder and try again. Contact support if this continues to happen.'
);
}
if (nextBasePath.endsWith('/')) {
throw new Error(
'basePath must not end with `/`. Please upgrade your `@now/next` builder and try again. Contact support if this continues to happen.'
);
}
nextBasePathRoute.push({
src: `^${nextBasePath}(?:$|/(.*))$`,
dest: `/$1`,
continue: true,
});
}
break;
}
default: {
@@ -354,30 +377,12 @@ export const build = async ({
}
}
const exportIntent = await getExportIntent(entryPath);
const userExport = await getExportStatus(entryPath);
if (exportIntent || userExport) {
if (userExport) {
const exportIntent = await getExportIntent(entryPath);
const { trailingSlash = false } = exportIntent || {};
if (!userExport) {
await writePackageJson(entryPath, {
...pkg,
scripts: {
...pkg.scripts,
'now-automatic-next-export': `next export --outdir "${path.resolve(
entryPath,
'out'
)}"`,
},
});
await runPackageJsonScript(entryPath, 'now-automatic-next-export', {
...spawnOpts,
env,
});
}
const resultingExport = await getExportStatus(entryPath);
if (!resultingExport) {
throw new Error(
@@ -417,6 +422,9 @@ export const build = async ({
routes: [
// TODO: low priority: handle trailingSlash
// Add top level rewrite for basePath if provided
...nextBasePathRoute,
// redirects take the highest priority
...redirects,
// Before we handle static files we need to set proper caching headers
@@ -958,6 +966,9 @@ export const build = async ({
...staticDirectoryFiles,
},
routes: [
// Add top level rewrite for basePath if provided
...nextBasePathRoute,
// redirects take the highest priority
...redirects,
// Before we handle static files we need to set proper caching headers

View File

@@ -11,9 +11,9 @@ import {
FileFsRef,
streamToBuffer,
Lambda,
Route,
isSymbolicLink,
} from '@now/build-utils';
import { Route, Source } from '@now/routing-utils';
type stringMap = { [key: string]: string };
@@ -279,13 +279,13 @@ async function getRoutes(
if (!isPublic) continue;
const fileName = path.relative('public', relativePath);
const route = {
const route: Source = {
src: `${prefix}${fileName}`,
dest: `${url}/${fileName}`,
};
// Only add the route if a page is not already using it
if (!routes.some(r => r.src === route.src)) {
if (!routes.some(r => (r as Source).src === route.src)) {
routes.push(route);
}
}
@@ -308,6 +308,7 @@ type RoutesManifestRegex = {
};
export type RoutesManifest = {
basePath: string | undefined;
redirects: (Redirect & RoutesManifestRegex)[];
rewrites: (Rewrite & RoutesManifestRegex)[];
dynamicRoutes: {
@@ -360,7 +361,8 @@ export async function getDynamicRoutes(
if (routesManifest) {
switch (routesManifest.version) {
case 1: {
case 1:
case 2: {
return routesManifest.dynamicRoutes.map(
({ page, regex }: { page: string; regex: string }) => {
return {

View File

@@ -12,7 +12,7 @@
},
{
"path": "/",
"mustContain": "nextExport\":true"
"mustNotContain": "nextExport\":true"
}
]
}

View File

@@ -1,4 +1,7 @@
{
"scripts": {
"build": "next build && next export"
},
"dependencies": {
"next": "canary",
"react": "^16.8.6",

View File

@@ -0,0 +1,8 @@
const withOffline = require('next-offline');
module.exports = withOffline({
generateBuildId() {
return 'testing-build-id';
},
exportPathMap: d => d,
});

View File

@@ -0,0 +1,18 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/next" }],
"probes": [
{
"path": "/",
"mustContain": "Hi There"
},
{
"path": "/about",
"mustContain": "Hi on About"
},
{
"path": "/",
"mustNotContain": "nextExport\":true"
}
]
}

View File

@@ -0,0 +1,8 @@
{
"dependencies": {
"next": "canary",
"next-offline": "4.0.6",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1,7 @@
function About() {
return <div>Hi on About</div>;
}
About.getInitialProps = () => ({});
export default About;

View File

@@ -0,0 +1,7 @@
function Home() {
return <div>Hi There</div>;
}
Home.getInitialProps = () => ({});
export default Home;

View File

@@ -0,0 +1,8 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
experimental: {
basePath: '/docs',
},
};

View File

@@ -0,0 +1,24 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/next" }],
"probes": [
{
"path": "/docs/_next/static/testing-build-id/pages/index.js",
"responseHeaders": {
"cache-control": "public,max-age=31536000,immutable"
}
},
{
"path": "/docs/",
"mustContain": "hello from index"
},
{
"path": "/docs",
"mustContain": "hello from index"
},
{
"path": "/docs/another",
"mustContain": "hello from another"
}
]
}

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1,5 @@
const Page = () => 'hello from another';
Page.getInitialProps = () => ({ hello: 'world' });
export default Page;

View File

@@ -0,0 +1 @@
export default () => 'hello from index';

View File

@@ -1,6 +1,6 @@
{
"name": "@now/node",
"version": "1.3.0",
"version": "1.3.3",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/node-js",
@@ -30,7 +30,7 @@
"@types/etag": "1.8.0",
"@types/test-listen": "1.1.0",
"@zeit/ncc": "0.20.4",
"@zeit/node-file-trace": "0.4.0",
"@zeit/node-file-trace": "0.4.1",
"content-type": "1.0.4",
"cookie": "0.4.0",
"etag": "1.8.1",

View File

@@ -71,7 +71,8 @@ async function downloadInstallAndBundle({
const nodeVersion = await getNodeVersion(
entrypointFsDirname,
undefined,
config
config,
meta
);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
await runNpmInstall(
@@ -369,16 +370,13 @@ export async function build({
});
}
// Use the system-installed version of `node` when running via `now dev`
const runtime = meta.isDev ? 'nodejs' : nodeVersion.runtime;
const lambda = await createLambda({
files: {
...preparedFiles,
...launcherFiles,
},
handler: `${LAUNCHER_FILENAME}.launcher`,
runtime,
runtime: nodeVersion.runtime,
});
return { output: lambda, watch };

View File

@@ -1,5 +1,6 @@
import { relative, basename, resolve, dirname } from 'path';
import _ts from 'typescript';
import { NowBuildError } from '@now/build-utils';
/*
* Fork of TS-Node - https://github.com/TypeStrong/ts-node
@@ -180,8 +181,8 @@ export function register(opts: Options = {}): Register {
};
function createTSError(diagnostics: ReadonlyArray<_ts.Diagnostic>) {
const diagnosticText = formatDiagnostics(diagnostics, diagnosticHost);
return new Error(diagnosticText);
const message = formatDiagnostics(diagnostics, diagnosticHost);
return new NowBuildError({ code: 'NOW_NODE_TYPESCRIPT_ERROR', message });
}
function reportTSError(

View File

@@ -7,7 +7,7 @@
}
],
"probes": [
{ "path": "/empty", "mustContain": "RANDOMNESS_PLACEHOLDER:8" },
{ "path": "/empty", "mustContain": "RANDOMNESS_PLACEHOLDER:12" },
{ "path": "/exact", "mustContain": "RANDOMNESS_PLACEHOLDER:10" },
{ "path": "/greater", "mustContain": "RANDOMNESS_PLACEHOLDER:12" },
{ "path": "/major", "mustContain": "RANDOMNESS_PLACEHOLDER:10" },

View File

@@ -1,7 +1,10 @@
{
"engines": {
"node": "10.x"
},
"dependencies": {
"chrome-aws-lambda": "1.11.1",
"lighthouse": "4.3.1",
"puppeteer-core": "1.11.0"
"chrome-aws-lambda": "1.20.4",
"lighthouse": "5.6.0",
"puppeteer-core": "1.20.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@now/python",
"version": "1.0.1",
"version": "1.1.1-canary.0",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/python",

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