Compare commits

..

104 Commits

Author SHA1 Message Date
Steven
8d5f6f1fa3 Publish Stable
- @now/frameworks@0.0.10
 - @now/build-utils@2.0.0
 - now@17.0.4
 - @now/go@1.0.4
 - @now/next@2.3.16
 - @now/python@1.1.4
 - @now/routing-utils@1.6.0
2020-02-21 18:09:20 -05:00
Steven
617f610c07 [tests] Update cancel workflow (#3817)
Update to latest version, [0.2.0](https://github.com/styfle/cancel-workflow-action/releases/tag/0.2.0), which will fix cancelling on push.

Also this action is available in the [marketplace](https://github.com/marketplace/actions/cancel-workflow-action).
2020-02-21 18:00:27 -05:00
Steven
3229ba0106 Publish Canary
- @now/go@1.0.4-canary.3
 - @now/python@1.1.4-canary.1
2020-02-21 17:27:20 -05:00
Steven
d16a6e4716 [now-go] Fix path segments in now dev (#3816)
The combination of file renaming and brackets doesn't work well for go build so we need to add the file extension back before building.

I also simplified the `.now/cache/folder` logic because it should be the same whether `builds` is defined or not (aka zero config).
2020-02-21 22:25:44 +00:00
Steven
4e5156fb0b [now-python] Fix path segments in now dev (#3815)
The combination of file renaming and brackets doesn't work well for python imports so we need too add the file extension back before importing the python module.

I also simplified the `.now/cache/folder` logic because it should be the same whether `builds` is defined or not (aka zero config).
2020-02-21 20:45:41 +00:00
Joe Haddad
3c013cc36b Publish Canary
- @now/build-utils@2.0.0-canary.2
 - @now/next@2.3.16-canary.1
2020-02-21 11:06:44 -05:00
Joe Haddad
1ed83135be Add Bypass Token to Next.js Builder (#3811)
This pull request adds the bypass token to returned `Prerender` output objects.

There's no tests as this doesn't exist in production yet.
2020-02-21 16:05:22 +00:00
Joe Haddad
0e71ff2df1 [now-build-utils] Add bypassToken to Prerender for iSSG (#3810)
This pull request adds a new `bypassToken` field to the `Prerender` output.

The field is required to be at least 128 bits for security reasons!
2020-02-20 01:59:45 +00:00
JJ Kasper
d8e98ee476 Publish Canary
- @now/next@2.3.16-canary.0
 - @now/routing-utils@1.5.4-canary.0
2020-02-19 16:23:25 -06:00
JJ Kasper
1bc1e8a9b1 [now-routing-utils] Update to not pass used segments in redirect query (#3809)
This updates to not pass segments already used in a redirect's destination query since these values are most likely unwanted and can still be manually added if desired. This change does not affect rewrites and we still pass through all segments in the query
2020-02-19 22:13:36 +00:00
JJ Kasper
274fdeb49a Publish Stable
- @now/next@2.3.15
2020-02-19 15:02:21 -06:00
JJ Kasper
d15c90b42f Publish Canary
- now@17.0.4-canary.3
 - @now/next@2.3.15-canary.0
2020-02-19 14:57:20 -06:00
JJ Kasper
5be2917c47 [now-next] Update 404 handling for pages/404 (#3697)
This adds handling for treating `pages/404.js` as the custom error page in Next.js to allow for more flexible auto static optimization on the 404 page. This makes sure we handle `pages/404.js` being a lambda due to `_app` having `getInitialProps` and also makes sure that visiting `/404` doesn't respond with a 200 status code

~We can add tests for this behavior after the below PR has been released in a canary of Next.js~

x-ref: https://github.com/zeit/next.js/pull/10329
x-ref: https://github.com/zeit/next.js/pull/10593
2020-02-19 20:55:26 +00:00
dependabot[bot]
5f08b24546 Bump codecov from 3.1.0 to 3.6.5 (#3807)
Bumps [codecov](https://github.com/codecov/codecov-node) from 3.1.0 to 3.6.5.
<details>
<summary>Release notes</summary>

*Sourced from [codecov's releases](https://github.com/codecov/codecov-node/releases).*

> ## v3.6.4
> Fix for Cirrus CI
> 
> ## v3.6.3
> AWS Codebuild fixes + package updates
> 
> ## v3.6.2
> command line args sanitised
> 
> ## v3.6.1
> Fix for Semaphore
> 
> ## v3.6.0
> AWS CodeBuild
> Semaphore v2
> 
> ## v3.3.0
> Added pipe `--pipe`, `-l`
</details>
<details>
<summary>Commits</summary>

- See full diff in [compare view](https://github.com/codecov/codecov-node/commits)
</details>
<details>
<summary>Maintainer changes</summary>

This version was pushed to npm by [drazisil](https://www.npmjs.com/~drazisil), a new releaser for codecov since your current version.
</details>
<br />

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=codecov&package-manager=npm_and_yarn&previous-version=3.1.0&new-version=3.6.5)](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 close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor 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>
2020-02-19 20:19:49 +00:00
Steven
cc3026ffcb [tests] Fix builder debug flag (#3805)
When testing, you can set `NOW_BUILDER_DEBUG=1` to print verbose logs from builders.

However, this was causing some tests to fail that depended on a build environment variable.

This PR fixes the assignment so that it preserves the environment variables from the tests by adding `NOW_BUILDER_DEBUG` instead of replacing.
2020-02-18 22:57:17 +00:00
JJ Kasper
8cb6e56679 Publish Stable
- @now/next@2.3.14
2020-02-17 14:28:07 -06:00
JJ Kasper
7da5ff4b1d Publish Canary
- now@17.0.4-canary.2
 - @now/next@2.3.14-canary.1
2020-02-17 11:45:53 -06:00
JJ Kasper
5d1069d464 [now-next] Make sure to 404 and clear cache header for invalid _next requests (#3801)
This handles an edge case where deeply nested dynamic routes were handling `_next` 404s that were cached forever since the immutable header we set isn't cleared after not matching on the filesystem.

I updated our cache-header fixture to check for this edge case also to make sure we don't regress on this
2020-02-17 17:42:51 +00:00
Andy
6e1a72f557 [examples] Use Full URL for Logos (#3800)
Use Full URL for Logos

[PRODUCT-1193]

[PRODUCT-1193]: https://zeit.atlassian.net/browse/PRODUCT-1193
2020-02-17 13:44:35 +00:00
dependabot[bot]
6f97a2cc7a Bump yarn from 1.17.3 to 1.22.0 (#3796)
Bumps [yarn](https://github.com/yarnpkg/yarn) from 1.17.3 to 1.22.0.
<details>
<summary>Changelog</summary>

*Sourced from [yarn's changelog](https://github.com/yarnpkg/yarn/blob/master/CHANGELOG.md).*

> # Changelog
> 
> Please add one entry in this file for each change in Yarn's behavior. Use the same format for all entries, including the third-person verb. Make sure you don't add more than one line of text to keep it clean. Thanks!
> 
> ## Master
> 
> - Passes arguments following `--` when running a workspace script (`yarn workspace pkg run command -- arg`)
> 
>   [#7776](https://github-redirect.dependabot.com/yarnpkg/yarn/pull/7776) - [**Jeff Valore**](https://twitter.com/rally25rs)
> 
> - Prints workspace names with `yarn workspaces` (silence with `-s`)
> 
>   [#7722](https://github-redirect.dependabot.com/yarnpkg/yarn/pull/7722) - [**Orta**](https://twitter.com/orta)
> 
> - Implements `yarn init --install <version>`
> 
>   [#7723](https://github-redirect.dependabot.com/yarnpkg/yarn/pull/7723) - [**Maël Nison**](https://twitter.com/arcanis)
>   
> - Implements `yarn init -2`
> 
>   [#7862](https://github-redirect.dependabot.com/yarnpkg/yarn/pull/7862) - [**Maël Nison**](https://twitter.com/arcanis)
> 
> - Implements `yarn set version <version>` as an alias for `policies set-version`
> 
>   [#7862](https://github-redirect.dependabot.com/yarnpkg/yarn/pull/7862) - [**Maël Nison**](https://twitter.com/arcanis)
> 
> - Fixes an issue where the archive paths were incorrectly sanitized
> 
>   [#7831](https://github-redirect.dependabot.com/yarnpkg/yarn/pull/7831) - [**Maël Nison**](https://twitter.com/arcanis)
> 
> - Allows some dots in binary names again
> 
>   [#7811](https://github-redirect.dependabot.com/yarnpkg/yarn/pull/7811) - [**Valery Bugakov**](https://github.com/valerybugakov)
>   
> - Better error handling on `yarn set version`
> 
>   [#7848](https://github-redirect.dependabot.com/yarnpkg/yarn/pull/7848) - [**Nick Olinger**](https://github.com/olingern)
> 
> ## 1.19.2
> 
> - Folders like `.cache` won't be pruned from the `node_modules` after each install.
> 
>   [#7699](https://github-redirect.dependabot.com/yarnpkg/yarn/pull/7699) - [**Maël Nison**](https://twitter.com/arcanis)
> 
> - Correctly installs workspace child dependencies when workspace child not symlinked to root.
> 
>   [#7289](https://github-redirect.dependabot.com/yarnpkg/yarn/pull/7289) - [**Daniel Tschinder**](https://github.com/danez)
> 
> - Makes running scripts with Plug'n Play possible on node 13.
> 
></tr></table> ... (truncated)
</details>
<details>
<summary>Commits</summary>

- [`eee67d0`](eee67d02ed) v1.22.0
- [`6971622`](69716228e9) Update CHANGELOG.md
- [`0e7133c`](0e7133ca28) Fixes arbitrary file write on fetch ([#7831](https://github-redirect.dependabot.com/yarnpkg/yarn/issues/7831))
- [`fa613f3`](fa613f3a71) handle failed policy set-version ([#7848](https://github-redirect.dependabot.com/yarnpkg/yarn/issues/7848))
- [`f381c5f`](f381c5f0ce) Implements set version ([#7862](https://github-redirect.dependabot.com/yarnpkg/yarn/issues/7862))
- [`d2c3a48`](d2c3a485e0) Configure bug report issue template ([#7844](https://github-redirect.dependabot.com/yarnpkg/yarn/issues/7844))
- [`af4c205`](af4c2056a3) Allow dots in bin names ([#7811](https://github-redirect.dependabot.com/yarnpkg/yarn/issues/7811))
- [`1b334e6`](1b334e687b) fix(workspaces): Pass args following "--" when running workspace scripts ([#7786](https://github-redirect.dependabot.com/yarnpkg/yarn/issues/7786))
- [`2c8e97e`](2c8e97eaf3) Fix suggestions for CLI ([#7808](https://github-redirect.dependabot.com/yarnpkg/yarn/issues/7808))
- [`039bafd`](039bafd74b) Fixes bin overwrites ([#7755](https://github-redirect.dependabot.com/yarnpkg/yarn/issues/7755))
- Additional commits viewable in [compare view](https://github.com/yarnpkg/yarn/compare/v1.17.3...v1.22.0)
</details>
<details>
<summary>Maintainer changes</summary>

This version was pushed to npm by [daniel15](https://www.npmjs.com/~daniel15), a new releaser for yarn since your current version.
</details>
<br />

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=yarn&package-manager=npm_and_yarn&previous-version=1.17.3&new-version=1.22.0)](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 close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor 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>
2020-02-15 01:56:34 +00:00
Steven
6e402bffc3 Publish Canary
- now@17.0.4-canary.1
 - @now/go@1.0.4-canary.2
 - @now/next@2.3.14-canary.0
2020-02-14 15:05:00 -05:00
Steven
e41d0f8e68 [now-go] Fix /api/go.sum detection (#3795)
Follow up to #3793 

The `go.mod` and `go.sum` file must both be moved.
2020-02-14 19:58:44 +00:00
Steven
1a046744f2 [now-go] Add support for root go.mod in api directory (#3793)
Some users wish to put all Go related code in the `/api` directory and the root is reserved for a frontend framework.

This PR adds support for `/api/go.mod` which will act the same as `/go.mod` by moving the file to the root.
2020-02-14 17:32:20 +00:00
Andy
a9bf011f2c [api] Add rewrite to frameworks API (#3794)
* [api] Add rewrite to frameworks API

* Adjust now.json

* Change output file
2020-02-14 00:30:58 +01:00
JJ Kasper
60c76b3290 [now-next] Add support for using the fallback file for dynamic SSG pages (#3752)
This adds support for using the fallback file for dynamic (lazy) SSG pages to allow showing a loading state while the cache is populated with data. We can add tests for this behavior once the below referenced PR is shipped on canary

x-ref: https://github.com/zeit/next.js/pull/10424

~Update: added tests now that fallback support has landed in Next.js although it appears `now-proxy` might need to be updated to revalidate the fallback correctly since the initial fallback is always being shown for lazy SSG pages with these changes~

Looks like this is working properly in `now-proxy` and we were overriding the prerender output with the static page output, thanks @juancampa for helping investigate! 🙏
2020-02-13 01:06:55 +00:00
Steven
c7ff1e7044 [now-cli] Update ZEIT Logo (#3790)
This PR updates the logo to match https://zeit.co/home
2020-02-12 10:17:54 -05:00
Andy Bitz
e2deaef54a Publish Canary
- @now/frameworks@0.0.10-canary.0
2020-02-12 14:46:11 +01:00
Andy
b2c7386c83 [frameworks] Adjust Next.js build command placeholder (#3788) 2020-02-12 14:34:09 +01:00
Steven
3dce84c3cf Publish Canary
- @now/go@1.0.4-canary.1
2020-02-11 16:46:21 -05:00
Steven
443b1ac158 [now-go] Fix path segments with square brackets (#3782)
Previously, `@now/go` would fail when using [Path Segments](https://zeit.co/docs/v2/serverless-functions/introduction#path-segments) since `go build` [doesn't support](https://stackoverflow.com/q/60140859/266535) files that begin with square bracket.

This PR changes the build logic so that the entrypoint file is renamed if if begins with a square bracket.

The output file is not renamed because v3 builders can't rename outputs which works great for this feature.
2020-02-11 21:41:57 +00:00
Andy Bitz
be3dca3d94 Publish Canary
- @now/build-utils@2.0.0-canary.1
 - now@17.0.4-canary.0
 - @now/go@1.0.4-canary.0
 - @now/python@1.1.4-canary.0
2020-02-11 16:50:13 +01:00
Andy Bitz
cb135e4329 [now-build-utils] Bump version 2020-02-11 16:50:01 +01:00
Andy
55c73b68bb [now-build-utils][now-cli] Refactor builds and routes detection (#3784)
* [now-build-utils] Refactor detect builds

* Refactor routes detection

* [now-cli] Adjust `now dev`

* Adjust tests

* Adjust integrations test

* Remove unused file

* Replace reducw with for-loop

* Add cache for absolute paths

* Filter while looping

* Add fallback for entrypoint with buildCommand
2020-02-11 16:47:39 +01:00
Steven
eb793ba139 [tests] Add windows to CI unit tests (#3727)
This adds initial support for Windows in GitHub Actions CI tests.

There is still work to be done to prevent certain tests from hanging in CI so those are skipped for now.
2020-02-11 01:16:14 +00:00
Steven
00908e8ebe [now-python] Fix path segments with square brackets (#3781)
Previously, python would fail when using [Path Segments](https://zeit.co/docs/v2/serverless-functions/introduction#path-segments) as seen in #3729.

This PR changes the import logic so that the entrypoint file is [imported as a relative file path](https://stackoverflow.com/a/67692/266535) instead of by module name.
2020-02-10 21:01:31 +00:00
Leo Lamprecht
5ce7ff86a4 Minor tweaks for CONTRIBUTING.md file (#3778)
* Minor tweaks for CONTRIBUTING.md file

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md
2020-02-10 18:34:00 +01:00
Steven
4b34ee2993 [examples] Remove outdated jekyll build section from README (#3780)
We used to require a `package.json` but we have since introduced [Advanced Project Settings](https://zeit.co/blog/advanced-project-settings) which will ask the user for a build command during the first deploy.
2020-02-10 15:31:52 +00:00
Andi
646bd288ba [now-go] Update go to version 1.13.7 (#3750)
Updates go to the latest version 1.13.7 (https://golang.org/doc/devel/release.html#go1.13). This allows users to use the new error functionality that was introduced in go 1.13 (https://blog.golang.org/go1.13-errors) and also includes a couple of security fixes.

Looking at the last PR that updated the version (https://github.com/zeit/now-builders/pull/247/files) this should be as simple as updating the version number.

P.S. I tried to follow the contribution guidelines, but `yarn lint` fails with `exit code 1`/`295 problems (33 errors, 262 warnings)` and `yarn test` returns `error Command "test" not found` on master. But `yarn test-unit` shows no errors.
2020-02-08 01:47:47 +00:00
Steven
8aba6f1ff8 Publish Stable
- now@17.0.3
2020-02-07 20:01:17 -05:00
Steven
35e5e328aa Publish Canary
- now@17.0.3-canary.1
2020-02-07 19:18:47 -05:00
Andy
bdd4953d62 [now-cli] Consider root directory for now.json (#3764)
* [now-cli] Consider root directory for now.json

* Adjust message check in test

* Fallback if config does not exist as well

* Assign localConfig later and add debug

* Prefere now.json from root directory

* Comment

* Adjust test and add warning
2020-02-08 01:04:34 +01:00
Steven
e938b298e2 [now-cli] Fix port assignment for now dev (#3769)
This fixes a regression from #3749 where the PORT env var was removed. This is necessary so frameworks like create-react-app and gatsby can proxy to `now dev`.

Fixes #3761
2020-02-07 19:03:56 -05:00
Andy
b67f324e10 [api] Add hasDetectors property (#3770) 2020-02-08 00:29:11 +01:00
Andy Bitz
1dfa286af5 Publish Canary
- now@17.0.3-canary.0
2020-02-07 21:14:50 +01:00
Andy
d4391bd4cc [now-cli] Fix chalk template for legacy (#3768)
* [now-cli] Fix chalk template for legacy

* Update packages/now-cli/src/commands/deploy/legacy.ts

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

Co-authored-by: Nathan Rajlich <n@n8.io>
2020-02-07 21:14:11 +01:00
luc
53eb71f26d Publish Stable
- now@17.0.2
 - @now/routing-utils@1.5.3
2020-02-06 19:18:35 +01:00
luc
92ffd654b5 Publish Canary
- now@17.0.2-canary.1
2020-02-06 18:53:36 +01:00
Luc
36b83f1606 [now-cli] Use npx or yarn to execute dev command (#3760)
1. Use bundled yarn to run `yarn bin`
2. Skip `yarn bin` if `npx` is used
2020-02-06 17:47:39 +00:00
Steven
7a79f620c0 [tests] Fix changelog script (#3751)
Previously, the changelog script was looking for the last "Publish Stable" commit, but it should really be looking for the last Stable release of Now CLI.

This PR updates the changelog script so that it fetches the latest GH Release (which should be Now CLI) and then compares that to the HEAD.
2020-02-06 14:35:15 +00:00
Steven
f3b286ecf3 Publish Canary
- now@17.0.2-canary.0
 - @now/routing-utils@1.5.3-canary.0
2020-02-06 09:12:19 -05:00
Steven
adf31c3fcc [now-routing-utils] Change behavior of trailingSlash: true redirects (#3745)
This PR changes the behavior of `trailingSlash: true` after we received feedback that files should not be redirected with a trailing slash. This matches the behavior of `serve` and `serve-handler`.

### Examples 
* `/index.html` => serve
* `/assets/style.css` => serve
* `/assets` => redirect to `/assets/`
* `/assets/style` => redirect to `/assets/style/` 

### Additional

In order to avoid duplicate content, this PR also adds redirects to files without a trailing slash.

* `/about.html/` => redirect to `/about.html`
* `/assets/style.css/` => redirect to `/assets/style.css`


Fixes #3731
2020-02-06 09:07:46 -05:00
luc
deacdfc47c Publish Stable
- now@17.0.1
2020-02-06 01:07:15 +01:00
Luc
ac9badbe9e [now-cli] Always output deployment url in stdout (#3753) 2020-02-06 00:58:24 +01:00
luc
62beb0f78d Publish Stable
- @now/frameworks@0.0.9
 - @now/build-utils@1.3.9
 - @now/cgi@1.0.3
 - now@17.0.0
 - now-client@7.0.0
 - @now/go@1.0.3
 - @now/next@2.3.13
 - @now/node@1.4.1
 - @now/python@1.1.3
 - @now/routing-utils@1.5.2
 - @now/ruby@1.0.2
 - @now/static-build@0.14.12
2020-02-05 23:03:23 +01:00
luc
5f9777f4af Publish Canary
- now@17.0.0-canary.35
2020-02-05 22:38:39 +01:00
Luc
e00db4437a [now-cli] Use npx when available to run dev command (#3749)
* use npx when available

* add --no-install

* replace $PORT and %PORT% in devCommand

* remove PORT from env variables

* replace more than one $PORT or %PORT%

* fix regex
2020-02-05 22:37:06 +01:00
Steven
b0e4f2590d Publish Stable
- @now/cgi@1.0.2
 - @now/go@1.0.2
 - @now/python@1.1.2
2020-02-05 15:09:14 -05:00
Leo Lamprecht
f0d58eac8c Remove now dev suggestion (#3748) 2020-02-05 20:50:21 +01:00
Steven
dae830d2b6 [examples] Fix hugo theme (#3746)
The Hugo theme was lost when transferring from `zeit/now-examples` to `zeit/now`.

This PR fixes the `.gitignore` file to include the `dist` directory and override our root `.gitignore`.
2020-02-05 19:30:08 +00:00
dependabot[bot]
e3071e4e29 Bump mixin-deep from 1.3.1 to 1.3.2 in /packages/now-cgi (#3736)
Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/jonschlinkert/mixin-deep/releases)
- [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-02-05 09:52:08 -05:00
Steven
073d7ece23 Publish Canary
- @now/build-utils@1.3.9-canary.0
 - now@17.0.0-canary.34
2020-02-05 08:29:10 -05:00
Andy Bitz
071258ba33 Publish Stable
- @now/build-utils@1.3.8
2020-02-05 03:27:31 +01:00
Andy
c0e00dc69a [now-build-utils] Fix build script check (#3743) 2020-02-05 03:27:17 +01:00
luc
6e5c136337 Publish Canary
- now@17.0.0-canary.33
2020-02-05 02:26:53 +01:00
Steven
60428cd4cf [now-cli] Bump @zeit/fun to 0.11.2 (#3741)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-02-05 02:20:54 +01:00
Luc
c6b9d80eec Fix now cli breaking tests (#3742) 2020-02-05 02:17:43 +01:00
Luc
953bdc10e5 [now-cli] Fix prompts and fetch team on windows (#3740)
* fix fetch on windows

* update inquirer

Co-authored-by: Leo Lamprecht <mindrun@icloud.com>
2020-02-05 01:58:49 +01:00
Andy Bitz
becdbd2136 Publish Stable
- @now/build-utils@1.3.7
2020-02-05 01:52:02 +01:00
Luc
da9bb31259 [now-cli] Handle no framework detected case (#3738)
* handle no framework detected case

* remove related test

* simplify

Co-authored-by: Leo Lamprecht <mindrun@icloud.com>
2020-02-05 01:49:13 +01:00
Luc
8cbf036921 [now-cli] Do not prompt for root directory when project is linked (#3737)
* do not prompt for root directory when linked

* run tests

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Leo Lamprecht <mindrun@icloud.com>
2020-02-05 01:48:52 +01:00
Andy
8f66e4a308 [now-build-utils] Handle empty buildCommand and outputDirectory (#3739)
* [now-build-utils] Handle empty buildCommand and outputDirectory

* Update comment
2020-02-05 01:47:54 +01:00
Luc
9627b612f2 [now-cli] Do not show spinners with --debug (#3732)
* create output.wait and use it

* fix printing "ended" multiple times

* update more `wait` -> `output.spinner`

* timeout -> delay
2020-02-04 23:23:37 +01:00
Luc
8a9ded6d61 [now-cli] Slugify suggested project name (#3733)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-02-04 22:29:07 +01:00
Steven
d89c772bd5 [now-build-utils] Exclude _test.go files (#3735)
Typically, Go tests are side-by-side with their source files in a `_test.go`.

The Go documentation says the following:

> To write a new test suite, create a file whose name ends _test.go that contains the TestXxx functions as described here. Put the file in the same package as the one being tested. The file will be excluded from regular package builds but will be included when the “go test” command is run. [View Docs](https://golang.org/pkg/testing/)

This PR excludes the test files from being turned into Serverless Functions.
2020-02-04 21:11:26 +00:00
Steven
32137586b9 Publish Stable
- @now/static-build@0.14.11
2020-02-04 15:10:33 -05:00
Andy Bitz
9a3e435175 Publish Stable
- @now/frameworks@0.0.8
2020-02-04 21:06:30 +01:00
Andy
01d5a10ebd [@now/frameworks] Add Other (#3734)
* [@now/frameworks] Add Other

* Add logo

* Fix typo

* Add description
2020-02-04 21:03:56 +01:00
Steven
040658fbfa Publish Canary
- now@17.0.0-canary.32
 - @now/go@1.0.2-canary.0
2020-02-03 19:43:39 -05:00
Steven
51c00286a4 [now-go] Fix now dev on Windows (#3730) 2020-02-03 19:40:41 -05:00
Steven
c3e274fc2f [now-cli] Fix now dev when api file is added or removed (#3708)
This PR is a follow up to #3703 which fixes file output renaming on initial boot but not when files are added or removed while `now dev` is running. This PR fixes that behavior.
2020-02-03 16:30:37 -05:00
Steven
fe633c528a Publish Canary
- now@17.0.0-canary.31
 - @now/static-build@0.14.11-canary.2
2020-02-03 13:28:43 -05:00
Steven
5e306d49f8 [now-static-build] Fix error when HUGO_VERSION not found (#3724)
This PR improves the error message when the user specifies a version but that version does not exist.

- HUGO_VERSION
- ZOLA_VERSION
- GUTENBERG_VERSION

Typically this means there is no tag for that particular version. It could also mean there is a tag but no binary is attached to the tag/release.

![image](https://user-images.githubusercontent.com/229881/73673953-900f4e80-467d-11ea-90ed-3317cf83c74e.png)

![image](https://user-images.githubusercontent.com/229881/73673985-9d2c3d80-467d-11ea-8ee4-cc6ecabaac20.png)

Related to [zeit/docs#1624](https://github.com/zeit/docs/pull/1624)
2020-02-03 13:21:11 -05:00
Steven
274259b7dd [tests] Update to use github action cancel workflow (#3720)
Removes script and instead uses proper GitHub Action.

This reduces the time to run from 10 seconds to 5 seconds (due to ncc).

https://github.com/styfle/cancel-workflow-action
2020-02-02 02:47:33 +00:00
Steven
230e96c687 [tests] Change warn to error on unsed vars (#3715)
Follow up to #3714 that turns the warning into an error.

![image](https://user-images.githubusercontent.com/229881/73582375-ea7c9500-445a-11ea-9d20-2a17073a068a.png)
2020-02-01 00:41:40 +00:00
Nathan Rajlich
8c0c6e546d [now-cli] Remove unused fs import (#3714)
Fixes GH pull request auto-lint:

> #### Check warning on line 2 in `packages/now-cli/src/util/input/input-root-directory.ts`:
>
> ## GitHub Actions / Unit Tests (ubuntu-latest, 12)
>
> `packages/now-cli/src/util/input/input-root-directory.ts#L2`
>
> ```
> 'fs' is defined but never used
> ```
2020-01-31 18:57:09 -05:00
Steven
5e8541b936 [tests] Don't run tests in forks (#3713)
There is no need to run the tests in a fork because when the forked repo submits a PR to this repo, the tests will run.
2020-01-31 18:49:56 -05:00
Simon Hänisch
300558f24e [now-static-build] Add caching headers for Stencil (#3493)
/cc @adamdbradley @leo
2020-01-31 23:48:46 +00:00
Steven
829f7d8aeb Publish Canary
- @now/build-utils@1.3.7-canary.2
 - @now/python@1.1.2-canary.0
2020-01-31 16:48:34 -05:00
Steven
340f7db68a [now-python] Add excludeFiles config option (#3712)
This PR adds a `config.excludeFiles` pattern for `@now/python` that defaults to `node_modules/**` for users who are using a Node.js SSG frontend with Python APIs backend.

If the user chooses, they can override to any glob pattern such as `{.cache,node_modules}/**`.

Related to #2830
Related to #3416
2020-01-31 21:47:14 +00:00
luc
2fe987b5da Publish Canary
- now@17.0.0-canary.30
2020-01-31 22:13:11 +01:00
Andy
5b3aa48cd6 [now-cli] Adjust error message for Root Directory (#3710)
* [now-cli] Adjust error message for Root Directory

* Update packages/now-cli/src/commands/deploy/latest.js

Co-Authored-By: Luc <luc.leray@gmail.com>

Co-authored-by: Luc <luc.leray@gmail.com>
2020-01-31 21:36:06 +01:00
Steven
080d96bfa1 [api] Silence github comments (#3709)
Most PRs don't touch the examples api so let's silence the comments and cancel in-progress builds if we push again.

This should reduce the noise in this repository.

Documentation: https://zeit.co/docs/configuration#introduction/configuration-reference
2020-01-31 16:57:18 +00:00
Luc
7d9bf682b4 [now-cli] Add [copied to clipboard] (#3707) 2020-01-31 17:16:29 +01:00
Steven
a913a4f59f [tests] Cancel previous CI runs (#3706)
This PR adds an action to cancel previous runs for the current branch using the [github workflow api](https://developer.github.com/v3/actions/workflow_runs/).

This action is ignored for the `master` branch.
2020-01-31 09:30:09 -05:00
Andy Bitz
2042c96d98 Publish Canary
- now@17.0.0-canary.29
2020-01-31 13:30:12 +01:00
Steven
4ca0ff8426 [now-cli] Fix now dev file output renaming (#3703)
There was a bug where python src files were being renamed when it really should be the output files only.

This is a tricky bug because production deployments build all files first and then perform routing. So we simply rename lambda outputs with prod deployments. But `now dev` matches a request URL to a build before performing the build lazily so we have to rename source files.

The solution is to add both the original file name and the renamed file name in the `files` map so that it matches correctly but `zeit/fun` will still copy the original source files in the output correctly.

Routing will match on the extensionless file, the builder will use the file with extension (it doesn't know about file renaming), then the build results in a lambda output which is renamed to extensionless.

I added a test for `@now/python` and updated the `@now/bash` test.

Fixes #3638
2020-01-31 12:20:46 +00:00
Andy
554cc42d83 [now-cli] Add support for the rootDirectory property (#3686)
* [now-cli] Add support for the `rootDirectory` property

* Only check if rootDirectory exists

* Add test

* Support now dev

* Use defaults

* Comment

* Normalize the path input

* Adjust test

* Remove .only

* Adjust more tests

* Adjust test

* Fix test

* Remove unused import

* Update packages/now-cli/src/util/validate-paths.ts

Co-Authored-By: Luc <luc.leray@gmail.com>

* Run check on normalized path

* Add more checks

* Change error message

* Use basename as prefix

* Use correct path when linking

* Update packages/now-cli/src/util/input/input-root-directory.ts

Co-Authored-By: Luc <luc.leray@gmail.com>

Co-authored-by: Luc <luc.leray@gmail.com>
2020-01-31 12:56:09 +01:00
Steven
3f9e30d031 [docs] Update readme with CI status (#3704)
This repo no longer uses Circle CI since #3660 so this PR updates the badge in the readme.
2020-01-30 17:04:33 -05:00
luc
5a9ca8644c Publish Canary
- now@17.0.0-canary.28
2020-01-30 20:07:27 +01:00
Luc
3f362d4b50 [now-cli] Add scope indication to now secrets (#3699)
It looks like this:
![image](https://user-images.githubusercontent.com/6616955/73408935-03bbff00-42fe-11ea-9f1c-70860437c6bc.png)
2020-01-30 19:01:44 +00:00
Steven
7360886c9a [tests] Run unit tests on ubuntu and macos (#3702)
* [tests] Run unit tests on ubuntu and macos

* Add back Node 10 for Now CLI Tests

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-01-30 18:21:16 +01:00
Luc
5b8a1b47b0 [now-cli] Adjust name error messages (#3698)
* adjust `name` error messages

* return after outputting error
2020-01-30 18:10:44 +01:00
luc
0506b262d2 Publish Canary
- now@17.0.0-canary.27
2020-01-30 17:38:31 +01:00
Luc
17c4569f41 [now-cli] Send framework to api-deployments (#3701)
* send `framework` to api-deployments

* add error message

* Update packages/now-cli/src/util/input/edit-project-settings.ts

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

Co-authored-by: Steven <steven@ceriously.com>
2020-01-30 17:37:48 +01:00
178 changed files with 3257 additions and 1866 deletions

1
.github/CODEOWNERS vendored
View File

@@ -2,6 +2,7 @@
# https://help.github.com/en/articles/about-code-owners
* @tootallnate @leo
/.github/workflows @AndyBitz @styfle
/packages/frameworks @AndyBitz
/packages/now-cli/src/commands/dev/ @tootallnate @leo @styfle @AndyBitz
/packages/now-cli/src/util/dev/ @tootallnate @leo @styfle @AndyBitz

View File

@@ -97,13 +97,9 @@ Sometimes you want to test changes to a Builder against an existing project, may
## Add a New Framework
You can add support for a new framework by creating a Pull Request for this repository by following the steps below.
You can add support for a new Framework by creating a Pull Request for this repository and following the steps below:
1. Add the framework to the `@now/frameworks` package.
The file is located in `packages/frameworks/frameworks.json`. You can copy the structure of an existing one and adjust the required fields. Note that the `settings` property either contains a `value` or a `placeholder`. The `value` property is used when something is not configurable, the `placeholder` is used when something is configurable and can be changed with configuration. An example would be the output directory for Hugo, it's `public` by default but can be changed through its config file, so we use `placeholder` with an explanation of what can be used.
2. Add an example to the `examples/` directory.
The name of the directory should equal the slug of the framework used in `@now/frameworks`.
The `.github/EXAMPLE_README_TEMPLATE.md` file can be used to create a README for the example.
3. Update the `@now/static-build` package
The files `packages/now-static-build/src/frameworks.ts` has to be extended. You can add default routes that will always be applied to projects that use this framework or specify some paths that will be cached to speed up the build process.
4. After your PR has been merged and released other users can use `now init` to get the example and deploy it to ZEIT Now.
1. Add the Framework to the `@now/frameworks` package: The file is located in `packages/frameworks/frameworks.json`. You can copy the structure of an existing one and adjust the required fields. Note that the `settings` property either contains a `value` or a `placeholder`. The `value` property is used when something is not configurable, the `placeholder` is used when something is configurable and can be changed with configuration. An example would be the Output Directory for Hugo, it's `public` by default but can be changed through its config file, so we use `placeholder` with an explanation of what can be used.
2. Add an example to the `examples/` directory: The name of the directory should equal the slug of the framework used in `@now/frameworks`. The `.github/EXAMPLE_README_TEMPLATE.md` file can be used to create a `README.md` file for the example.
3. Update the `@now/static-build` package: The file `packages/now-static-build/src/frameworks.ts` has to be extended. You can add default routes that will always be applied to projects that use this Framework or specify some paths that will be cached to speed up the build process.
4. After your Pull Request has been merged and released, other users can select the example on the ZEIT Now dashboard and deploy it.

18
.github/workflows/cancel.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: Cancel
on:
push:
branches:
- '*'
- '!master'
jobs:
cancel:
name: 'Cancel Previous Runs'
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- uses: styfle/cancel-workflow-action@0.2.0
with:
workflow_id: 435869
access_token: ${{ secrets.GITHUB_WORKFLOW_TOKEN }}

View File

@@ -12,18 +12,24 @@ jobs:
test-unit:
name: Unit Tests
timeout-minutes: 15
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [10, 12]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- run: git fetch origin master --depth=10
- run: git fetch origin ${{ github.ref }} --depth=10
- uses: actions/setup-node@v1
with:
node-version: 10
- run: yarn install && yarn run build
- run: yarn install
- run: yarn run build
- run: yarn run test-lint
- run: yarn run test-unit --clean false
- uses: actions/upload-artifact@v1
- name: Upload Artifact
if: matrix.os == 'ubuntu-latest' && matrix.node == 12 # only run once
uses: actions/upload-artifact@v1
with:
name: test-unit-output
path: packages/now-cli/.nyc_output
@@ -36,7 +42,8 @@ jobs:
- uses: actions/checkout@v2
- run: git fetch origin master --depth=10
- run: git fetch origin ${{ github.ref }} --depth=10
- run: yarn install && yarn run build
- run: yarn install
- run: yarn run build
- run: yarn test-integration-once --clean false
test-now-cli:
@@ -45,15 +52,18 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest]
node: [10, 12]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- run: git fetch origin master --depth=10
- run: git fetch origin ${{ github.ref }} --depth=10
- run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
- run: yarn install && yarn run build
- name: Install Hugo
if: matrix.os == 'macos-latest'
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
- run: yarn install
- run: yarn run build
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
@@ -72,8 +82,11 @@ jobs:
- uses: actions/checkout@v2
- run: git fetch origin master --depth=10
- run: git fetch origin ${{ github.ref }} --depth=10
- run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
- run: yarn install && yarn run build
- name: Install Hugo
if: matrix.os == 'macos-latest'
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
- run: yarn install
- run: yarn run build
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}

View File

@@ -1,6 +1,6 @@
![now](https://assets.zeit.co/image/upload/v1542240976/repositories/now-cli/now-cli-repo-banner-v3.png)
![now](https://assets.zeit.co/image/upload/v1581518533/repositories/now-cli/v4.png)
[![Build Status](https://badgen.net/circleci/github/zeit/now/master)](https://circleci.com/gh/zeit/workflows/now/tree/master)
[![CI Status](https://badgen.net/github/checks/zeit/now?label=CI)](https://github.com/zeit/now/actions?workflow=CI)
[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/zeit)
## Usage
@@ -16,7 +16,6 @@ To quickly start a new project, run the following commands:
```
now init # Pick an example project to clone
cd <PROJECT> # Change directory to the newly created project
now dev # Run locally during development
now # Deploy to the cloud
```

View File

@@ -2,17 +2,19 @@ import { NowRequest, NowResponse } from '@now/node';
import { withApiHandler } from './_lib/util/with-api-handler';
import frameworkList, { Framework } from '../packages/frameworks';
const frameworks: Framework[] = (frameworkList as Framework[]).map(
framework => {
delete framework.detectors;
const frameworks = (frameworkList as Framework[]).map(frameworkItem => {
const framework = {
...frameworkItem,
hasDetectors: Boolean(frameworkItem.detectors),
detectors: undefined,
};
if (framework.logo) {
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
}
return framework;
if (framework.logo) {
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
}
);
return framework;
});
export default withApiHandler(async function(
req: NowRequest,

View File

@@ -1,4 +1,4 @@
![Angular Logo](../../packages/frameworks/logos/angular.svg)
![Angular Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/angular.svg)
# Angular Example

View File

@@ -1,4 +1,4 @@
![React Logo](../../packages/frameworks/logos/react.svg)
![React Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/react.svg)
# React Example

View File

@@ -1,4 +1,4 @@
![Docusaurus Logo](../../packages/frameworks/logos/docusaurus.svg)
![Docusaurus Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/docusaurus.svg)
# Docusaurus Example

View File

@@ -1,4 +1,4 @@
![Eleventy Logo](../../packages/frameworks/logos/eleventy.svg)
![Eleventy Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/eleventy.svg)
# Eleventy Example

View File

@@ -1,4 +1,4 @@
![Ember Logo](../../packages/frameworks/logos/ember.svg)
![Ember Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/ember.svg)
# Ember Example

View File

@@ -1,4 +1,4 @@
![Gatsby Logo](../../packages/frameworks/logos/gatsby.svg)
![Gatsby Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/gatsby.svg)
# Gatsby Example

View File

@@ -1,4 +1,4 @@
![Gridsome Logo](../../packages/frameworks/logos/gridsome.svg)
![Gridsome Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/gridsome.svg)
# Gridsome Example

View File

@@ -1,4 +1,4 @@
![Hexo Logo](../../packages/frameworks/logos/hexo.svg)
![Hexo Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/hexo.svg)
# Hexo Example

View File

@@ -28,3 +28,5 @@ npm-debug.log
/junit.xml
partials/structure/stylesheet.html
!dist

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
!function(n){function t(e){if(r[e])return r[e].exports;var o=r[e]={i:e,l:!1,exports:{}};return n[e].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var r={};t.m=n,t.c=r,t.i=function(n){return n},t.d=function(n,r,e){t.o(n,r)||Object.defineProperty(n,r,{configurable:!1,enumerable:!0,get:e})},t.n=function(n){var r=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(r,"a",r),r},t.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},t.p="",t(t.s=1)}([function(n,t){},function(n,t,r){"use strict";var e=r(0);!function(n){n&&n.__esModule}(e)}]);

View File

@@ -25,18 +25,3 @@ You can deploy your new Jekyll project with a single command from your terminal
```shell
$ now
```
### Build Command
The default build command is `jekyll build`.
If you wish to change the build command, add a `package.json` file with the following:
```json
{
"private": true,
"scripts": {
"build": "jekyll build"
}
}
```

View File

@@ -1,4 +1,4 @@
![Next.js Logo](../../packages/frameworks/logos/next.svg)
![Next.js Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/next.svg)
# Next.js Example

View File

@@ -1,4 +1,4 @@
![Polymer Logo](../../packages/frameworks/logos/polymer.svg)
![Polymer Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/polymer.svg)
# Polymer Example

View File

@@ -1,4 +1,4 @@
![Preact Logo](../../packages/frameworks/logos/preact.svg)
![Preact Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/preact.svg)
# Preact Example

View File

@@ -1,4 +1,4 @@
![Saber Logo](../../packages/frameworks/logos/saber.svg)
![Saber Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/saber.svg)
# Saber Example

View File

@@ -1,4 +1,4 @@
![UmiJS Logo](../../packages/frameworks/logos/umi.svg)
![UmiJS Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/umi.svg)
# UmiJS Example

View File

@@ -1,4 +1,4 @@
![Vue.js Logo](../../packages/frameworks/logos/vue.svg)
![Vue.js Logo](https://github.com/zeit/now/blob/master/packages/frameworks/logos/vue.svg)
# Vue.js Example

View File

@@ -1,15 +1,18 @@
{
"redirects": [
"rewrites": [
{
"source": "/",
"destination": "https://zeit.co/new",
"statusCode": 307
"destination": "/api/frameworks"
}
],
"env": {
"GITHUB_ACCESS_TOKEN": "@now-api-examples-github-token",
"SENTRY_DSN": "@sentry-product-dsn"
},
"github": {
"silent": true,
"autoJobCancelation": true
},
"headers": [
{
"source": "/api/frameworks",

View File

@@ -91,6 +91,7 @@
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-unused-vars": 2,
"@typescript-eslint/no-use-before-define": 0
},
"overrides": [

View File

@@ -17,7 +17,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`next build` or `build` from `package.json`"
"placeholder": "`build` from `package.json` or `next build`"
},
"devCommand": {
"value": "next dev --port $PORT"
@@ -808,5 +808,22 @@
"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.",
"description": "A Foundation app, created with the Foundation CLI."
},
{
"name": "Other",
"slug": null,
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/other.svg",
"description": "No framework or a unoptimized framework.",
"settings": {
"buildCommand": {
"placeholder": "`build` or `now-build` from `package.json` if it exists"
},
"devCommand": {
"placeholder": "None"
},
"outputDirectory": {
"placeholder": "`public` if it exists, or `.`"
}
}
}
]

View File

@@ -13,11 +13,11 @@ export type Setting = SettingValue | SettingPlaceholder;
export interface Framework {
name: string;
slug: string;
slug: string | null;
logo: string;
demo: string;
tagline: string;
website: string;
demo?: string;
tagline?: string;
website?: string;
description: string;
detectors?: {
every?: FrameworkDetectionItem[];

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 4L16 15H4L10 4Z" stroke="#666666" stroke-dasharray="2 2"/>
</svg>

After

Width:  |  Height:  |  Size: 215 B

View File

@@ -1,6 +1,6 @@
{
"name": "@now/frameworks",
"version": "0.0.7",
"version": "0.0.10",
"main": "frameworks.json",
"license": "UNLICENSED"
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,432 +0,0 @@
import { parse as parsePath, extname } from 'path';
import { Route, Source } from '@now/routing-utils';
import { Builder } from './types';
import { getIgnoreApiFilter, sortFiles } from './detect-builders';
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,
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, i): string => {
const name = getSegmentName(segment);
const isLast = i === parts.length - 1;
if (name !== null) {
// We can't use `URLSearchParams` because `$` would get escaped
query.push(`${name}=$${counter++}`);
isDynamic = true;
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),
featHandleMiss && cleanUrls
? ''
: prefix + escapeName(fileName) + escapeName(ext),
].filter(Boolean);
// Either filename with extension, filename without extension
// 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, 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('/')}$`;
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
// 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 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[],
featHandleMiss: boolean,
cleanUrls: boolean
): Promise<ApiRoutesResult> {
if (!files || files.length === 0) {
return {
defaultRoutes: null,
dynamicRoutes: 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: 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 && b.config.functions)
) {
continue;
}
const conflictingSegment = getConflictingSegment(file);
if (conflictingSegment) {
return {
defaultRoutes: null,
dynamicRoutes: 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,
dynamicRoutes: 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}`,
},
};
}
const out = createRouteFromPath(file, featHandleMiss, cleanUrls);
if (out.isDynamic) {
dynamicRoutes.push(out.route);
}
defaultRoutes.push(out.route);
}
return { defaultRoutes, dynamicRoutes, error: null };
}
function getPublicBuilder(builders: Builder[]): Builder | null {
const builder = builders.find(
builder =>
builder.use === '@now/static' &&
/^.*\/\*\*\/\*$/.test(builder.src) &&
builder.config &&
builder.config.zeroConfig === true
);
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 function detectApiDirectory(builders: Builder[]): string | null {
// TODO: We eventually want to save the api directory to
// builder.config.apiDirectory so it is only detected once
const found = builders.some(
b => b.config && b.config.zeroConfig && b.src.startsWith('api/')
);
return found ? 'api' : null;
}
export function detectApiExtensions(builders: Builder[]): Set<string> {
return new Set<string>(
builders
.filter(
b =>
b.config && b.config.zeroConfig && b.src && b.src.startsWith('api/')
)
.map(b => extname(b.src))
.filter(Boolean)
);
}
export async function detectRoutes(
files: string[],
builders: Builder[],
featHandleMiss = false,
cleanUrls = false,
trailingSlash?: boolean
): Promise<RoutesResult> {
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' });
const extSet = detectApiExtensions(builders);
if (extSet.size > 0) {
const exts = Array.from(extSet)
.map(ext => ext.slice(1))
.join('|');
const extGroup = `(?:\\.(?:${exts}))`;
if (cleanUrls) {
redirectRoutes.push({
src: `^/(api(?:.+)?)/index${extGroup}?/?$`,
headers: { Location: trailingSlash ? '/$1/' : '/$1' },
status: 308,
});
redirectRoutes.push({
src: `^/api/(.+)${extGroup}/?$`,
headers: { Location: trailingSlash ? '/api/$1/' : '/api/$1' },
status: 308,
});
} else {
defaultRoutes.push({
src: `^/api/(.+)${extGroup}$`,
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 (!featHandleMiss && directory) {
defaultRoutes.push({
src: '/(.*)',
dest: `/${directory}/$1`,
});
}
return { defaultRoutes, redirectRoutes, error };
}

View File

@@ -5,7 +5,7 @@ import { promisify } from 'util';
import { lstat, Stats } from 'fs-extra';
import FileFsRef from '../file-fs-ref';
type GlobOptions = vanillaGlob_.IOptions;
export type GlobOptions = vanillaGlob_.IOptions;
interface FsFiles {
[filePath: string]: FileFsRef;

View File

@@ -5,7 +5,7 @@ import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
import { Prerender } from './prerender';
import download, { DownloadedFiles, isSymbolicLink } from './fs/download';
import getWriteableDirectory from './fs/get-writable-directory';
import glob from './fs/glob';
import glob, { GlobOptions } from './fs/glob';
import rename from './fs/rename';
import {
execAsync,
@@ -40,6 +40,7 @@ export {
DownloadedFiles,
getWriteableDirectory,
glob,
GlobOptions,
rename,
execAsync,
spawnAsync,
@@ -63,12 +64,11 @@ export {
};
export {
detectRoutes,
detectBuilders,
detectOutputDirectory,
detectApiDirectory,
detectApiExtensions,
} from './detect-routes';
export { detectBuilders } from './detect-builders';
} from './detect-builders';
export { detectFramework } from './detect-framework';
export { DetectorFilesystem } from './detectors/filesystem';
export { readConfigFile } from './fs/read-config-file';

View File

@@ -8,6 +8,7 @@ interface PrerenderOptions {
lambda: Lambda;
fallback: FileBlob | FileFsRef | FileRef | null;
group?: number;
bypassToken?: string | null /* optional to be non-breaking change */;
}
export class Prerender {
@@ -16,8 +17,15 @@ export class Prerender {
public lambda: Lambda;
public fallback: FileBlob | FileFsRef | FileRef | null;
public group?: number;
public bypassToken: string | null;
constructor({ expiration, lambda, fallback, group }: PrerenderOptions) {
constructor({
expiration,
lambda,
fallback,
group,
bypassToken,
}: PrerenderOptions) {
this.type = 'Prerender';
this.expiration = expiration;
this.lambda = lambda;
@@ -32,6 +40,22 @@ export class Prerender {
}
this.group = group;
if (bypassToken == null) {
this.bypassToken = null;
} else if (typeof bypassToken === 'string') {
if (bypassToken.length < 32) {
// Enforce 128 bits of entropy for safety reasons (UUIDv4 size)
throw new Error(
'The `bypassToken` argument for `Prerender` must be 32 characters or more.'
);
}
this.bypassToken = bypassToken;
} else {
throw new Error(
'The `bypassToken` argument for `Prerender` must be a `string`.'
);
}
if (typeof fallback === 'undefined') {
throw new Error(
'The `fallback` argument for `Prerender` needs to be a `FileBlob`, `FileFsRef`, `FileRef`, or null.'

View File

@@ -31,6 +31,7 @@ export interface Config {
| undefined;
maxLambdaSize?: string;
includeFiles?: string | string[];
excludeFiles?: string | string[];
bundle?: boolean;
ldsflags?: string;
helpers?: boolean;

View File

@@ -4,7 +4,7 @@ const {
packAndDeploy,
testDeployment,
} = require('../../../test/lib/deployment/test-deployment');
const { glob, detectBuilders, detectRoutes } = require('../');
const { glob, detectBuilders } = require('../');
jest.setTimeout(4 * 60 * 1000);
@@ -110,8 +110,7 @@ it('Test `detectBuilders` and `detectRoutes`', async () => {
},
];
const { builders } = await detectBuilders(files, pkg);
const { defaultRoutes } = await detectRoutes(files, builders);
const { builders, defaultRoutes } = await detectBuilders(files, pkg);
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
await fs.writeFile(
@@ -126,7 +125,7 @@ it('Test `detectBuilders` and `detectRoutes`', async () => {
expect(deployment).toBeDefined();
});
it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
it('Test `detectBuilders` with `index` files', async () => {
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
const fileList = await glob('**', fixture);
@@ -192,8 +191,7 @@ it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
},
];
const { builders } = await detectBuilders(files, pkg);
const { defaultRoutes } = await detectRoutes(files, builders);
const { builders, defaultRoutes } = await detectBuilders(files, pkg);
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
await fs.writeFile(

View File

@@ -1,5 +1,5 @@
import { Source, Route } from '@now/routing-utils';
import { detectBuilders, detectRoutes } from '../src';
import { detectBuilders } from '../src';
import {
detectOutputDirectory,
detectApiDirectory,
@@ -151,6 +151,27 @@ describe('Test `detectBuilders`', () => {
expect(builders!.length).toBe(2);
});
it('api go with test files', async () => {
const files = [
'api/index.go',
'api/index_test.go',
'api/test.go',
'api/testing_another.go',
'api/readme.md',
'api/config/staging.go',
'api/config/staging_test.go',
'api/config/production.go',
'api/config/production_test.go',
'api/src/controllers/health.go',
'api/src/controllers/user.module.go',
'api/src/controllers/user.module_test.go',
];
const { builders } = await detectBuilders(files);
expect(builders!.length).toBe(7);
expect(builders!.some(b => b.src.endsWith('_test.go'))).toBe(false);
});
it('just public', async () => {
const files = ['public/index.html', 'public/favicon.ico', 'README.md'];
@@ -478,7 +499,7 @@ describe('Test `detectBuilders`', () => {
});
expect(errors!.length).toBe(1);
expect(errors![0].code).toBe('invalid_function_source');
expect(errors![0].code).toBe('unused_function');
});
it('do not allow empty functions', async () => {
@@ -627,14 +648,14 @@ describe('Test `detectBuilders`', () => {
const files = ['dist/index.html', 'dist/style.css'];
const { builders } = await detectBuilders(files, null, { projectSettings });
const { builders, defaultRoutes } = await detectBuilders(files, null, {
projectSettings,
});
expect(builders!.length).toBe(1);
expect(builders![0].src).toBe('dist/**/*');
expect(builders![0].use).toBe('@now/static');
const { defaultRoutes } = await detectRoutes(files, builders!);
expect(defaultRoutes!.length).toBe(1);
expect((defaultRoutes![0] as any).src).toBe('/(.*)');
expect((defaultRoutes![0] as any).dest).toBe('/dist/$1');
@@ -647,14 +668,14 @@ describe('Test `detectBuilders`', () => {
const files = ['api/user.ts', 'output/index.html', 'output/style.css'];
const { builders } = await detectBuilders(files, null, { projectSettings });
const { builders, defaultRoutes } = await detectBuilders(files, null, {
projectSettings,
});
expect(builders!.length).toBe(2);
expect(builders![1].src).toBe('output/**/*');
expect(builders![1].use).toBe('@now/static');
const { defaultRoutes } = await detectRoutes(files, builders!);
expect(defaultRoutes!.length).toBe(3);
expect((defaultRoutes![1] as any).status).toBe(404);
expect((defaultRoutes![2] as any).src).toBe('/(.*)');
@@ -745,14 +766,60 @@ describe('Test `detectBuilders`', () => {
},
]);
});
it('All static if `buildCommand` is an empty string', async () => {
const files = ['index.html'];
const projectSettings = { buildCommand: '' };
const { builders, errors } = await detectBuilders(files, null, {
projectSettings,
});
expect(errors).toBe(null);
expect(builders).toBe(null);
});
it('All static if `outputDirectory` is an empty string', async () => {
const files = ['index.html'];
const projectSettings = { outputDirectory: '' };
const { builders, errors } = await detectBuilders(files, null, {
projectSettings,
});
expect(errors).toBe(null);
expect(builders).toBe(null);
});
it('All static if `buildCommand` is an empty string with an `outputDirectory`', async () => {
const files = ['out/index.html'];
const projectSettings = { buildCommand: '', outputDirectory: 'out' };
const { builders, errors } = await detectBuilders(files, null, {
projectSettings,
});
expect(errors).toBe(null);
expect(builders![0]!.use).toBe('@now/static');
expect(builders![0]!.src).toBe('out/**/*');
});
it('do not require build script when `buildCommand` is an empty string', async () => {
const files = ['index.html', 'about.html', 'package.json'];
const projectSettings = { buildCommand: '', outputDirectory: '' };
const pkg = {
scripts: {
build: 'false',
},
};
const { builders, errors } = await detectBuilders(files, pkg, {
projectSettings,
});
expect(builders).toBe(null);
expect(errors).toBe(null);
});
});
it('Test `detectRoutes`', async () => {
{
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(files, builders!);
const { defaultRoutes } = await detectBuilders(files);
expect(defaultRoutes!.length).toBe(3);
expect((defaultRoutes![0] as any).dest).toBe('/api/team.js');
expect((defaultRoutes![1] as any).dest).toBe('/api/user.go');
@@ -763,41 +830,36 @@ it('Test `detectRoutes`', async () => {
{
const files = ['api/user.go', 'api/user.js'];
const { builders } = await detectBuilders(files);
const { error } = await detectRoutes(files, builders!);
expect(error!.code).toBe('conflicting_file_path');
const { errors } = await detectBuilders(files);
expect(errors![0]!.code).toBe('conflicting_file_path');
}
{
const files = ['api/[user].go', 'api/[team]/[id].js'];
const { builders } = await detectBuilders(files);
const { error } = await detectRoutes(files, builders!);
expect(error!.code).toBe('conflicting_file_path');
const { errors } = await detectBuilders(files);
expect(errors![0]!.code).toBe('conflicting_file_path');
}
{
const files = ['api/[team]/[team].js'];
const { builders } = await detectBuilders(files);
const { error } = await detectRoutes(files, builders!);
expect(error!.code).toBe('conflicting_path_segment');
const { errors } = await detectBuilders(files);
expect(errors![0]!.code).toBe('conflicting_path_segment');
}
{
const files = ['api/date/index.js', 'api/date/index.go'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, error } = await detectRoutes(files, builders!);
const { defaultRoutes, errors } = await detectBuilders(files);
expect(defaultRoutes).toBe(null);
expect(error!.code).toBe('conflicting_file_path');
expect(errors![0]!.code).toBe('conflicting_file_path');
}
{
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(files, builders!);
const { defaultRoutes } = await detectBuilders(files);
expect(defaultRoutes!.length).toBe(3);
}
@@ -808,8 +870,7 @@ it('Test `detectRoutes`', async () => {
'api/[endpoint]/[id].js',
];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(files, builders!);
const { defaultRoutes } = await detectBuilders(files);
expect((defaultRoutes![2] as any).status).toBe(404);
expect((defaultRoutes![2] as any).src).toBe('^/api(/.*)?$');
expect((defaultRoutes![3] as any).src).toBe('/(.*)');
@@ -824,8 +885,7 @@ it('Test `detectRoutes`', async () => {
};
const files = ['public/index.html', 'api/[endpoint].js'];
const { builders } = await detectBuilders(files, pkg);
const { defaultRoutes } = await detectRoutes(files, builders!);
const { defaultRoutes } = await detectBuilders(files, pkg);
expect((defaultRoutes![1] as any).status).toBe(404);
expect((defaultRoutes![1] as any).src).toBe('^/api(/.*)?$');
expect(defaultRoutes!.length).toBe(2);
@@ -834,8 +894,7 @@ it('Test `detectRoutes`', async () => {
{
const files = ['public/index.html'];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(files, builders!);
const { defaultRoutes } = await detectBuilders(files);
expect(defaultRoutes!.length).toBe(1);
}
@@ -843,8 +902,7 @@ it('Test `detectRoutes`', async () => {
{
const files = ['api/date/index.js', 'api/date.js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(files, builders!);
const { defaultRoutes } = await detectBuilders(files);
expect(defaultRoutes!.length).toBe(3);
expect((defaultRoutes![0] as any).src).toBe(
@@ -860,8 +918,7 @@ it('Test `detectRoutes`', async () => {
{
const files = ['api/date.js', 'api/[date]/index.js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(files, builders!);
const { defaultRoutes } = await detectBuilders(files);
expect(defaultRoutes!.length).toBe(3);
expect((defaultRoutes![0] as any).src).toBe(
@@ -885,8 +942,7 @@ it('Test `detectRoutes`', async () => {
'api/food.ts',
'api/ts/gold.ts',
];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(files, builders!);
const { builders, defaultRoutes } = await detectBuilders(files);
expect(builders!.length).toBe(4);
expect(builders![0].use).toBe('@now/node');
@@ -901,8 +957,7 @@ it('Test `detectRoutes`', async () => {
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
const files = ['api/user.php'];
const { builders } = await detectBuilders(files, null, { functions });
const { defaultRoutes } = await detectRoutes(files, builders!);
const { defaultRoutes } = await detectBuilders(files, null, { functions });
expect(defaultRoutes!.length).toBe(2);
expect((defaultRoutes![0] as any).dest).toBe('/api/user.php');
@@ -915,12 +970,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
{
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss
);
const { defaultRoutes } = await detectBuilders(files, null, {
featHandleMiss,
});
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
@@ -939,49 +991,40 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
{
const files = ['api/user.go', 'api/user.js'];
const { builders } = await detectBuilders(files);
const { error } = await detectRoutes(files, builders!, featHandleMiss);
expect(error!.code).toBe('conflicting_file_path');
const { errors } = await detectBuilders(files, null, { featHandleMiss });
expect(errors![0]!.code).toBe('conflicting_file_path');
}
{
const files = ['api/[user].go', 'api/[team]/[id].js'];
const { builders } = await detectBuilders(files);
const { error } = await detectRoutes(files, builders!, featHandleMiss);
expect(error!.code).toBe('conflicting_file_path');
const { errors } = await detectBuilders(files, null, { featHandleMiss });
expect(errors![0]!.code).toBe('conflicting_file_path');
}
{
const files = ['api/[team]/[team].js'];
const { builders } = await detectBuilders(files);
const { error } = await detectRoutes(files, builders!, featHandleMiss);
expect(error!.code).toBe('conflicting_path_segment');
const { errors } = await detectBuilders(files, null, { featHandleMiss });
expect(errors![0]!.code).toBe('conflicting_path_segment');
}
{
const files = ['api/date/index.js', 'api/date/index.go'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, error } = await detectRoutes(
files,
builders!,
featHandleMiss
);
const { defaultRoutes, errors } = await detectBuilders(files, null, {
featHandleMiss,
});
expect(defaultRoutes).toBe(null);
expect(error!.code).toBe('conflicting_file_path');
expect(errors![0]!.code).toBe('conflicting_file_path');
}
{
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss
);
const { defaultRoutes } = await detectBuilders(files, null, {
featHandleMiss,
});
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
@@ -1014,12 +1057,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
'api/[endpoint]/[id].js',
];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss
);
const { defaultRoutes } = await detectBuilders(files, null, {
featHandleMiss,
});
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
@@ -1058,12 +1098,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
const files = ['public/index.html', 'api/[endpoint].js'];
const { builders } = await detectBuilders(files, pkg);
const { defaultRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss
);
const { defaultRoutes } = await detectBuilders(files, pkg, {
featHandleMiss,
});
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
@@ -1087,24 +1124,18 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
{
const files = ['public/index.html'];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss
);
const { defaultRoutes } = await detectBuilders(files, null, {
featHandleMiss,
});
expect(defaultRoutes).toStrictEqual([]);
}
{
const files = ['api/date/index.js', 'api/date.js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss
);
const { defaultRoutes } = await detectBuilders(files, null, {
featHandleMiss,
});
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
@@ -1123,12 +1154,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
{
const files = ['api/date.js', 'api/[date]/index.js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss
);
const { defaultRoutes } = await detectBuilders(files, null, {
featHandleMiss,
});
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
@@ -1158,12 +1186,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
'api/food.ts',
'api/ts/gold.ts',
];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss
);
const { defaultRoutes } = await detectBuilders(files, null, {
featHandleMiss,
});
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
@@ -1181,12 +1206,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
const files = ['api/user.php'];
const { builders } = await detectBuilders(files, null, { functions });
const { defaultRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss
);
const { defaultRoutes } = await detectBuilders(files, null, {
functions,
featHandleMiss,
});
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
@@ -1200,8 +1223,11 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
});
it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async () => {
const featHandleMiss = true;
const cleanUrls = true;
const options = {
featHandleMiss: true,
cleanUrls: true,
};
const testHeaders = (redirectRoutes: Route[] | null) => {
if (!redirectRoutes || redirectRoutes.length === 0) {
throw new Error('Expected one redirect but found none');
@@ -1213,12 +1239,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
{
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls
null,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1260,65 +1284,43 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
{
const files = ['api/user.go', 'api/user.js'];
const { builders } = await detectBuilders(files);
const { error } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
expect(error!.code).toBe('conflicting_file_path');
const { errors } = await detectBuilders(files, null, options);
expect(errors![0]!.code).toBe('conflicting_file_path');
}
{
const files = ['api/[user].go', 'api/[team]/[id].js'];
const { builders } = await detectBuilders(files);
const { error } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
expect(error!.code).toBe('conflicting_file_path');
const { errors } = await detectBuilders(files, null, options);
expect(errors![0]!.code).toBe('conflicting_file_path');
}
{
const files = ['api/[team]/[team].js'];
const { builders } = await detectBuilders(files);
const { error } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
expect(error!.code).toBe('conflicting_path_segment');
const { errors } = await detectBuilders(files, null, options);
expect(errors![0]!.code).toBe('conflicting_path_segment');
}
{
const files = ['api/date/index.js', 'api/date/index.go'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, error } = await detectRoutes(
const { defaultRoutes, errors } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls
null,
options
);
expect(defaultRoutes).toBe(null);
expect(error!.code).toBe('conflicting_file_path');
expect(errors![0]!.code).toBe('conflicting_file_path');
}
{
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls
null,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1348,12 +1350,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
'api/[endpoint]/[id].js',
];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls
null,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1389,12 +1389,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
const files = ['public/index.html', 'api/[endpoint].js'];
const { builders } = await detectBuilders(files, pkg);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls
pkg,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1415,25 +1413,17 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
{
const files = ['public/index.html'];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
const { defaultRoutes } = await detectBuilders(files, null, options);
expect(defaultRoutes).toStrictEqual([]);
}
{
const files = ['api/date/index.js', 'api/date.js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls
null,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1449,12 +1439,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
{
const files = ['api/date.js', 'api/[date]/index.js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls
null,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1481,12 +1469,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
'api/food.ts',
'api/ts/gold.ts',
];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls
null,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1500,12 +1486,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
const files = ['api/user.php'];
const { builders } = await detectBuilders(files, null, { functions });
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls
null,
{ functions, ...options }
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1516,9 +1500,12 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
});
it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingSlash=true`', async () => {
const featHandleMiss = true;
const cleanUrls = true;
const trailingSlash = true;
const options = {
featHandleMiss: true,
cleanUrls: true,
trailingSlash: true,
};
const testHeaders = (redirectRoutes: Route[] | null) => {
if (!redirectRoutes || redirectRoutes.length === 0) {
throw new Error('Expected one redirect but found none');
@@ -1530,13 +1517,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
{
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
null,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1581,13 +1565,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
{
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
null,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1617,13 +1598,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
'api/[endpoint]/[id].js',
];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
null,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1659,13 +1637,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
const files = ['public/index.html', 'api/[endpoint].js'];
const { builders } = await detectBuilders(files, pkg);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
pkg,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1686,13 +1661,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
{
const files = ['api/date/index.js', 'api/date.js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
null,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1708,13 +1680,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
{
const files = ['api/date.js', 'api/[date]/index.js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
null,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1741,13 +1710,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
'api/food.ts',
'api/ts/gold.ts',
];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
null,
options
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
@@ -1761,13 +1727,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
const files = ['api/user.php'];
const { builders } = await detectBuilders(files, null, { functions });
const { defaultRoutes, redirectRoutes } = await detectRoutes(
const { defaultRoutes, redirectRoutes } = await detectBuilders(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
null,
{ functions, ...options }
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([

View File

@@ -11,6 +11,10 @@ const {
} = require('../dist');
it('should re-create symlinks properly', async () => {
if (process.platform === 'win32') {
console.log('Skipping test on windows');
return;
}
const files = await glob('**', path.join(__dirname, 'symlinks'));
assert.equal(Object.keys(files).length, 2);
@@ -29,6 +33,10 @@ it('should re-create symlinks properly', async () => {
});
it('should create zip files with symlinks properly', async () => {
if (process.platform === 'win32') {
console.log('Skipping test on windows');
return;
}
const files = await glob('**', path.join(__dirname, 'symlinks'));
assert.equal(Object.keys(files).length, 2);

View File

@@ -1,6 +1,6 @@
{
"name": "@now/cgi",
"version": "1.0.1",
"version": "1.0.3",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -15,7 +15,7 @@
"@zeit/best@0.4.3":
version "0.4.3"
resolved "http://registry.npmjs.org/@zeit/best/-/best-0.4.3.tgz#eaebdfa8b24121a97b1753501ea8c9330d549b30"
resolved "https://registry.npmjs.org/@zeit/best/-/best-0.4.3.tgz#eaebdfa8b24121a97b1753501ea8c9330d549b30"
dependencies:
arg "1.0.0"
chalk "2.3.1"
@@ -144,7 +144,7 @@ call-me-maybe@^1.0.1:
chalk@2.3.1:
version "2.3.1"
resolved "http://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
resolved "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
dependencies:
ansi-styles "^3.2.0"
escape-string-regexp "^1.0.5"
@@ -585,8 +585,8 @@ minimatch@^3.0.4:
brace-expansion "^1.1.7"
mixin-deep@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
version "1.3.2"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
dependencies:
for-in "^1.0.2"
is-extendable "^1.0.1"
@@ -704,7 +704,7 @@ rmfr@2.0.0:
safe-regex@^1.1.0:
version "1.1.0"
resolved "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
resolved "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
dependencies:
ret "~0.1.10"

View File

@@ -1,4 +1,4 @@
![now](https://assets.zeit.co/image/upload/v1542240976/repositories/now-cli/now-cli-repo-banner-v3.png)
![now](https://assets.zeit.co/image/upload/v1581518533/repositories/now-cli/v4.png)
[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/zeit)

View File

@@ -1,6 +1,6 @@
{
"name": "now",
"version": "17.0.0-canary.26",
"version": "17.0.4",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Now",
@@ -12,7 +12,7 @@
},
"scripts": {
"preinstall": "node ./scripts/preinstall.js",
"test-unit": "nyc ava test/*unit.js --serial --fail-fast --verbose",
"test-unit": "nyc ava test/unit.js test/dev-builder.unit.js test/dev-router.unit.js test/dev-server.unit.js --serial --fail-fast --verbose",
"test-integration": "ava test/integration.js --serial --fail-fast",
"test-integration-v1": "ava test/integration-v1.js --serial --fail-fast",
"test-integration-now-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
@@ -63,6 +63,7 @@
},
"devDependencies": {
"@sentry/node": "5.5.0",
"@sindresorhus/slugify": "0.10.0",
"@types/ansi-escapes": "3.0.0",
"@types/ansi-regex": "4.0.0",
"@types/async-retry": "1.2.1",
@@ -89,10 +90,10 @@
"@types/tar-fs": "1.16.1",
"@types/text-table": "0.2.0",
"@types/universal-analytics": "0.4.2",
"@types/which": "1.3.1",
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@zeit/dockerignore": "0.0.5",
"@zeit/fun": "0.11.0",
"@zeit/fun": "0.11.2",
"@zeit/ncc": "0.18.5",
"@zeit/source-map-support": "0.6.2",
"ajv": "6.10.2",
@@ -108,7 +109,7 @@
"chalk": "2.4.2",
"chokidar": "2.1.6",
"clipboardy": "2.1.0",
"codecov": "3.1.0",
"codecov": "3.6.5",
"cpy": "7.2.0",
"credit-card": "3.0.1",
"date-fns": "1.29.0",
@@ -131,7 +132,7 @@
"http-proxy": "1.17.0",
"ignore": "4.0.6",
"ini": "1.3.4",
"inquirer": "3.3.0",
"inquirer": "7.0.4",
"is-port-reachable": "3.0.0",
"is-url": "1.2.2",
"jaro-winkler": "0.2.8",
@@ -174,10 +175,10 @@
"universal-analytics": "0.4.20",
"update-check": "1.5.3",
"utility-types": "2.1.0",
"which": "1.3.1",
"which": "2.0.2",
"which-promise": "1.0.0",
"write-json-file": "2.2.0",
"xdg-app-paths": "5.1.0",
"yarn": "1.17.3"
"yarn": "1.22.0"
}
}

View File

@@ -8,12 +8,11 @@ import getAliases from '../../util/alias/get-aliases';
import getScope from '../../util/get-scope.ts';
import stamp from '../../util/output/stamp.ts';
import strlen from '../../util/strlen.ts';
import wait from '../../util/output/wait';
export default async function ls(ctx, opts, args, output) {
const {
authConfig: { token },
config
config,
} = ctx;
const { currentTeam } = config;
const { apiUrl } = ctx;
@@ -22,7 +21,7 @@ export default async function ls(ctx, opts, args, output) {
apiUrl,
token,
currentTeam,
debug: debugEnabled
debug: debugEnabled,
});
let contextName = null;
@@ -51,7 +50,7 @@ export default async function ls(ctx, opts, args, output) {
return 1;
}
cancelWait = wait(
cancelWait = output.spinner(
args[0]
? `Fetching alias details for "${args[0]}" under ${chalk.bold(
contextName
@@ -112,13 +111,13 @@ function printAliasTable(aliases) {
? a.deployment.url
: chalk.gray(''),
a.alias,
ms(Date.now() - new Date(a.created))
])
ms(Date.now() - new Date(a.created)),
]),
],
{
align: ['l', 'l', 'r'],
hsep: ' '.repeat(4),
stringLength: strlen
stringLength: strlen,
}
).replace(/^/gm, ' ')}\n\n`;
}
@@ -130,13 +129,13 @@ function printPathAliasTable(rules) {
rules.map(rule => [
rule.pathname ? rule.pathname : chalk.cyan('[fallthrough]'),
rule.method ? rule.method : '*',
rule.dest
rule.dest,
])
),
{
align: ['l', 'l', 'l', 'l'],
hsep: ' '.repeat(6),
stringLength: strlen
stringLength: strlen,
}
).replace(/^(.*)/gm, ' $1')}\n`;
}

View File

@@ -21,7 +21,7 @@ export default async function({ creditCards, clear = false, contextName }) {
name: {
label: rightPad('Full Name', 12),
placeholder: 'John Appleseed',
validateValue: data => data.trim().length > 0
validateValue: data => data.trim().length > 0,
},
cardNumber: {
@@ -36,7 +36,7 @@ export default async function({ creditCards, clear = false, contextName }) {
return false;
}
return ccValidator.isValidCardNumber(data, type);
}
},
},
ccv: {
@@ -46,7 +46,7 @@ export default async function({ creditCards, clear = false, contextName }) {
validateValue: data => {
const brand = state.cardNumber.brand.toLowerCase();
return ccValidator.doesCvvMatchType(data, brand);
}
},
},
expDate: {
@@ -54,8 +54,8 @@ export default async function({ creditCards, clear = false, contextName }) {
mask: 'expDate',
placeholder: 'mm / yyyy',
middleware: expDateMiddleware,
validateValue: data => !ccValidator.isExpired(...data.split(' / '))
}
validateValue: data => !ccValidator.isExpired(...data.split(' / ')),
},
};
async function render() {
@@ -80,7 +80,7 @@ export default async function({ creditCards, clear = false, contextName }) {
mask: piece.mask,
validateKeypress: piece.validateKeypress,
validateValue: piece.validateValue,
autoComplete: piece.autoComplete
autoComplete: piece.autoComplete,
});
piece.value = result;
@@ -135,7 +135,7 @@ export default async function({ creditCards, clear = false, contextName }) {
name: state.name.value,
cardNumber: state.cardNumber.value,
ccv: state.ccv.value,
expDate: state.expDate.value
expDate: state.expDate.value,
});
stopSpinner();
@@ -156,9 +156,9 @@ export default async function({ creditCards, clear = false, contextName }) {
stopSpinner();
const linesToClear = state.error ? 15 : 14;
process.stdout.write(ansiEscapes.eraseLines(linesToClear));
state.error = `${chalk.red(
'> Error!'
)} ${err.message} Please make sure the info is correct`;
state.error = `${chalk.red('> Error!')} ${
err.message
} Please make sure the info is correct`;
await render();
}
}

View File

@@ -5,7 +5,6 @@ import Now from '../../util';
import Client from '../../util/client';
import getScope from '../../util/get-scope';
import stamp from '../../util/output/stamp';
import wait from '../../util/output/wait';
import createCertFromFile from '../../util/certs/create-cert-from-file';
import createCertForCns from '../../util/certs/create-cert-for-cns';
import { NowContext } from '../../types';
@@ -110,7 +109,7 @@ async function add(
(res, item) => res.concat(item.split(',')),
[]
);
const cancelWait = wait(
const cancelWait = output.spinner(
`Generating a certificate for ${chalk.bold(cns.join(', '))}`
);

View File

@@ -186,7 +186,7 @@ export default async ctx => {
contextName,
output,
stats,
localConfig || {},
localConfig,
parts.latestArgs
);
}

View File

@@ -1,5 +1,6 @@
import ms from 'ms';
import bytes from 'bytes';
import { join } from 'path';
import { write as copy } from 'clipboardy';
import chalk from 'chalk';
import title from 'title';
@@ -47,8 +48,12 @@ import {
import getProjectName from '../../util/get-project-name';
import selectOrg from '../../util/input/select-org';
import inputProject from '../../util/input/input-project';
import validatePaths from '../../util/validate-paths';
import { prependEmoji, emoji } from '../../util/emoji';
import { inputRootDirectory } from '../../util/input/input-root-directory';
import validatePaths, {
validateRootDirectory,
} from '../../util/validate-paths';
import { readLocalConfig } from '../../util/config/files';
const addProcessEnv = async (log, env) => {
let val;
@@ -90,7 +95,6 @@ const printDeploymentStatus = async (
},
deployStamp,
isClipboardEnabled,
quiet,
isFile
) => {
const isProdDeployment = target === 'production';
@@ -131,22 +135,22 @@ const printDeploymentStatus = async (
}
// copy to clipboard
let isCopiedToClipboard = false;
if (isClipboardEnabled && !isWildcard) {
await copy(previewUrl).catch(error =>
output.debug(`Error copying to clipboard: ${error}`)
);
}
// write to stdout
if (quiet) {
process.stdout.write(`https://${deploymentUrl}`);
await copy(previewUrl)
.then(() => {
isCopiedToClipboard = true;
})
.catch(error => output.debug(`Error copying to clipboard: ${error}`));
}
output.print(
prependEmoji(
`${isProdDeployment ? 'Production' : 'Preview'}: ${chalk.bold(
previewUrl
)} ${deployStamp()}`,
)}${
isCopiedToClipboard ? chalk.gray(` [copied to clipboard]`) : ''
} ${deployStamp()}`,
emoji('success')
) + `\n`
);
@@ -235,13 +239,6 @@ export default async function main(
const { isFile, path } = pathValidation;
const autoConfirm = argv['--confirm'] || isFile;
// build `meta`
const meta = Object.assign(
{},
parseMeta(localConfig.meta),
parseMeta(argv['--meta'])
);
// --no-scale
if (argv['--no-scale']) {
warn(`The option --no-scale is only supported on Now 1.0 deployments`);
@@ -257,7 +254,125 @@ export default async function main(
);
}
if (localConfig && localConfig.name) {
const client = new Client({
apiUrl: ctx.apiUrl,
token: ctx.authConfig.token,
debug: debugEnabled,
});
// retrieve `project` and `org` from .now
const link = await getLinkedProject(output, client, path);
if (link.status === 'error') {
return link.exitCode;
}
let { org, project, status } = link;
let newProjectName = null;
let rootDirectory = project ? project.rootDirectory : null;
if (status === 'not_linked') {
const shouldStartSetup =
autoConfirm ||
(await confirm(
`Set up and deploy ${chalk.cyan(`${toHumanPath(path)}`)}?`,
true
));
if (!shouldStartSetup) {
output.print(`Aborted. Project not set up.\n`);
return 0;
}
org = await selectOrg(
output,
'Which scope do you want to deploy to?',
client,
ctx.config.currentTeam,
autoConfirm
);
// We use `localConfig` here to read the name
// even though the `now.json` file can change
// afterwards, this is fine since the property
// will be deprecated and can be replaced with
// user input.
const detectedProjectName = getProjectName({
argv,
nowConfig: localConfig || {},
isFile,
paths,
});
const projectOrNewProjectName = await inputProject(
output,
client,
org,
detectedProjectName,
autoConfirm
);
if (typeof projectOrNewProjectName === 'string') {
newProjectName = projectOrNewProjectName;
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
} else {
project = projectOrNewProjectName;
rootDirectory = project.rootDirectory;
// we can already link the project
await linkFolderToProject(
output,
path,
{
projectId: project.id,
orgId: org.id,
},
project.name,
org.slug
);
status = 'linked';
}
}
const sourcePath = rootDirectory ? join(path, rootDirectory) : path;
if (
rootDirectory &&
(await validateRootDirectory(
output,
path,
sourcePath,
project
? `To change your project settings, go to https://zeit.co/${org.slug}/${project.name}/settings`
: ''
)) === false
) {
return 1;
}
// If Root Directory is used we'll try to read the config
// from there instead and use it if it exists.
if (rootDirectory) {
const rootDirectoryConfig = readLocalConfig(sourcePath);
if (rootDirectoryConfig) {
debug(`Read local config from root directory (${rootDirectory})`);
localConfig = rootDirectoryConfig;
} else if (localConfig) {
output.print(
`${prependEmoji(
`The ${highlight(
'now.json'
)} file should be inside of the provided root directory.`,
emoji('warning')
)}\n`
);
}
}
localConfig = localConfig || {};
if (localConfig.name) {
output.print(
`${prependEmoji(
`The ${code('name')} property in ${highlight(
@@ -307,6 +422,13 @@ export default async function main(
}
}
// build `meta`
const meta = Object.assign(
{},
parseMeta(localConfig.meta),
parseMeta(argv['--meta'])
);
// Merge dotenv config, `env` from now.json, and `--env` / `-e` arguments
const deploymentEnv = Object.assign(
{},
@@ -364,77 +486,6 @@ export default async function main(
target = 'production';
}
const client = new Client({
apiUrl: ctx.apiUrl,
token: ctx.authConfig.token,
debug: debugEnabled,
});
// retrieve `project` and `org` from .now
const link = await getLinkedProject(output, client, path);
if (link.status === 'error') {
return link.exitCode;
}
let { org, project, status } = link;
let newProjectName = null;
if (status === 'not_linked') {
const shouldStartSetup =
autoConfirm ||
(await confirm(
`Set up and deploy ${chalk.cyan(`${toHumanPath(path)}`)}?`,
true
));
if (!shouldStartSetup) {
output.print(`Aborted. Project not set up.\n`);
return 0;
}
org = await selectOrg(
'Which scope do you want to deploy to?',
client,
ctx.config.currentTeam,
autoConfirm
);
const detectedProjectName = getProjectName({
argv,
nowConfig: localConfig,
isFile,
paths,
});
const projectOrNewProjectName = await inputProject(
output,
client,
org,
detectedProjectName,
autoConfirm
);
if (typeof projectOrNewProjectName === 'string') {
newProjectName = projectOrNewProjectName;
} else {
project = projectOrNewProjectName;
// we can already link the project
await linkFolderToProject(
output,
path,
{
projectId: project.id,
orgId: org.id,
},
project.name,
org.slug
);
status = 'linked';
}
}
const currentTeam = org.type === 'team' ? org.id : undefined;
const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam });
let deployStamp = stamp();
@@ -462,10 +513,11 @@ export default async function main(
output,
now,
contextName,
[path],
[sourcePath],
createArgs,
org,
!project && !isFile
!project && !isFile,
path
);
if (
@@ -474,6 +526,10 @@ export default async function main(
) {
let { projectSettings, framework } = deployment;
if (rootDirectory) {
projectSettings.rootDirectory = rootDirectory;
}
const settings = await editProjectSettings(
output,
projectSettings,
@@ -489,10 +545,11 @@ export default async function main(
output,
now,
contextName,
[path],
[sourcePath],
createArgs,
org,
false
false,
path
);
}
@@ -501,6 +558,14 @@ export default async function main(
return 1;
}
if (deployment instanceof Error) {
output.error(
`${deployment.message ||
'An unexpected error occurred while deploying your project'} (http://zeit.ink/P4)`
);
return 1;
}
const deploymentResponse = await getDeploymentByIdOrHost(
now,
contextName,
@@ -616,7 +681,6 @@ export default async function main(
deployment,
deployStamp,
!argv['--no-clipboard'],
quiet,
isFile
);
}
@@ -665,6 +729,11 @@ function handleCreateDeployError(output, error) {
return 1;
}
if (dataPath === '.name') {
output.error(message);
return 1;
}
if (keyword === 'type') {
const prop = dataPath.substr(1, dataPath.length);

View File

@@ -852,7 +852,7 @@ async function sync({
const { url } = now;
const dcs =
deploymentType !== 'static' && deployment.scale
? chalk` ({bold ${Object.keys(deployment.scale).join(', ')})`
? ` (${ chalk.bold(Object.keys(deployment.scale).join(', ')) })`
: '';
if (isTTY) {

View File

@@ -23,7 +23,7 @@ export default async function dev(
output: Output
) {
const [dir = '.'] = args;
const cwd = path.resolve(dir);
let cwd = path.resolve(dir);
const listen = parseListen(opts['--listen'] || '3000');
const debug = opts['--debug'] || false;
@@ -72,6 +72,10 @@ export default async function dev(
}
}
}
if (project.rootDirectory) {
cwd = path.join(cwd, project.rootDirectory);
}
}
const devServer = new DevServer(cwd, { output, debug, devCommand });

View File

@@ -13,7 +13,6 @@ import param from '../../util/output/param';
import promptBool from '../../util/input/prompt-bool';
import purchaseDomain from '../../util/domains/purchase-domain';
import stamp from '../../util/output/stamp';
import wait from '../../util/output/wait';
type Options = {
'--debug': boolean;
@@ -27,7 +26,7 @@ export default async function buy(
) {
const {
authConfig: { token },
config
config,
} = ctx;
const { currentTeam } = config;
const { apiUrl } = ctx;
@@ -100,7 +99,7 @@ export default async function buy(
let buyResult;
const purchaseStamp = stamp();
const stopPurchaseSpinner = wait('Purchasing');
const stopPurchaseSpinner = output.spinner('Purchasing');
try {
buyResult = await purchaseDomain(client, domainName, price);

View File

@@ -9,7 +9,6 @@ import listInput from '../../util/input/list';
import listItem from '../../util/output/list-item';
import promptBool from '../../util/input/prompt-bool';
import toHumanPath from '../../util/humanize-path';
import wait from '../../util/output/wait';
import { Output } from '../../util/output';
import { NowContext } from '../../types';
import success from '../../util/output/success';
@@ -24,10 +23,10 @@ type Options = {
};
type Example = {
name: string,
visible: boolean,
suggestions: string[]
}
name: string;
visible: boolean;
suggestions: string[];
};
const EXAMPLE_API = 'https://now-example-files.zeit.sh';
@@ -40,7 +39,7 @@ export default async function init(
const [name, dir] = args;
const force = opts['-f'] || opts['--force'];
const examples = await fetchExampleList();
const examples = await fetchExampleList(output);
if (!examples) {
throw new Error(`Could not fetch example list.`);
@@ -56,22 +55,22 @@ export default async function init(
return 0;
}
return extractExample(chosen, dir, force);
return extractExample(output, chosen, dir, force);
}
if (exampleList.includes(name)) {
return extractExample(name, dir, force);
return extractExample(output, name, dir, force);
}
const oldExample = examples.find(x => !x.visible && x.name === name);
if (oldExample) {
return extractExample(name, dir, force, 'v1');
return extractExample(output, name, dir, force, 'v1');
}
const found = await guess(exampleList, name);
if (typeof found === 'string') {
return extractExample(found, dir, force);
return extractExample(output, found, dir, force);
}
console.log(info('No changes made.'));
@@ -81,8 +80,8 @@ export default async function init(
/**
* Fetch example list json
*/
async function fetchExampleList() {
const stopSpinner = wait('Fetching examples');
async function fetchExampleList(output: Output) {
const stopSpinner = output.spinner('Fetching examples');
const url = `${EXAMPLE_API}/v2/list.json`;
try {
@@ -93,7 +92,7 @@ async function fetchExampleList() {
throw new Error(`Failed fetching list.json (${resp.statusText}).`);
}
return await resp.json() as Example[];
return (await resp.json()) as Example[];
} catch (e) {
stopSpinner();
}
@@ -106,22 +105,28 @@ async function chooseFromDropdown(message: string, exampleList: string[]) {
const choices = exampleList.map(name => ({
name,
value: name,
short: name
short: name,
}));
return listInput({
message,
separator: false,
choices
choices,
});
}
/**
* Extract example to directory
*/
async function extractExample(name: string, dir: string, force?: boolean, ver: string = 'v2') {
async function extractExample(
output: Output,
name: string,
dir: string,
force?: boolean,
ver: string = 'v2'
) {
const folder = prepareFolder(process.cwd(), dir || name, force);
const stopSpinner = wait(`Fetching ${name}`);
const stopSpinner = output.spinner(`Fetching ${name}`);
const url = `${EXAMPLE_API}/${ver}/download/${name}.tar.gz`;

View File

@@ -9,7 +9,6 @@ import createOutput from '../util/output';
import Now from '../util';
import logo from '../util/output/logo';
import elapsed from '../util/output/elapsed.ts';
import wait from '../util/output/wait';
import { handleError } from '../util/error';
import strlen from '../util/strlen.ts';
import Client from '../util/client.ts';
@@ -79,13 +78,16 @@ export default async function main(ctx) {
return 1;
}
const { authConfig: { token }, config } = ctx;
const {
authConfig: { token },
config,
} = ctx;
const { currentTeam } = config;
const client = new Client({
apiUrl,
token,
currentTeam,
debug: debugEnabled
debug: debugEnabled,
});
let contextName = null;
@@ -104,7 +106,7 @@ export default async function main(ctx) {
// resolve the deployment, since we might have been given an alias
const depFetchStart = Date.now();
const cancelWait = wait(
const cancelWait = output.spinner(
`Fetching deployment "${id}" in ${chalk.bold(contextName)}`
);
@@ -140,7 +142,7 @@ export default async function main(ctx) {
limits,
version,
routes,
readyState
readyState,
} = deployment;
const isBuilds = version === 2;
@@ -159,7 +161,7 @@ export default async function main(ctx) {
)}/events?types=event`
)
),
isBuilds ? now.fetch(buildsUrl) : { builds: [] }
isBuilds ? now.fetch(buildsUrl) : { builds: [] },
]);
cancelWait();
@@ -174,7 +176,9 @@ export default async function main(ctx) {
print(` ${chalk.cyan('version')}\t${version}\n`);
print(` ${chalk.cyan('id')}\t\t${finalId}\n`);
print(` ${chalk.cyan('name')}\t${name}\n`);
print(` ${chalk.cyan('readyState')}\t${stateString(state || readyState)}\n`);
print(
` ${chalk.cyan('readyState')}\t${stateString(state || readyState)}\n`
);
if (!isBuilds) {
print(` ${chalk.cyan('type')}\t${type}\n`);
}
@@ -255,7 +259,7 @@ export default async function main(ctx) {
`${table(t, {
align: ['l', 'c', 'c', 'c'],
hsep: ' '.repeat(8),
stringLength: strlen
stringLength: strlen,
}).replace(/^(.*)/gm, ' $1')}\n`
);
print('\n');
@@ -269,9 +273,9 @@ export default async function main(ctx) {
events.forEach(data => {
if (!data.event) return; // keepalive
print(
` ${chalk.gray(
new Date(data.created).toISOString()
)} ${data.event} ${getEventMetadata(data)}\n`
` ${chalk.gray(new Date(data.created).toISOString())} ${
data.event
} ${getEventMetadata(data)}\n`
);
});
print('\n');

View File

@@ -8,7 +8,6 @@ import chalk from 'chalk';
import ua from '../util/ua.ts';
import getArgs from '../util/get-args';
import error from '../util/output/error';
import wait from '../util/output/wait';
import highlight from '../util/output/highlight';
import ok from '../util/output/ok';
import cmd from '../util/output/cmd.ts';
@@ -191,7 +190,7 @@ const login = async ctx => {
let verificationToken;
let securityCode;
stopSpinner = wait('Sending you an email');
stopSpinner = output.spinner('Sending you an email');
try {
const data = await executeLogin(apiUrl, email);
@@ -216,7 +215,7 @@ const login = async ctx => {
)}.\n`
);
stopSpinner = wait('Waiting for your confirmation');
stopSpinner = output.spinner('Waiting for your confirmation');
let token;

View File

@@ -6,7 +6,6 @@ import logo from '../util/output/logo';
import elapsed from '../util/output/elapsed.ts';
import { maybeURL, normalizeURL, parseInstanceURL } from '../util/url';
import printEvents from '../util/events';
import wait from '../util/output/wait';
import Client from '../util/client.ts';
import getScope from '../util/get-scope.ts';
@@ -165,7 +164,7 @@ export default async function main(ctx) {
const id = deploymentIdOrURL;
const depFetchStart = Date.now();
const cancelWait = wait(
const cancelWait = output.spinner(
`Fetching deployment "${id}" in ${chalk.bold(contextName)}`
);
@@ -287,12 +286,12 @@ function printLogShort(log) {
data = JSON.stringify(obj, null, 2);
} else {
data = (log.text || '')
.replace(/\n$/, '')
.replace(/^\n/, '')
// eslint-disable-next-line no-control-regex
.replace(/\x1b\[1000D/g, '')
.replace(/\x1b\[0K/g, '')
.replace(/\x1b\[1A/g, '');
.replace(/\n$/, '')
.replace(/^\n/, '')
// eslint-disable-next-line no-control-regex
.replace(/\x1b\[1000D/g, '')
.replace(/\x1b\[0K/g, '')
.replace(/\x1b\[1A/g, '');
if (/warning/i.test(data)) {
data = chalk.yellow(data);
} else if (log.type === 'stderr') {

View File

@@ -6,7 +6,6 @@ import table from 'text-table';
import Now from '../util';
import getAliases from '../util/alias/get-aliases';
import createOutput from '../util/output';
import wait from '../util/output/wait';
import logo from '../util/output/logo';
import cmd from '../util/output/cmd.ts';
import elapsed from '../util/output/elapsed.ts';
@@ -132,7 +131,7 @@ export default async function main(ctx) {
throw err;
}
const cancelWait = wait(
const cancelWait = output.spinner(
`Fetching deployment(s) ${ids
.map(id => `"${id}"`)
.join(' ')} in ${chalk.bold(contextName)}`

View File

@@ -11,6 +11,7 @@ import logo from '../util/output/logo';
import Client from '../util/client.ts';
import getScope from '../util/get-scope.ts';
import createOutput from '../util/output';
import confirm from '../util/input/confirm';
const help = () => {
console.log(`
@@ -144,7 +145,7 @@ async function run({ output, token, contextName, currentTeam }) {
const elapsed = ms(new Date() - start);
console.log(
`> ${plural('secret', list.length, true)} found under ${chalk.bold(
`${plural('secret', list.length, true)} found under ${chalk.bold(
contextName
)} ${chalk.gray(`[${elapsed}]`)}`
);
@@ -189,22 +190,29 @@ async function run({ output, token, contextName, currentTeam }) {
const theSecret = list.find(secret => secret.name === args[0]);
if (theSecret) {
const yes = argv.yes || (await readConfirmation(theSecret));
const yes =
argv.yes || (await readConfirmation(output, theSecret, contextName));
if (!yes) {
console.error(error('User abort'));
output.print(`Aborted. Secret not deleted.\n`);
return exit(0);
}
} else {
console.error(error(`No secret found by name "${args[0]}"`));
console.error(
error(
`No secret found by name "${args[0]}" under ${chalk.bold(
contextName
)}`
)
);
return exit(1);
}
const secret = await secrets.rm(args[0]);
const elapsed = ms(new Date() - start);
console.log(
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
`${chalk.cyan('Success!')} Secret ${chalk.bold(
secret.name
)} removed ${chalk.gray(`[${elapsed}]`)}`
)} under ${chalk.bold(contextName)} removed ${chalk.gray(`[${elapsed}]`)}`
);
return secrets.close();
}
@@ -223,9 +231,11 @@ async function run({ output, token, contextName, currentTeam }) {
const secret = await secrets.rename(args[0], args[1]);
const elapsed = ms(new Date() - start);
console.log(
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
`${chalk.cyan('Success!')} Secret ${chalk.bold(
secret.oldName
)} renamed to ${chalk.bold(args[1])} ${chalk.gray(`[${elapsed}]`)}`
)} renamed to ${chalk.bold(args[1])} under ${chalk.bold(
contextName
)} ${chalk.gray(`[${elapsed}]`)}`
);
return secrets.close();
}
@@ -243,7 +253,7 @@ async function run({ output, token, contextName, currentTeam }) {
if (args.length > 2) {
const example = chalk.cyan(`$ now secret add -- "${args[0]}"`);
console.log(
`> If your secret has spaces or starts with '-', make sure to terminate command options with double dash and wrap it in quotes. Example: \n ${example} `
`If your secret has spaces or starts with '-', make sure to terminate command options with double dash and wrap it in quotes. Example: \n ${example} `
);
}
@@ -259,9 +269,9 @@ async function run({ output, token, contextName, currentTeam }) {
}
console.log(
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
`${chalk.cyan('Success!')} Secret ${chalk.bold(
name.toLowerCase()
)} added (${chalk.bold(contextName)}) ${chalk.gray(`[${elapsed}]`)}`
)} added under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`
);
return secrets.close();
}
@@ -278,33 +288,19 @@ process.on('uncaughtException', err => {
exit(1);
});
function readConfirmation(secret) {
return new Promise(resolve => {
const time = chalk.gray(`${ms(new Date() - new Date(secret.created))} ago`);
const tbl = table([[chalk.bold(secret.name), time]], {
align: ['r', 'l'],
hsep: ' '.repeat(6),
});
process.stdout.write(
'> The following secret will be removed permanently\n'
);
process.stdout.write(` ${tbl}\n`);
process.stdout.write(
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`
);
process.stdin
.on('data', d => {
process.stdin.pause();
resolve(
d
.toString()
.trim()
.toLowerCase() === 'y'
);
})
.resume();
async function readConfirmation(output, secret, contextName) {
const time = chalk.gray(`${ms(new Date() - new Date(secret.created))} ago`);
const tbl = table([[chalk.bold(secret.name), time]], {
align: ['r', 'l'],
hsep: ' '.repeat(6),
});
output.print(
`The following secret will be removed permanently from ${chalk.bold(
contextName
)}\n`
);
output.print(` ${tbl}\n`);
return confirm(`${chalk.bold.red('Are you sure?')}`, false);
}

View File

@@ -57,7 +57,7 @@ export default async function({ apiUrl, token, teams, config }) {
validateKeypress: validateSlugKeypress,
initialValue: slug,
valid: team,
forceLowerCase: true
forceLowerCase: true,
});
} catch (err) {
if (err.message === 'USER_ABORT') {
@@ -95,7 +95,7 @@ export default async function({ apiUrl, token, teams, config }) {
try {
name = await textInput({
label: `- ${teamNamePrefix}`,
validateKeypress: validateNameKeypress
validateKeypress: validateNameKeypress,
});
} catch (err) {
if (err.message === 'USER_ABORT') {
@@ -153,7 +153,7 @@ export default async function({ apiUrl, token, teams, config }) {
introMsg: 'Invite your teammates! When done, press enter on an empty field',
noopMsg: `You can invite teammates later by running ${cmd(
'now teams invite'
)}`
)}`,
});
gracefulExit();

View File

@@ -30,7 +30,7 @@ const domains = Array.from(
'inbox.com',
'mail.com',
'gmx.com',
'icloud.com'
'icloud.com',
])
);
@@ -56,17 +56,15 @@ const emailAutoComplete = (value, teamSlug) => {
return false;
};
export default async function(
{
teams,
args,
config,
introMsg,
noopMsg = 'No changes made',
apiUrl,
token
} = {}
) {
export default async function({
teams,
args,
config,
introMsg,
noopMsg = 'No changes made',
apiUrl,
token,
} = {}) {
const { currentTeam: currentTeamId } = config;
const stopSpinner = wait('Fetching teams');
@@ -86,7 +84,11 @@ export default async function(
if (!currentTeam) {
// We specifically need a team scope here
let err = `You can't run this command under ${param(user.username || user.email)}.\nPlease select a team scope using ${cmd('now switch')} or use ${cmd('--scope')}`;
let err = `You can't run this command under ${param(
user.username || user.email
)}.\nPlease select a team scope using ${cmd('now switch')} or use ${cmd(
'--scope'
)}`;
return fatalError(err);
}
@@ -107,7 +109,9 @@ export default async function(
userInfo = res.name || res.username;
} catch (err) {
if (err.code === 'user_not_found') {
console.error(error(`No user exists with the email address "${email}".`));
console.error(
error(`No user exists with the email address "${email}".`)
);
return 1;
}
@@ -115,7 +119,11 @@ export default async function(
}
stopSpinner();
console.log(`${chalk.cyan(chars.tick)} ${email}${userInfo ? ` (${userInfo})` : ''} ${elapsed()}`);
console.log(
`${chalk.cyan(chars.tick)} ${email}${
userInfo ? ` (${userInfo})` : ''
} ${elapsed()}`
);
} else {
console.log(`${chalk.red(`${email}`)} ${chalk.gray('[invalid]')}`);
}
@@ -135,7 +143,7 @@ export default async function(
email = await textInput({
label: `- ${inviteUserPrefix}`,
validateValue: validateEmail,
autoComplete: value => emailAutoComplete(value, currentTeam.slug)
autoComplete: value => emailAutoComplete(value, currentTeam.slug),
});
} catch (err) {
if (err.message !== 'USER_ABORT') {
@@ -149,7 +157,10 @@ export default async function(
stopSpinner = wait(inviteUserPrefix + email);
try {
// eslint-disable-next-line no-await-in-loop
const { name, username } = await teams.inviteUser({ teamId: currentTeam.id, email });
const { name, username } = await teams.inviteUser({
teamId: currentTeam.id,
email,
});
stopSpinner();
const userInfo = name || username;
email = `${email}${userInfo ? ` (${userInfo})` : ''} ${elapsed()}`;

View File

@@ -27,20 +27,20 @@ export default async function({ teams, config, apiUrl, token }) {
if (accountIsCurrent) {
currentTeam = {
slug: user.username || user.email
slug: user.username || user.email,
};
}
const teamList = list.map(({ slug, name }) => ({
name,
value: slug,
current: slug === currentTeam.slug ? chars.tick : ''
current: slug === currentTeam.slug ? chars.tick : '',
}));
teamList.unshift({
name: user.email,
value: user.username || user.email,
current: (accountIsCurrent && chars.tick) || ''
current: (accountIsCurrent && chars.tick) || '',
});
// Let's bring the current team to the beginning of the list

View File

@@ -224,6 +224,7 @@ export interface Project {
createdAt: number;
devCommand?: string | null;
framework?: string | null;
rootDirectory?: string | null;
}
export interface Org {

View File

@@ -3,7 +3,6 @@ import { Output } from '../output';
import * as ERRORS from '../errors-ts';
import Client from '../client';
import createCertForAlias from '../certs/create-cert-for-alias';
import wait from '../output/wait';
export type AliasRecord = {
uid: string;
@@ -20,7 +19,7 @@ export default async function createAlias(
alias: string,
externalDomain: boolean
) {
let cancelMessage = wait(`Creating alias`);
let cancelMessage = output.spinner(`Creating alias`);
const result = await performCreateAlias(
client,
contextName,
@@ -41,7 +40,7 @@ export default async function createAlias(
return cert;
}
let cancelMessage = wait(`Creating alias`);
let cancelMessage = output.spinner(`Creating alias`);
const secondTry = await performCreateAlias(
client,
contextName,
@@ -66,7 +65,7 @@ async function performCreateAlias(
`/now/deployments/${deployment.uid}/aliases`,
{
method: 'POST',
body: { alias }
body: { alias },
}
);
} catch (error) {
@@ -77,7 +76,10 @@ async function performCreateAlias(
return { uid: error.uid, alias: error.alias } as AliasRecord;
}
if (error.code === 'deployment_not_found') {
return new ERRORS.DeploymentNotFound({ context: contextName, id: deployment.uid });
return new ERRORS.DeploymentNotFound({
context: contextName,
id: deployment.uid,
});
}
if (error.code === 'gone') {
return new ERRORS.DeploymentFailedAliasImpossible();
@@ -94,7 +96,7 @@ async function performCreateAlias(
}
}
if (error.status === 400) {
return new ERRORS.DeploymentNotReady({url: deployment.url })
return new ERRORS.DeploymentNotReady({ url: deployment.url });
}
throw error;

View File

@@ -3,7 +3,6 @@ import chalk from 'chalk';
import getAppLastDeployment from '../deploy/get-app-last-deployment';
import getAppName from '../deploy/get-app-name';
import fetchDeploymentByIdOrHost from '../deploy/get-deployment-by-id-or-host';
import wait from '../output/wait';
import Client from '../client';
import { Output } from '../output';
import { User, Config } from '../../types';
@@ -17,7 +16,7 @@ export default async function getDeploymentForAlias(
contextName: string,
localConfig: Config
) {
const cancelWait = wait(
const cancelWait = output.spinner(
`Fetching deployment to alias in ${chalk.bold(contextName)}`
);

View File

@@ -4,7 +4,6 @@ import * as ERRORS from '../errors-ts';
import Client from '../client';
import createCertForAlias from '../certs/create-cert-for-alias';
import setupDomain from '../domains/setup-domain';
import wait from '../output/wait';
const NOW_SH_REGEX = /\.now\.sh$/;
@@ -34,6 +33,7 @@ export default async function upsertPathAlias(
}
const result = await performUpsertPathAlias(
output,
client,
alias,
rules,
@@ -51,23 +51,26 @@ export default async function upsertPathAlias(
return cert;
}
return performUpsertPathAlias(client, alias, rules, contextName);
return performUpsertPathAlias(output, client, alias, rules, contextName);
}
return result;
}
async function performUpsertPathAlias(
output: Output,
client: Client,
alias: string,
rules: PathRule[],
contextName: string
) {
const cancelMessage = wait(`Updating path alias rules for ${alias}`);
const cancelMessage = output.spinner(
`Updating path alias rules for ${alias}`
);
try {
const record = await client.fetch<AliasRecord>(`/now/aliases`, {
body: { alias, rules },
method: 'POST'
method: 'POST',
});
cancelMessage();
return record;

View File

@@ -5,7 +5,6 @@ import createCertForCns from './create-cert-for-cns';
import getWildcardCnsForAlias from './get-wildcard-cns-for-alias';
import joinWords from '../output/join-words';
import stamp from '../output/stamp';
import wait from '../output/wait';
export default async function createCertificateForAlias(
output: Output,
@@ -15,7 +14,7 @@ export default async function createCertificateForAlias(
shouldBeWildcard: boolean
) {
const cns = shouldBeWildcard ? getWildcardCnsForAlias(alias) : [alias];
const cancelMessage = wait(`Generating a certificate...`);
const cancelMessage = output.spinner(`Generating a certificate...`);
const certStamp = stamp();
const cert = await createCertForCns(client, cns, context);
if (cert instanceof NowError) {
@@ -25,9 +24,9 @@ export default async function createCertificateForAlias(
cancelMessage();
output.log(
`Certificate for ${joinWords(
cert.cns
)} (${cert.uid}) created ${certStamp()}`
`Certificate for ${joinWords(cert.cns)} (${
cert.uid
}) created ${certStamp()}`
);
return cert;
}

View File

@@ -11,10 +11,11 @@ export default async function createDeploy(
paths,
createArgs,
org,
isSettingUpProject
isSettingUpProject,
cwd
) {
try {
return await now.create(paths, createArgs, org, isSettingUpProject);
return await now.create(paths, createArgs, org, isSettingUpProject, cwd);
} catch (error) {
if (error.code === 'rate_limited') {
throw new ERRORS_TS.DeploymentsRateLimited(error.message);

View File

@@ -4,7 +4,6 @@ import { Output } from '../output';
import Client from '../client';
import createCertForCns from '../certs/create-cert-for-cns';
import setupDomain from '../domains/setup-domain';
import wait from '../output/wait';
import { InvalidDomain } from '../errors-ts';
export default async function generateCertForDeploy(
@@ -23,7 +22,9 @@ export default async function generateCertForDeploy(
return new InvalidDomain(deployURL);
}
const cancelSetupWait = wait(`Setting custom suffix domain ${domain}`);
const cancelSetupWait = output.spinner(
`Setting custom suffix domain ${domain}`
);
const result = await setupDomain(output, client, domain, contextName);
cancelSetupWait();
if (result instanceof NowError) {
@@ -31,7 +32,7 @@ export default async function generateCertForDeploy(
}
// Generate the certificate with the given parameters
const cancelCertWait = wait(
const cancelCertWait = output.spinner(
`Generating a wildcard certificate for ${domain}`
);
const cert = await createCertForCns(

View File

@@ -6,7 +6,6 @@ import {
DeploymentOptions,
NowClientOptions,
} from 'now-client';
import wait from '../output/wait';
import { Output } from '../output';
// @ts-ignore
import Now from '../../util';
@@ -50,6 +49,7 @@ function printInspectUrl(
export default async function processDeployment({
isLegacy,
org,
cwd,
projectName,
isSettingUpProject,
skipAutoDetectionConfirmation,
@@ -70,6 +70,7 @@ export default async function processDeployment({
projectName: string;
isSettingUpProject: boolean;
skipAutoDetectionConfirmation?: boolean;
cwd?: string;
}) {
if (isLegacy) return processLegacyDeployment(args);
@@ -82,6 +83,7 @@ export default async function processDeployment({
deployStamp,
force,
nowConfig,
quiet,
} = args;
const { debug } = output;
@@ -104,7 +106,7 @@ export default async function processDeployment({
let buildSpinner = null;
let deploySpinner = null;
let deployingSpinner = wait(
let deployingSpinner = output.spinner(
isSettingUpProject
? `Setting up project`
: `Deploying ${chalk.bold(`${org.slug}/${projectName}`)}`,
@@ -167,7 +169,7 @@ export default async function processDeployment({
await linkFolderToProject(
output,
paths[0],
cwd || paths[0],
{
orgId: org.id,
projectId: event.payload.projectId,
@@ -178,11 +180,15 @@ export default async function processDeployment({
printInspectUrl(output, event.payload.url, deployStamp, org.slug);
if (quiet) {
process.stdout.write(`https://${event.payload.url}`);
}
if (queuedSpinner === null) {
queuedSpinner =
event.payload.readyState === 'QUEUED'
? wait('Queued', 0)
: wait('Building', 0);
? output.spinner('Queued', 0)
: output.spinner('Building', 0);
}
}
@@ -192,7 +198,7 @@ export default async function processDeployment({
}
if (buildSpinner === null) {
buildSpinner = wait('Building', 0);
buildSpinner = output.spinner('Building', 0);
}
}
@@ -204,7 +210,7 @@ export default async function processDeployment({
buildSpinner();
}
deploySpinner = wait('Completing', 0);
deploySpinner = output.spinner('Completing', 0);
}
// Handle error events

View File

@@ -20,7 +20,6 @@ import {
import pkg from '../../../package.json';
import { NoBuilderCacheError } from '../errors-ts';
import wait from '../output/wait';
import { Output } from '../output';
import { getDistTag } from '../get-dist-tag';
@@ -246,7 +245,7 @@ export async function installBuilders(
return;
}
const stopSpinner = wait(
const stopSpinner = output.spinner(
`Installing ${pluralize(
'Runtime',
packagesToInstall.length

View File

@@ -3,19 +3,10 @@
import ms from 'ms';
import bytes from 'bytes';
import { promisify } from 'util';
import { delimiter, dirname, extname, join } from 'path';
import { delimiter, dirname, join } from 'path';
import { fork, ChildProcess } from 'child_process';
import { createFunction } from '@zeit/fun';
import {
Builder,
File,
Lambda,
FileBlob,
FileFsRef,
detectApiDirectory,
detectApiExtensions,
} from '@now/build-utils';
import which from 'which';
import { Builder, File, Lambda, FileBlob, FileFsRef } from '@now/build-utils';
import plural from 'pluralize';
import minimatch from 'minimatch';
import _treeKill from 'tree-kill';
@@ -52,12 +43,6 @@ interface BuildMessageResult extends BuildMessage {
const treeKill = promisify(_treeKill);
let nodeBinPromise: Promise<string>;
async function getNodeBin(): Promise<string> {
return which.sync('node', { nothrow: true }) || process.execPath;
}
async function createBuildProcess(
match: BuildMatch,
buildEnv: EnvConfig,
@@ -65,13 +50,8 @@ async function createBuildProcess(
output: Output,
yarnPath?: string
): Promise<ChildProcess> {
if (!nodeBinPromise) {
nodeBinPromise = getNodeBin();
}
const [execPath, modulePath] = await Promise.all([
nodeBinPromise,
builderModulePathPromise,
]);
const { execPath } = process;
const modulePath = await builderModulePathPromise;
// Ensure that `node` is in the builder's `PATH`
let PATH = `${dirname(execPath)}${delimiter}${process.env.PATH}`;
@@ -128,7 +108,7 @@ export async function executeBuild(
const {
builderWithPkg: { runInProcess, builder, package: pkg },
} = match;
const { src: entrypoint } = match;
const { entrypoint } = match;
const { env, debug, buildEnv, yarnPath, cwd: workPath } = devServer;
const startTime = Date.now();
@@ -282,8 +262,8 @@ export async function executeBuild(
}
const { output } = result;
const { cleanUrls } = nowConfig;
// Mimic fmeta-util and perform file renaming
Object.entries(output).forEach(([path, value]) => {
if (cleanUrls && path.endsWith('.html')) {
@@ -294,6 +274,11 @@ export async function executeBuild(
}
}
const extensionless = devServer.getExtensionlessFile(path);
if (extensionless) {
path = extensionless;
}
delete output[path];
output[path] = value;
});
@@ -403,6 +388,7 @@ export async function getBuildMatches(
cwd: string,
yarnDir: string,
output: Output,
devServer: DevServer,
fileList: string[]
): Promise<BuildMatch[]> {
const matches: BuildMatch[] = [];
@@ -415,9 +401,6 @@ export async function getBuildMatches(
const noMatches: Builder[] = [];
const builds = nowConfig.builds || [{ src: '**', use: '@now/static' }];
const apiDir = detectApiDirectory(builds || []);
const apiExtensions = detectApiExtensions(builds || []);
const apiMatch = apiDir + '/';
for (const buildConfig of builds) {
let { src, use } = buildConfig;
@@ -436,10 +419,13 @@ export async function getBuildMatches(
// We need to escape brackets since `glob` will
// try to find a group otherwise
src = src.replace(/(\[|\])/g, '[$1]');
const ext = extname(src);
if (apiDir && src.startsWith(apiMatch) && apiExtensions.has(ext)) {
// lambda function files are trimmed of their file extension
src = src.slice(0, -ext.length);
// lambda function files are trimmed of their file extension
const mapToEntrypoint = new Map<string, string>();
const extensionless = devServer.getExtensionlessFile(src);
if (extensionless) {
mapToEntrypoint.set(extensionless, src);
src = extensionless;
}
const files = fileList
@@ -456,6 +442,7 @@ export async function getBuildMatches(
matches.push({
...buildConfig,
src,
entrypoint: mapToEntrypoint.get(src) || src,
builderWithPkg,
buildOutput: {},
buildResults: new Map(),

View File

@@ -11,22 +11,21 @@ import { randomBytes } from 'crypto';
import serveHandler from 'serve-handler';
import { watch, FSWatcher } from 'chokidar';
import { parse as parseDotenv } from 'dotenv';
import { basename, dirname, extname, join, delimiter } from 'path';
import { basename, dirname, extname, join } from 'path';
import { getTransformedRoutes, HandleValue } from '@now/routing-utils';
import directoryTemplate from 'serve-handler/src/directory';
import getPort from 'get-port';
import { ChildProcess } from 'child_process';
import isPortReachable from 'is-port-reachable';
import which from 'which';
import {
Builder,
FileFsRef,
PackageJson,
detectBuilders,
detectRoutes,
detectApiDirectory,
detectApiExtensions,
execAsync,
spawnCommand,
} from '@now/build-utils';
@@ -112,6 +111,8 @@ export default class DevServer {
public address: string;
private cachedNowConfig: NowConfig | null;
private apiDir: string | null;
private apiExtensions: Set<string>;
private server: http.Server;
private stopping: boolean;
private serverUrlPrinted: boolean;
@@ -145,6 +146,8 @@ export default class DevServer {
this.yarnPath = '/';
this.cachedNowConfig = null;
this.apiDir = null;
this.apiExtensions = new Set<string>();
this.server = http.createServer(this.devServerHandler);
this.server.timeout = 0; // Disable timeout
this.serverUrlPrinted = false;
@@ -291,6 +294,10 @@ export default class DevServer {
const name = relative(this.cwd, fsPath);
try {
this.files[name] = await FileFsRef.fromFsPath({ fsPath });
const extensionless = this.getExtensionlessFile(name);
if (extensionless) {
this.files[extensionless] = await FileFsRef.fromFsPath({ fsPath });
}
fileChanged(name, changed, removed);
this.output.debug(`File created: ${name}`);
} catch (err) {
@@ -311,6 +318,11 @@ export default class DevServer {
const name = relative(this.cwd, fsPath);
this.output.debug(`File deleted: ${name}`);
fileRemoved(name, this.files, changed, removed);
const extensionless = this.getExtensionlessFile(name);
if (extensionless) {
this.output.debug(`File deleted: ${extensionless}`);
fileRemoved(extensionless, this.files, changed, removed);
}
}
async handleFileModified(
@@ -343,6 +355,7 @@ export default class DevServer {
this.cwd,
this.yarnPath,
this.output,
this,
fileList
);
const sources = matches.map(m => m.src);
@@ -534,10 +547,19 @@ export default class DevServer {
const featHandleMiss = true; // enable for zero config
const { projectSettings, cleanUrls, trailingSlash } = config;
let { builders, warnings, errors } = await detectBuilders(files, pkg, {
let {
builders,
warnings,
errors,
defaultRoutes,
redirectRoutes,
} = await detectBuilders(files, pkg, {
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest',
functions: config.functions,
...(projectSettings ? { projectSettings } : {}),
featHandleMiss,
cleanUrls,
trailingSlash,
});
if (errors) {
@@ -554,32 +576,15 @@ export default class DevServer {
builders = builders.filter(filterFrontendBuilds);
}
const {
defaultRoutes,
redirectRoutes,
error: routesError,
} = await detectRoutes(
files,
builders,
featHandleMiss,
cleanUrls,
trailingSlash
);
config.builds = config.builds || [];
config.builds.push(...builders);
if (routesError) {
this.output.error(routesError.message);
await this.exit();
} else {
const routes: RouteConfig[] = [];
const { routes: nowConfigRoutes } = config;
routes.push(...(redirectRoutes || []));
routes.push(...(nowConfigRoutes || []));
routes.push(...(defaultRoutes || []));
config.routes = routes;
}
const routes: RouteConfig[] = [];
const { routes: nowConfigRoutes } = config;
routes.push(...(redirectRoutes || []));
routes.push(...(nowConfigRoutes || []));
routes.push(...(defaultRoutes || []));
config.routes = routes;
}
}
@@ -600,6 +605,8 @@ export default class DevServer {
await this.validateNowConfig(config);
this.cachedNowConfig = config;
this.apiDir = detectApiDirectory(config.builds || []);
this.apiExtensions = detectApiExtensions(config.builds || []);
return config;
}
@@ -739,26 +746,21 @@ export default class DevServer {
const opts = { output: this.output, isBuilds: true };
const files = await getFiles(this.cwd, nowConfig, opts);
const results: { [filePath: string]: FileFsRef } = {};
const apiDir = detectApiDirectory(nowConfig.builds || []);
const apiExtensions = detectApiExtensions(nowConfig.builds || []);
const apiMatch = apiDir + '/';
this.files = {};
for (const fsPath of files) {
let path = relative(this.cwd, fsPath);
const { mode } = await fs.stat(fsPath);
const ext = extname(path);
if (apiDir && path.startsWith(apiMatch) && apiExtensions.has(ext)) {
// lambda function files are trimmed of their file extension
path = path.slice(0, -ext.length);
this.files[path] = new FileFsRef({ mode, fsPath });
const extensionless = this.getExtensionlessFile(path);
if (extensionless) {
this.files[extensionless] = new FileFsRef({ mode, fsPath });
}
results[path] = new FileFsRef({ mode, fsPath });
}
this.files = results;
const builders: Set<string> = new Set(
const builders = new Set<string>(
(nowConfig.builds || [])
.filter((b: Builder) => b.use)
.map((b: Builder) => b.use as string)
.map((b: Builder) => b.use)
);
await installBuilders(builders, this.yarnPath, this.output);
@@ -1085,7 +1087,9 @@ export default class DevServer {
// If the requested asset wasn't found in the match's
// outputs then trigger a build
const buildKey =
requestPath === null ? match.src : `${match.src}-${requestPath}`;
requestPath === null
? match.entrypoint
: `${match.entrypoint}-${requestPath}`;
let buildPromise = this.inProgressBuilds.get(buildKey);
if (buildPromise) {
// A build for `buildKey` is already in progress, so don't trigger
@@ -1127,6 +1131,19 @@ export default class DevServer {
}
}
getExtensionlessFile = (path: string) => {
const ext = extname(path);
if (
this.apiDir &&
path.startsWith(this.apiDir + '/') &&
this.apiExtensions.has(ext)
) {
// lambda function files are trimmed of their file extension
return path.slice(0, -ext.length);
}
return null;
};
/**
* DevServer HTTP handler
*/
@@ -1606,18 +1623,14 @@ export default class DevServer {
}
async runDevCommand() {
if (!this.devCommand) return;
const { devCommand, cwd } = this;
const cwd = this.cwd;
const { stdout: yarnBinStdout } = await execAsync('yarn', ['bin'], {
cwd,
});
const yarnBinPath = yarnBinStdout.trim();
if (!devCommand) {
return;
}
this.output.log(
`Running Dev Command ${chalk.cyan.bold(`${this.devCommand}`)}`
`Running Dev Command ${chalk.cyan.bold(`${devCommand}`)}`
);
const port = await getPort();
@@ -1625,24 +1638,43 @@ export default class DevServer {
const env: EnvConfig = {
...process.env,
...this.buildEnv,
PATH: `${yarnBinPath}${delimiter}${process.env.PATH}`,
NOW_REGION: 'dev1',
PORT: `${port}`,
};
// This is necesary so that the dev command in the Project
// will work cross-platform (especially Windows).
let command = devCommand
.replace(/\$PORT/g, `${port}`)
.replace(/%PORT%/g, `${port}`);
this.output.debug(
`Starting dev command with parameters : ${JSON.stringify({
cwd: this.cwd,
devCommand: this.devCommand,
cwd,
command,
port,
})}`
);
const p = spawnCommand(this.devCommand, {
stdio: 'inherit',
cwd,
env,
});
const isNpxAvailable = await which('npx')
.then(() => true)
.catch(() => false);
if (isNpxAvailable) {
command = `npx --no-install ${command}`;
} else {
const isYarnAvailable = await which('yarn')
.then(() => true)
.catch(() => false);
if (isYarnAvailable) {
command = `yarn run --silent ${command}`;
}
}
this.output.debug(`Spawning dev command: ${command}`);
const p = spawnCommand(command, { stdio: 'inherit', cwd, env });
p.on('exit', () => {
this.devProcessPort = undefined;

View File

@@ -26,6 +26,7 @@ export interface EnvConfig {
}
export interface BuildMatch extends BuildConfig {
entrypoint: string;
builderWithPkg: BuilderWithPackage;
buildOutput: BuilderOutputs;
buildResults: Map<string | null, BuildResult>;

View File

@@ -9,7 +9,6 @@ import getDomainStatus from './get-domain-status';
import promptBool from '../input/prompt-bool';
import purchaseDomain from './purchase-domain';
import stamp from '../output/stamp';
import wait from '../output/wait';
import * as ERRORS from '../errors-ts';
const isTTY = process.stdout.isTTY;
@@ -20,7 +19,7 @@ export default async function purchaseDomainIfAvailable(
domain: string,
contextName: string
) {
const cancelWait = wait(`Checking status of ${chalk.bold(domain)}`);
const cancelWait = output.spinner(`Checking status of ${chalk.bold(domain)}`);
const buyDomainStamp = stamp();
const { available } = await getDomainStatus(client, domain);

View File

@@ -1,6 +1,8 @@
import Client from './client';
import { APIError, InvalidToken } from './errors-ts';
import { Team } from '../types';
// @ts-ignore
import NowTeams from './teams.js';
let teams: Team[] | undefined;
@@ -8,10 +10,15 @@ export default async function getTeams(client: Client) {
if (teams) return teams;
try {
const res = await client.fetch<{ teams: Team[] }>('/teams');
// we're using NowTeams because `client.fetch` hangs on windows
const teamClient = new NowTeams({
apiUrl: client._apiUrl,
token: client._token,
debug: client._debug,
});
teams = res.teams;
return teams;
const teams = (await teamClient.ls()).teams;
return teams as Team[];
} catch (error) {
if (error instanceof APIError && error.status === 403) {
throw new InvalidToken();

View File

@@ -70,7 +70,8 @@ export default class Now extends EventEmitter {
skipAutoDetectionConfirmation,
},
org,
isSettingUpProject
isSettingUpProject,
cwd
) {
const opts = { output: this._output, hasNowJson };
const { log, warn } = this._output;
@@ -183,6 +184,7 @@ export default class Now extends EventEmitter {
projectName: name,
isSettingUpProject,
skipAutoDetectionConfirmation,
cwd,
});
// We report about files whose sizes are too big

View File

@@ -11,6 +11,10 @@ export interface ProjectSettings {
devCommand: string | null;
}
export interface ProjectSettingsWithFramework extends ProjectSettings {
framework: string | null;
}
const fields: { name: string; value: keyof ProjectSettings }[] = [
{ name: 'Build Command', value: 'buildCommand' },
{ name: 'Output Directory', value: 'outputDirectory' },
@@ -22,22 +26,30 @@ export default async function editProjectSettings(
projectSettings: ProjectSettings | null,
framework: Framework | null
) {
// create new settings object filled with "null" values
const settings: Partial<ProjectSettings> = {};
// create new settings object, missing values will be filled with `null`
const settings: Partial<ProjectSettingsWithFramework> = {
...projectSettings,
};
for (let field of fields) {
settings[field.value] =
(projectSettings && projectSettings[field.value]) || null;
}
// skip editing project settings if no framework is detected
if (!framework) {
settings.framework = null;
return settings;
}
output.print(
`Auto-detected project settings (${chalk.bold(framework.name)}):\n`
!framework.slug
? `No framework detected. Default project settings:\n`
: `Auto-detected project settings (${chalk.bold(framework.name)}):\n`
);
settings.framework = framework.slug;
for (let field of fields) {
const defaults = framework.settings[field.value];

View File

@@ -6,7 +6,7 @@ import chalk from 'chalk';
import { ProjectNotFound } from '../../util/errors-ts';
import { Output } from '../output';
import { Project, Org } from '../../types';
import wait from '../output/wait';
import slugify from '@sindresorhus/slugify';
export default async function inputProject(
output: Output,
@@ -19,16 +19,26 @@ export default async function inputProject(
return detectedProjectName;
}
const slugifiedName = slugify(detectedProjectName);
// attempt to auto-detect a project to link
let detectedProject = null;
const existingProjectSpinner = wait('Searching for existing projects…', 1000);
const existingProjectSpinner = output.spinner(
'Searching for existing projects…',
1000
);
try {
const project = await getProjectByIdOrName(
client,
detectedProjectName,
org.id
);
detectedProject = project instanceof ProjectNotFound ? null : project;
const [project, slugifiedProject] = await Promise.all([
getProjectByIdOrName(client, detectedProjectName, org.id),
slugifiedName !== detectedProjectName
? getProjectByIdOrName(client, slugifiedName, org.id)
: null,
]);
detectedProject = !(project instanceof ProjectNotFound)
? project
: !(slugifiedProject instanceof ProjectNotFound)
? slugifiedProject
: null;
} catch (error) {}
existingProjectSpinner();
@@ -42,7 +52,7 @@ export default async function inputProject(
if (
await confirm(
`Found project ${chalk.cyan(
`${org.slug}/${detectedProjectName}`
`${org.slug}/${detectedProject.name}`
)}. Link to it?`,
true
)
@@ -74,7 +84,7 @@ export default async function inputProject(
continue;
}
const spinner = wait('Verifying project name…', 1000);
const spinner = output.spinner('Verifying project name…', 1000);
try {
project = await getProjectByIdOrName(client, projectName, org.id);
} finally {
@@ -97,7 +107,7 @@ export default async function inputProject(
type: 'input',
name: 'newProjectName',
message: `Whats your projects name?`,
default: !detectedProject ? detectedProjectName : undefined,
default: !detectedProject ? slugifiedName : undefined,
});
newProjectName = answers.newProjectName as string;
@@ -106,7 +116,7 @@ export default async function inputProject(
continue;
}
const spinner = wait('Verifying project name…', 1000);
const spinner = output.spinner('Verifying project name…', 1000);
let existingProject: Project | ProjectNotFound;
try {
existingProject = await getProjectByIdOrName(

View File

@@ -0,0 +1,54 @@
import path from 'path';
import chalk from 'chalk';
import inquirer from 'inquirer';
import { Output } from '../output';
import { validateRootDirectory } from '../validate-paths';
export async function inputRootDirectory(
cwd: string,
output: Output,
autoConfirm: boolean
) {
if (autoConfirm) {
return null;
}
const basename = path.basename(cwd);
// eslint-disable-next-line no-constant-condition
while (true) {
const { rootDirectory } = await inquirer.prompt({
type: 'input',
name: 'rootDirectory',
message: `In which directory is your code located?`,
transformer: (input: string) => {
return `${chalk.dim(`${basename}/`)}${input}`;
},
});
if (!rootDirectory) {
return null;
}
const normal = path.normalize(rootDirectory);
if (normal === '.' || normal === './') {
return null;
}
const fullPath = path.join(cwd, normal);
if (
(await validateRootDirectory(
output,
cwd,
fullPath,
'Please choose a different one.'
)) === false
) {
continue;
}
return normal;
}
}

View File

@@ -3,11 +3,12 @@ import inquirer from 'inquirer';
import getUser from '../get-user';
import getTeams from '../get-teams';
import { User, Team, Org } from '../../types';
import wait from '../output/wait';
import { Output } from '../output';
type Choice = { name: string; value: Org };
export default async function selectProject(
export default async function selectOrg(
output: Output,
question: string,
client: Client,
currentTeam?: string,
@@ -15,7 +16,7 @@ export default async function selectProject(
): Promise<Org> {
require('./patch-inquirer');
const spinner = wait('Loading scopes…', 1000);
const spinner = output.spinner('Loading scopes…', 1000);
let user: User;
let teams: Team[];
try {

View File

@@ -2,6 +2,7 @@ import chalk from 'chalk';
import boxen from 'boxen';
import { format } from 'util';
import { Console } from 'console';
import wait from './wait';
export type Output = ReturnType<typeof createOutput>;
@@ -76,6 +77,20 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
}
}
function spinner(message: string, delay: number = 300) {
if (debugEnabled) {
debug(`Spinner invoked (${message}) with a ${delay}ms delay`);
let isEnded = false;
return () => {
if (isEnded) return;
isEnded = true;
debug(`Spinner ended (${message})`);
};
}
return wait(message, delay);
}
// This is pretty hacky, but since we control the version of Node.js
// being used because of `pkg` it's safe to do in this case.
const c = {
@@ -109,5 +124,6 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
dim,
time,
note,
spinner,
};
}

View File

@@ -2,7 +2,7 @@ import ora from 'ora';
import chalk from 'chalk';
import eraseLines from './erase-lines';
export default function wait(msg: string, timeout: number = 300, _ora = ora) {
export default function wait(msg: string, delay: number = 300, _ora = ora) {
let spinner: ReturnType<typeof _ora>;
let running = false;
@@ -11,7 +11,7 @@ export default function wait(msg: string, timeout: number = 300, _ora = ora) {
spinner.color = 'gray';
spinner.start();
running = true;
}, timeout);
}, delay);
const cancel = () => {
clearTimeout(planned);

View File

@@ -12,7 +12,6 @@ import { Project } from '../../types';
import { Org, ProjectLink } from '../../types';
import chalk from 'chalk';
import { prependEmoji, emoji } from '../emoji';
import wait from '../output/wait';
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
@@ -86,7 +85,7 @@ export async function getLinkedOrg(
return { status: 'not_linked', org: null };
}
const spinner = wait('Retrieving scope…', 1000);
const spinner = output.spinner('Retrieving scope…', 1000);
try {
const org = await getOrgById(client, orgId);
@@ -141,7 +140,7 @@ export async function getLinkedProject(
return { status: 'not_linked', org: null, project: null };
}
const spinner = wait('Retrieving project…', 1000);
const spinner = output.spinner('Retrieving project…', 1000);
let org: Org | null;
let project: Project | ProjectNotFound | null;
try {

View File

@@ -1,5 +1,4 @@
import chalk from 'chalk';
import wait from '../output/wait';
import joinWords from '../output/join-words';
import * as Errors from '../errors-ts';
import { Output } from '../output';
@@ -17,7 +16,7 @@ export default async function patchDeploymentScale(
scaleArgs: ScaleArgs,
url: string
) {
const cancelWait = wait(
const cancelWait = output.spinner(
`Setting scale rules for ${joinWords(
Object.keys(scaleArgs).map(dc => `${chalk.bold(dc)}`)
)}`
@@ -28,7 +27,7 @@ export default async function patchDeploymentScale(
`/v3/now/deployments/${encodeURIComponent(deploymentId)}/instances`,
{
method: 'PATCH',
body: scaleArgs
body: scaleArgs,
}
);
cancelWait();

View File

@@ -4,7 +4,6 @@ import { Output } from '../output';
import * as ERRORS from '../errors-ts';
import Client from '../client';
import joinWords from '../output/join-words';
import wait from '../output/wait';
type ScaleArgs = {
min: number;
@@ -18,7 +17,7 @@ export default async function setScale(
scaleArgs: ScaleArgs | DeploymentScale,
url: string
) {
const cancelWait = wait(
const cancelWait = output.spinner(
`Setting scale rules for ${joinWords(
Object.keys(scaleArgs).map(dc => `${chalk.bold(dc)}`)
)}`
@@ -29,7 +28,7 @@ export default async function setScale(
`/v3/now/deployments/${encodeURIComponent(deploymentId)}/instances`,
{
method: 'PUT',
body: scaleArgs
body: scaleArgs,
}
);
cancelWait();

View File

@@ -9,7 +9,6 @@ import Client from '../client';
import joinWords from '../output/join-words';
import stamp from '../output/stamp';
import verifyDeploymentScale from './verify-deployment-scale';
import wait from '../output/wait';
export default async function waitForScale(
output: Output,
@@ -19,7 +18,7 @@ export default async function waitForScale(
) {
const remainingDCs = new Set(Object.keys(scale));
const scaleStamp = stamp();
let cancelWait = renderWaitDcs(Array.from(remainingDCs.keys()));
let cancelWait = renderWaitDcs(output, Array.from(remainingDCs.keys()));
for await (const dcReady of verifyDeploymentScale(
output,
@@ -43,13 +42,13 @@ export default async function waitForScale(
}
if (remainingDCs.size > 0) {
cancelWait = renderWaitDcs(Array.from(remainingDCs.keys()));
cancelWait = renderWaitDcs(output, Array.from(remainingDCs.keys()));
}
}
}
function renderWaitDcs(dcs: string[]) {
return wait(
function renderWaitDcs(output: Output, dcs: string[]) {
return output.spinner(
`Waiting for instances in ${joinWords(
dcs.map(dc => chalk.bold(dc))
)} to be ready`

View File

@@ -9,6 +9,48 @@ import toHumanPath from './humanize-path';
const stat = promisify(lstatRaw);
/**
* A helper function to validate the `rootDirectory` input.
*/
export async function validateRootDirectory(
output: Output,
cwd: string,
path: string,
errorSuffix: string
) {
const pathStat = await stat(path).catch(() => null);
const suffix = errorSuffix ? ` ${errorSuffix}` : '';
if (!pathStat) {
output.print(
`${chalk.red('Error!')} The provided path ${chalk.cyan(
`${toHumanPath(path)}`
)} does not exist.${suffix}\n`
);
return false;
}
if (!pathStat.isDirectory()) {
output.print(
`${chalk.red('Error!')} The provided path ${chalk.cyan(
`${toHumanPath(path)}`
)} is a file, but expected a directory.${suffix}\n`
);
return false;
}
if (!path.startsWith(cwd)) {
output.print(
`${chalk.red('Error!')} The provided path ${chalk.cyan(
`${toHumanPath(path)}`
)} is outside of the project.${suffix}\n`
);
return false;
}
return true;
}
export default async function validatePaths(
output: Output,
paths: string[]
@@ -25,10 +67,7 @@ export default async function validatePaths(
const path = paths[0];
// can only deploy a directory
let pathStat;
try {
pathStat = await stat(path);
} catch (error) {}
const pathStat = await stat(path).catch(() => null);
if (!pathStat) {
output.print(

View File

@@ -5,7 +5,7 @@ import execa from 'execa';
import fs from 'fs-extra';
import fetch from 'node-fetch';
import listen from 'async-listen';
import { request, createServer } from 'http';
import { createServer } from 'http';
import createOutput from '../src/util/output';
import DevServer from '../src/util/dev/server';
import { installBuilders, getBuildUtils } from '../src/util/dev/builder-cache';
@@ -13,12 +13,28 @@ import parseListen from '../src/util/dev/parse-listen';
async function runNpmInstall(fixturePath) {
if (await fs.exists(path.join(fixturePath, 'package.json'))) {
return execa('yarn', ['install'], { cwd: fixturePath });
return execa('yarn', ['install'], { cwd: fixturePath, shell: true });
}
}
const skipOnWindows = new Set([
'now-dev-default-builds-and-routes',
'now-dev-static-routes',
'now-dev-static-build-routing',
'now-dev-directory-listing',
'now-dev-api-with-public',
'now-dev-api-with-static',
'now-dev-custom-404',
]);
function testFixture(name, fn) {
return async t => {
if (process.platform === 'win32' && skipOnWindows.has(name)) {
console.log(`Skipping test "${name}" on Windows.`);
t.is(true, true);
return;
}
let server;
const fixturePath = path.join(__dirname, 'fixtures', 'unit', name);
@@ -68,14 +84,6 @@ function validateResponseHeaders(t, res, podId = null) {
}
}
function get(url) {
return new Promise((resolve, reject) => {
request(url, resolve)
.on('error', reject)
.end();
});
}
test(
'[DevServer] Maintains query when invoking lambda',
testFixture('now-dev-query-invoke', async (t, server) => {
@@ -138,16 +146,23 @@ test(
test(
'[DevServer] Allow `cache-control` to be overwritten',
testFixture('now-dev-headers', async (t, server) => {
const res = await get(
const res = await fetch(
`${server.address}/?name=cache-control&value=immutable`
);
t.is(res.headers['cache-control'], 'immutable');
t.is(res.headers.get('cache-control'), 'immutable');
})
);
test(
'[DevServer] Sends `etag` header for static files',
testFixture('now-dev-headers', async (t, server) => {
if (process.platform === 'win32') {
console.log(
'Skipping "etag" test on windows since it yields a different result.'
);
t.is(true, true);
return;
}
const res = await fetch(`${server.address}/foo.txt`);
t.is(res.headers.get('etag'), '"d263af8ab880c0b97eb6c5c125b5d44f9e5addd9"');
t.is(await res.text(), 'hi\n');
@@ -400,12 +415,14 @@ test('[DevServer] parseListen()', t => {
t.deepEqual(parseListen('0.0.0.0'), [3000, '0.0.0.0']);
t.deepEqual(parseListen('127.0.0.1:3005'), [3005, '127.0.0.1']);
t.deepEqual(parseListen('tcp://127.0.0.1:5000'), [5000, '127.0.0.1']);
t.deepEqual(parseListen('unix:/home/user/server.sock'), [
'/home/user/server.sock',
]);
t.deepEqual(parseListen('pipe:\\\\.\\pipe\\PipeName'), [
'\\\\.\\pipe\\PipeName',
]);
if (process.platform !== 'win32') {
t.deepEqual(parseListen('unix:/home/user/server.sock'), [
'/home/user/server.sock',
]);
t.deepEqual(parseListen('pipe:\\\\.\\pipe\\PipeName'), [
'\\\\.\\pipe\\PipeName',
]);
}
let err;
try {

View File

@@ -0,0 +1 @@
SKIP_PREFLIGHT_CHECK=true

View File

@@ -22,3 +22,4 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
!yarn.lock
!.env

View File

@@ -0,0 +1 @@
.now

View File

@@ -1,7 +1,7 @@
{
"functions": {
"api/user": {
"runtime": "now-bash@1.0.3"
"api/user.sh": {
"runtime": "now-bash@3.0.0"
}
}
}

View File

@@ -0,0 +1 @@
.now

View File

@@ -0,0 +1,9 @@
from flask import Flask, Response
from datetime import datetime
app = Flask(__name__)
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def date(path):
d = datetime.now().isoformat()
return Response("Current date is %s" % (d), mimetype='text/html')

View File

@@ -0,0 +1,8 @@
from flask import Flask, Response, request
app = Flask(__name__)
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def user(path):
name = request.args.get('name')
return Response("Hello %s" % (name), mimetype='text/html')

View File

@@ -0,0 +1 @@
Home Page for Python

View File

@@ -0,0 +1 @@
Flask==1.0.3

View File

@@ -109,13 +109,14 @@ function validateResponseHeaders(t, res) {
async function exec(directory, args = []) {
return execa(binaryPath, ['dev', directory, ...args], {
reject: false,
shell: true,
env: { __NOW_SKIP_DEV_COMMAND: 1 },
});
}
async function runNpmInstall(fixturePath) {
if (await fs.exists(path.join(fixturePath, 'package.json'))) {
return execa('yarn', ['install'], { cwd: fixturePath });
return execa('yarn', ['install'], { cwd: fixturePath, shell: true });
}
}
@@ -123,6 +124,7 @@ async function getPackedBuilderPath(builderDirName) {
const packagePath = path.join(__dirname, '..', '..', '..', builderDirName);
const output = await execa('npm', ['pack'], {
cwd: packagePath,
shell: true,
});
if (output.exitCode !== 0 || output.stdout.trim() === '') {
@@ -160,6 +162,7 @@ async function testFixture(directory, opts = {}, args = []) {
{
reject: false,
detached: true,
shell: true,
stdio: 'pipe',
...opts,
env: { ...opts.env, __NOW_SKIP_DEV_COMMAND: 1 },
@@ -226,6 +229,7 @@ function testFixtureStdio(directory, fn) {
let printedOutput = false;
dev = execa(binaryPath, ['dev', dir, '-l', port], {
shell: true,
env: { __NOW_SKIP_DEV_COMMAND: 1 },
});
@@ -638,7 +642,7 @@ test(
await testPath(200, '/about/', 'About Page');
await testPath(200, '/sub/', 'Sub Index Page');
await testPath(200, '/sub/another/', 'Sub Another Page');
await testPath(200, '/style.css/', 'body { color: green }');
await testPath(200, '/style.css', 'body { color: green }');
await testPath(308, '/index.html', '', { Location: '/' });
await testPath(308, '/about.html', '', { Location: '/about/' });
await testPath(308, '/sub/index.html', '', { Location: '/sub/' });
@@ -653,17 +657,15 @@ test(
'[now dev] test trailingSlash true serve correct content',
testFixtureStdio('test-trailing-slash', async (t, port, testPath) => {
await testPath(200, '/', 'Index Page');
await testPath(200, '/index.html/', 'Index Page');
await testPath(200, '/about.html/', 'About Page');
await testPath(200, '/index.html', 'Index Page');
await testPath(200, '/about.html', 'About Page');
await testPath(200, '/sub/', 'Sub Index Page');
await testPath(200, '/sub/index.html/', 'Sub Index Page');
await testPath(200, '/sub/another.html/', 'Sub Another Page');
await testPath(200, '/style.css/', 'body { color: green }');
await testPath(308, '/about.html', '', { Location: '/about.html/' });
await testPath(200, '/sub/index.html', 'Sub Index Page');
await testPath(200, '/sub/another.html', 'Sub Another Page');
await testPath(200, '/style.css', 'body { color: green }');
await testPath(308, '/about.html/', '', { Location: '/about.html' });
await testPath(308, '/style.css/', '', { Location: '/style.css' });
await testPath(308, '/sub', '', { Location: '/sub/' });
await testPath(308, '/sub/another.html', '', {
Location: '/sub/another.html/',
});
})
);
@@ -772,17 +774,17 @@ test('[now dev] 03-aurelia', async t => {
await tester(t);
});
// test(
// '[now dev] 04-create-react-app-node',
// testFixtureStdio('create-react-app', async(t, port) => {
// const response = await fetch(`http://localhost:${port}`);
test(
'[now dev] 04-create-react-app',
testFixtureStdio('04-create-react-app', async (t, port) => {
const response = await fetch(`http://localhost:${port}`);
// validateResponseHeaders(t, response);
validateResponseHeaders(t, response);
// const body = await response.text();
// t.regex(body, /React App/gm);
// })
// );
const body = await response.text();
t.regex(body, /React App/gm);
})
);
test('[now dev] 05-gatsby', async t => {
if (shouldSkip(t, '05-gatsby', '>^6.14.0 || ^8.10.0 || >=9.10.0')) return;
@@ -1367,13 +1369,40 @@ test('[now dev] 25-nextjs-src-dir', async t => {
});
test(
'[now dev] Use runtime from the functions property',
testFixtureStdio('custom-runtime', async (t, port) => {
const response = await fetchWithRetry(`http://localhost:${port}/api/user`);
'[now dev] Use `@now/python` with Flask requirements.txt',
testFixtureStdio('python-flask', async (t, port) => {
const name = 'Alice';
const year = new Date().getFullYear();
const user = await fetchWithRetry(
`http://localhost:${port}/api/user?name=${name}`
);
const date = await fetchWithRetry(`http://localhost:${port}/api/date`);
const ext = await fetchWithRetry(`http://localhost:${port}/api/date.py`);
validateResponseHeaders(t, response);
validateResponseHeaders(t, user);
validateResponseHeaders(t, date);
validateResponseHeaders(t, ext);
const body = await response.text();
t.regex(body, /Hello, from Bash!/gm);
t.regex(await user.text(), new RegExp(`Hello ${name}`));
t.regex(await date.text(), new RegExp(`Current date is ${year}`));
t.regex(await ext.text(), new RegExp(`Current date is ${year}`));
})
);
test(
'[now dev] Use runtime from the functions property',
testFixtureStdio('custom-runtime', async (t, port) => {
const extensionless = await fetchWithRetry(
`http://localhost:${port}/api/user`
);
const extension = await fetchWithRetry(
`http://localhost:${port}/api/user.sh`
);
validateResponseHeaders(t, extensionless);
validateResponseHeaders(t, extension);
t.regex(await extensionless.text(), /Hello, from Bash!/gm);
t.regex(await extension.text(), /Hello, from Bash!/gm);
})
);

View File

@@ -485,6 +485,17 @@ CMD ["node", "index.js"]`,
},
}),
},
'project-root-directory': {
'src/index.html': '<h1>I am a website.</h1>',
'src/now.json': JSON.stringify({
rewrites: [
{
source: '/i-do-exist',
destination: '/',
},
],
}),
},
};
for (const typeName of Object.keys(spec)) {

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