Compare commits

..

121 Commits

Author SHA1 Message Date
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
Steven
888ff83309 Publish Stable
- @now/node@1.4.0
 - @now/python@1.1.1
2020-01-30 09:39:59 -05:00
Steven
7191421470 [tests] Fix rate limits for integration tests (#3696)
This PR will fix our CI rate limits for the "Now CLI Tests" check in GH Actions.

Thanks to @AndyBitz for most of the work in #3325
2020-01-29 16:24:02 -05:00
luc
e9c9aa5ce1 Publish Canary
- now@17.0.0-canary.26
2020-01-29 21:10:37 +01:00
Luc
d98f3d8140 [now-cli] Deprecate --name and now.json name property (#3690)
* remove `--name` from help

* add deprecation warning

* refactor

* deprecated `name` property in now.json

* add tests

* remove .only
2020-01-29 21:09:28 +01:00
Luc
fbf659e4e1 [now-cli] Add error message when running now dev on an unlinked folder (#3692)
* refactor getLinkedProject

* adjustments

* refactor getLinkedOrg

* refactor linkFolderToProject to skip linking

* fix dev tests

* adjust tests

* print deployment url for files

* fix missing `NOW_PROJECT_ID` check

* `process.status` -> `link.status`
2020-01-29 21:09:08 +01:00
Steven
6f7069aadd Publish Canary
- @now/python@1.1.1-canary.1
2020-01-29 13:26:41 -05:00
Steven
cef2c889f6 [now-python] Fix dependency detection in now dev (#3695)
Fixes a false positive regression from #3352 where `now dev`  assumed that local dependencies were already installed even when they were not.
2020-01-29 13:24:15 -05:00
Steven
e42d6c422a Publish Canary
- now@17.0.0-canary.25
 - @now/routing-utils@1.5.2-canary.4
2020-01-28 18:15:17 -05:00
Steven
2e9241b454 [now-routing-utils] Enabled case sensitive rewrites and set delimiter (#3688)
This enables case sensitive rewrites and redirects. It also changes the delimiter to `/` to match Next.js.

Unlike `routes` which were case insensitive, `rewrites` and `redirects` are case sensitive.

In the future, we may wish to add an option to toggle the sensitivity.
2020-01-28 18:13:57 -05:00
Steven
257a3b07fc [now-cli] Remove several v1 tests from CI (#3689)
These 5 tests are available in `integration-v1.js` already which can be executed manually.
2020-01-28 17:14:04 -05:00
luc
df9322b392 Publish Canary
- now@17.0.0-canary.24
2020-01-28 20:39:44 +01:00
Luc
28b5476bf3 [now-cli] Add link to "deploying files is deprecated" (#3685) 2020-01-28 20:38:50 +01:00
Luc
70a61f045f [now-cli] (Major) Use local .now scope for all commands and add scope to now whoami (#3643)
* use .now scope for commands

* add cache to getUser

* add cache on getTeams

* add cache for link

* refactor

* add error if NOW_ORG_ID is missing

* remove link cache

* refactor

* fix whoami

* add tests

* move fetching scope/org after subcommand condition

* Update packages/now-cli/src/util/projects/link.ts

Co-Authored-By: Andy <AndyBitz@users.noreply.github.com>

* remove userId from global scope

* show team and user in whoami command

* reset whoami

* Revert "move fetching scope/org after subcommand condition"

This reverts commit d145e6164074fe2442178cd8fafbeb225c978b9c.

* remove `client` from main scope

* tests adjustments

* adjust tests

* adjust tests (bis)

Co-authored-by: Andy <AndyBitz@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-01-28 20:38:27 +01:00
luc
238a8a8593 Publish Canary
- now@17.0.0-canary.23
 - now-client@7.0.0-canary.3
2020-01-28 15:48:36 +01:00
Luc
6f2d5c0045 [now-cli] Add README.txt when linking project (#3681)
* add README.txt when linking project

* replace ` with "

* add test

* Apply suggestions from code review

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

* extract NOW_DIR_README.txt

* Update packages/now-cli/src/util/projects/NOW_DIR_README.txt

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

Co-authored-by: Nathan Rajlich <n@n8.io>
2020-01-28 15:47:45 +01:00
Luc
fc798da398 [now-client] Update @types/node-fetch (#3682)
* update @types/node-fetch

* fix type error (another)

* add noEmitOnError

* add build test github action to test

* Revert "fix type error (another)"

This reverts commit 1d0dace70a0bf2dfb2473a48408e560f060e5c50.

* Revert "add noEmitOnError"

This reverts commit 57286016c69723b625293621f7474882f802521d.

* Revert "update @types/node-fetch"

This reverts commit 6979b5f866faaa141e5be1b1bda353f01a41349c.

* Revert "Revert "update @types/node-fetch""

This reverts commit 7459fb768cd9574854ac0f9f3d48eca1e5e63821.

* Revert "Revert "fix type error (another)""

This reverts commit 820db234f82d0a624cddb625529196807b571b5e.

* Revert "Revert "add noEmitOnError""

This reverts commit 896fdb9f33c7f5d62fea409a073de5c614d11024.

* Revert "add build test github action to test"

This reverts commit a2f1c2f914e6c66653dfabad262311ee2a2720b3.

* add noEmitOnError to now-cli

* fix signal-exit in yarn.lock
2020-01-28 11:42:32 +01:00
luc
0804483316 Publish Canary
- now@17.0.0-canary.22
2020-01-28 11:36:50 +01:00
Luc
4a087f842e [now-cli] Use devCommand from project settings locally (#3673)
* pull devCommand from cloud

* filter out front end builders with devCommand

* remove unnecessary import

* fix wrong directory listing with devCommand

* fix server not stopping

* Apply suggestions from code review

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

* remove comment

* remove redundant "if"

Co-authored-by: Nathan Rajlich <n@n8.io>
2020-01-28 11:35:57 +01:00
luc
01c7bf0059 Publish Canary
- now@17.0.0-canary.21
2020-01-28 11:29:25 +01:00
Luc
6c439516db [now-cli] Add tip to connect git repositories after now login is successful (#3678)
* add tip after now login is successful

* write success message to stdout
2020-01-28 11:27:56 +01:00
Steven
0db4ee69ef [tests] Add timeout to test jobs (#3683)
Circle CI used to have a max timeout of 10 minutes.

This adds a timeout to each job so that hanging tests are noticed earlier.

For example, these have been "in progress" for 4 hours: 

![image](https://user-images.githubusercontent.com/229881/73222438-3a90ea00-4131-11ea-97a7-76651a2cce45.png)
2020-01-27 19:25:39 -05:00
Steven
aec8a7ac9a Publish Canary
- now@17.0.0-canary.20
 - now-client@7.0.0-canary.2
 - @now/routing-utils@1.5.2-canary.3
 - @now/ruby@1.0.2-canary.0
2020-01-27 17:08:32 -05:00
m5o
882bc8cb99 [now-ruby] Add rack examples to ruby integration tests (#3626)
* add rack examples to ruby integration tests
  * https://github.com/rack/rack

Co-authored-by: Steven <steven@ceriously.com>
2020-01-27 16:55:13 -05:00
Steven
9aad701dfd [now-routing-utils] Merge destination query string with path segments (#3679)
This PR changes the behavior so that `:segment` in the `source` is always added to the query string unless the `destination` query string already contains a key with the same name.
2020-01-27 16:24:10 -05:00
Luc
f224f574be [now-cli] Deprecate deploying files with Now CLI (#3650)
* deprecate uploading files instead of erroring

* fix misc bugs

* remove final `.`

* add test

* check .now folder does not exist in test

* clean up after test
2020-01-27 20:41:38 +01:00
Andy
e201eaa315 [frameworks] Extend CONTRIBUTING.md (#3675)
* [frameworks] Extend CONTRIBUTING.md

* Update .github/CONTRIBUTING.md

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

* Update .github/CONTRIBUTING.md

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

Co-authored-by: Steven <steven@ceriously.com>
2020-01-27 15:54:29 +01:00
Andy
b0ed1f2be0 [now-cli] Remove unused code (#3674)
* [now-cli] Remove unused code

* [now-cli] Remove more unused code
2020-01-27 15:19:41 +01:00
Steven
4ac33e1c03 [now-cli][now-client] Drop support for Node 8 (#3670)
Deployments no longer support Node 8 since reaching EOL so we can also drop all of the special casing used to support Node 8 in Now CLI and Now Client.

The `tsconfig.json` has been updated to ES2018 per [Node-Target-Mapping](https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping).
2020-01-27 13:32:07 +00:00
luc
27ca517666 Publish Canary
- @now/static-build@0.14.11-canary.1
2020-01-25 23:51:36 +01:00
Luc
734e9c072d [now-static-build] Add public folder to Gatsby cache (#3669)
* add `public` to gatsby cache

* add test
2020-01-25 23:38:36 +01:00
Forrest Oliphant
8db54f3093 [examples] Fix svelte example github link (#3657)
https://svelte.now-examples.now.sh/ currently points to https://github.com/zeit/now-examples/tree/master/svelte-functions ... this should be the right link.
2020-01-25 14:41:37 +00:00
Andy
886c0718d6 [tests] Fix GitHub Actions on master (#3664)
* Checkout master on master

* Check if checkout works

* Revert "Check if checkout works"

This reverts commit 66f712a43edfb938c551110c214661e4a920b492.

* Revert "Checkout master on master"

This reverts commit 41b6eab926474830629e758a51e7cfdd1947bdf2.

* Use --depth=10

* Add another fetch
2020-01-25 10:58:13 +01:00
Andy
e160a2f21f [tests] Use GitHub Actions for the tests (#3660)
* Initial Build step

* Typo

* Remove circleci config

* Fix command

* Change Node version

* Add unit tests

* Use artifact

* Fix workflow file

* Remove node_modules

* Remove .git

* Do not upload files

* Checkout master branch

* Use git fetch

* Add now-dev tests

* Fix workflow file

* Use node10 for building

* Use node12 for building

* Download Hugo before building

* Fetch master branch

* Do not cancel other jobs when one fails

* Add all tests

* Remove needs

* Add build step

* Move test up

* Use isCanary function and remove tests

* Update Publish workflow

* Update .github/workflows/continuous-integration.yml

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

* Specify more events

* Add publish.sh script

* Remove Node8

Co-authored-by: Steven <steven@ceriously.com>
2020-01-25 00:42:55 +01:00
JJ Kasper
e09a418423 Publish Stable
- @now/next@2.3.12
2020-01-24 12:33:56 -06:00
JJ Kasper
52d4464368 Publish Canary
- @now/next@2.3.12-canary.0
2020-01-24 12:31:50 -06:00
JJ Kasper
4dc635e5f2 [now-next] Revert handle: miss/hit (#3658)
This reverts `handle: miss/hit` as it still needs some things sorted out before we're ready to use it in `@now/next`
2020-01-24 18:22:25 +00:00
Steven
510fb7ee7e Publish Canary
- @now/build-utils@1.3.7-canary.1
 - now@17.0.0-canary.19
2020-01-24 12:16:13 -05:00
Steven
243451e94b [now-build-utils] Add function detectApiExtensions() (#3653)
* [now-routing-utils] Add function detectApiExtensions

* Add more tests, fix broken test

* Add missing check for extensions

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-01-24 12:07:29 -05:00
Andy Bitz
11bbda977d Publish Stable
- @now/frameworks@0.0.7
 - @now/next@2.3.11
2020-01-24 17:20:55 +01:00
Andy Bitz
62c050f394 Publish Canary
- now@17.0.0-canary.18
 - now-client@7.0.0-canary.1
2020-01-24 17:19:27 +01:00
Andy
bd5a013312 [now-client] (Major) Remove builds check from now-client and change events (#3648)
* [now-client][now-cli] Remove builds check from now-client

* [now-client] Adjust README and change version

* Change events and adjust build error

* Use message from error

* Update packages/now-cli/src/util/deploy/process-deployment.ts

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

* [now-cli] Rename event

* Make types more consistent

* Fix type in process-legacy-deployment

* Adjust type in test

* Update type

* Make events type simpler

Co-authored-by: Max <8418866+rdev@users.noreply.github.com>
Co-authored-by: Luc <luc.leray@gmail.com>
2020-01-24 17:18:56 +01:00
Andy Bitz
1823cf452e Publish Canary
- @now/next@2.3.11-canary.4
2020-01-24 16:09:18 +01:00
Andy
c426d72ccf [now-next] Include file mode for pseudo layers (#3655) 2020-01-24 16:08:39 +01:00
Alex Grover
ddf59c052d Remove duplicated line from .gitignore (#3651) 2020-01-24 11:03:51 +01:00
luc
1dcf6e7fb1 Publish Canary
- now@17.0.0-canary.17
2020-01-23 20:06:18 +01:00
Luc
d4f4792988 [now-cli] Add --confirm to help (#3625)
```
$ now --help
[...]
  -c, --confirm                  Confirm default options and skip questions
```
2020-01-23 19:00:55 +00:00
Steven
7e1f2bd10e Publish Canary
- @now/build-utils@1.3.7-canary.0
2020-01-23 12:29:22 -05:00
Steven
a80a1d0c1d [now-build-utils] Fix api directory detection (#3647)
There was an issue where `@now/next` was emitting an api directory with serverless functions but the functions should not be renamed.
2020-01-23 16:54:34 +00:00
Steven
8ff747b4d7 Publish Canary
- @now/next@2.3.11-canary.3
 - @now/routing-utils@1.5.2-canary.2
2020-01-23 09:37:30 -05:00
Steven
aa63b5a581 [github] Update codeowners (#3642)
Added a few more code owners
2020-01-23 00:25:25 +00:00
luc
2094ec3c99 Publish Canary
- now@17.0.0-canary.16
2020-01-23 00:57:56 +01:00
Luc
bf30d10211 [now-cli] Output error if NOW_PROJECT_ID/NOW_ORG_ID is defined without the other (#3630)
* output error if one of both env is missing

* add test

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-01-23 00:53:15 +01:00
Luc
ccc03c9c6e [now-cli] Add warning if linked project was deleted or access was removed (#3631)
![image](https://user-images.githubusercontent.com/6616955/72800302-4ff4a880-3c47-11ea-8d74-0ae0c18469da.png)
2020-01-22 23:27:30 +00:00
Steven
4b7367e2dc [now-routing-utils] Fix segments in query string (#3640)
This PR a regression when path segments are used in the query string.

Take a look at the following ASCII Table for why I had to delete certain parts of the parsed url before formatting again.

https://nodejs.org/api/url.html#url_url_strings_and_url_objects

Related to #3539
2020-01-22 22:46:47 +00:00
JJ Kasper
00aa56a095 [now-next] Add headers support for custom-routes (#3494)
This adds support for `headers` in custom-routes which was landed in Next.js. 

This also updates `@now/routing-utils` `convertHeaders` to call `sourceToRegex` to match behavior with Next.js and allow using `segments` to match in the header `source` as not being able to use the same syntax for a header `source` as a `redirect` source could get confusing
2020-01-22 20:45:36 +00:00
Steven
56ae93a2a5 [examples] Fix jekyll readme build command (#3639)
Fixes #3634
2020-01-22 18:19:12 +00:00
JJ Kasper
adb32a09d3 Publish Canary
- @now/next@2.3.11-canary.2
 - @now/routing-utils@1.5.2-canary.1
2020-01-22 11:46:57 -06:00
JJ Kasper
3358d8e44c [now-next] Add handle: miss and handle: hit for custom-routes (#3489)
This is required to match custom-routes behavior in Next.js by checking dynamic routes after each rewrite although is currently blocked on `now dev` also supporting the feature

This reverts commit 0da98a7f5d.
2020-01-22 17:39:27 +00:00
Steven
c3bd2698e8 [now-routing-utils] Disallow "status" in hit phase (#3637)
This will prevent any strange behavior since production already ignores status code in the hit phase.
2020-01-22 17:03:10 +00:00
Steven
a7baa4761d Publish Canary
- now@17.0.0-canary.15
2020-01-21 19:56:55 -05:00
Steven
5dd2daa970 [now dev] Add support for handle: miss and handle: hit (#3537)
- [x] Add tests from now-proxy for `handle: miss`
- [x] Add tests from now-proxy for `handle: hit`
- [x] Add file output renaming when `featHandleMiss` is true (also assign true for now dev)
2020-01-22 00:54:24 +00:00
JJ Kasper
dd36a489ed Publish Canary
- @now/frameworks@0.0.7-canary.0
 - @now/next@2.3.11-canary.1
 - @now/static-build@0.14.11-canary.0
2020-01-20 15:36:58 -06:00
JJ Kasper
2e742209e3 [now-next] Add initial support for static 404 (#3628)
This adds initial support for static 404 pages when enabled for Next.js applications > `9.2.1-canary.3` it also disables tracing/logging related to lambdas when there aren't any lambdas besides the `_error` when a static 404 is being used 

Closes: #3368
2020-01-20 20:55:08 +00:00
Andy
8d13464cba [frameworks][now-static-build] Use hugo -D --gc as build command (#3624)
* [frameworks][now-static-build] Use `hugo --gc` as build command

* Add -D option
2020-01-20 19:46:15 +01:00
Max Rovensky
20fdcfa0af Publish Stable
- @now/static-build@0.14.10
2020-01-18 04:00:08 +08:00
Max
fac004f83c [now-static-build] Remove legacy message from static build (#3618)
* Remove legacy message from static build

* Remove unused config

* Remove unused config
2020-01-17 20:59:10 +01:00
Andy Bitz
5fee4bbad1 Publish Stable
- @now/static-build@0.14.9
2020-01-17 20:21:52 +01:00
Andy
18e4b18839 [now-static-build] Ignore commands for non-zero-config (#3617) 2020-01-17 20:19:53 +01:00
Max Rovensky
b8627fd384 Publish Stable
- @now/static-build@0.14.8
2020-01-18 02:42:30 +08:00
Max
4e2db6f8a5 [now-static-build] Improve static build errors copy (#3616)
* Improve static build errors copy

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

Co-Authored-By: Leo Lamprecht <mindrun@icloud.com>

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

Co-Authored-By: Leo Lamprecht <mindrun@icloud.com>

Co-authored-by: Leo Lamprecht <mindrun@icloud.com>
2020-01-17 19:36:52 +01:00
Steven
b685a3afdd Publish Canary
- @now/next@2.3.11-canary.0
 - @now/node@1.3.6-canary.0
2020-01-17 13:09:32 -05:00
Steven
aea3f58970 [api] Fix build script (#3615)
I was getting errors when running `yarn build` locally because the public directory already exists.

This will make sure the public directory is deleted before generating it again.
2020-01-17 13:08:41 -05:00
Steven
b0bb90dc11 Bump node-file-trace to 0.5.1 (#3613)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-01-17 13:07:59 -05:00
208 changed files with 3398 additions and 1960 deletions

View File

@@ -1,501 +0,0 @@
version: 2
jobs:
install:
docker:
- image: circleci/node:10
working_directory: ~/repo
environment:
GOPATH: $HOME/go
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "yarn.lock" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run:
name: Updating apt packages
command: sudo apt-get update
- run:
name: Installing the latest version of Go
command: sudo apt-get install golang-go
- run:
name: Installing Dependencies
command: yarn install --check-files --frozen-lockfile
- save_cache:
paths:
- node_modules
- packages/gatsby-plugin-now/node_modules
- packages/now-build-utils/node_modules
- packages/now-cgi/node_modules
- packages/now-cli/node_modules
- packages/now-client/node_modules
- packages/now-go/node_modules
- packages/now-next/node_modules
- packages/now-node/node_modules
- packages/now-node-bridge/node_modules
- packages/now-python/node_modules
- packages/now-routing-utils/node_modules
- packages/now-ruby/node_modules
- packages/now-static-build/node_modules
key: v1-dependencies-{{ checksum "yarn.lock" }}
- persist_to_workspace:
root: .
paths:
- node_modules
- packages/gatsby-plugin-now/node_modules
- packages/now-build-utils/node_modules
- packages/now-cgi/node_modules
- packages/now-cli/node_modules
- packages/now-client/node_modules
- packages/now-go/node_modules
- packages/now-next/node_modules
- packages/now-node/node_modules
- packages/now-node-bridge/node_modules
- packages/now-python/node_modules
- packages/now-routing-utils/node_modules
- packages/now-ruby/node_modules
- packages/now-static-build/node_modules
build:
docker:
- image: circleci/node:10
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Linking dependencies
command: yarn bootstrap
- run:
name: Building
command: yarn build
- store_artifacts:
path: packages/now-cli/dist
- persist_to_workspace:
root: .
paths:
- packages/gatsby-plugin-now/test/fixtures
- packages/now-build-utils/dist
- packages/now-cgi/dist
- packages/now-cli/dist
- packages/now-cli/assets
- packages/now-client/dist
- packages/now-go/dist
- packages/now-next/dist
- packages/now-node/dist
- packages/now-node/test/fixtures/15-helpers/ts/types.d.ts
- packages/now-node/test/fixtures/11-symlinks/symlink
- packages/now-node-bridge/index.js
- packages/now-node-bridge/bridge.js
- packages/now-python/dist
- packages/now-routing-utils/dist
- packages/now-ruby/dist
- packages/now-static-build/dist
- packages/now-static-build/test/fixtures/10a-gatsby-redirects/plugins
test-lint:
docker:
- image: circleci/node:10
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Compiling `now dev` HTML error templates
command: node packages/now-cli/scripts/compile-templates.js
- run:
name: Linting Code
command: yarn test-lint
test-integration-macos-node-8:
macos:
xcode: '9.0.1'
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Output version
command: node --version
- run:
name: Running Integration Tests
command: yarn test-integration --clean false
test-integration-macos-node-10:
macos:
xcode: '10.0.0'
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Output version
command: node --version
- run:
name: Running Integration Tests
command: yarn test-integration --clean false
test-integration-macos-node-12:
macos:
xcode: '10.3.0'
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Output version
command: node --version
- run:
name: Running Integration Tests
command: yarn test-integration --clean false
test-integration-linux-node-8:
docker:
- image: circleci/node:8
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Output version
command: node --version
- run:
name: Running Integration Tests
command: yarn test-integration --clean false
test-integration-linux-node-10:
docker:
- image: circleci/node:10
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Output version
command: node --version
- run:
name: Running Integration Tests
command: yarn test-integration --clean false
test-integration-linux-node-12:
docker:
- image: circleci/node:12
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Output version
command: node --version
- run:
name: Running Integration Tests
command: yarn test-integration --clean false
test-integration-macos-now-dev-node-8:
macos:
xcode: '9.0.1'
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Output version
command: node --version
- run:
name: Downloading Hugo
command: 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:
name: Running Integration Tests for `now dev`
command: yarn test-integration-now-dev --clean false
test-integration-macos-now-dev-node-10:
macos:
xcode: '10.0.0'
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Output version
command: node --version
- run:
name: Downloading Hugo
command: 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:
name: Running Integration Tests for `now dev`
command: yarn test-integration-now-dev --clean false
test-integration-macos-now-dev-node-12:
macos:
xcode: '10.3.0'
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Output version
command: node --version
- run:
name: Downloading Hugo
command: 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:
name: Running Integration Tests for `now dev`
command: yarn test-integration-now-dev --clean false
test-integration-linux-now-dev-node-8:
docker:
- image: circleci/node:8
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Output version
command: node --version
- run:
name: Downloading Hugo
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.55.6/hugo_0.55.6_Linux-64bit.tar.gz && tar -xzf hugo_0.55.6_Linux-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
- run:
name: Running Integration Tests for `now dev`
command: yarn test-integration-now-dev --clean false
test-integration-linux-now-dev-node-10:
docker:
- image: circleci/node:10
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Output version
command: node --version
- run:
name: Downloading Hugo
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.55.6/hugo_0.55.6_Linux-64bit.tar.gz && tar -xzf hugo_0.55.6_Linux-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
- run:
name: Running Integration Tests for `now dev`
command: yarn test-integration-now-dev --clean false
test-integration-linux-now-dev-node-12:
docker:
- image: circleci/node:12
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Output version
command: node --version
- run:
name: Downloading Hugo
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.55.6/hugo_0.55.6_Linux-64bit.tar.gz && tar -xzf hugo_0.55.6_Linux-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
- run:
name: Running Integration Tests for `now dev`
command: yarn test-integration-now-dev --clean false
test-integration-once:
docker:
- image: circleci/node:10
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Output version
command: node --version
- run:
name: Running Integration Tests Once
command: yarn test-integration-once --clean false
test-unit:
docker:
- image: circleci/node:10
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Compiling `now dev` HTML error templates
command: node packages/now-cli/scripts/compile-templates.js
- run:
name: Output version
command: node --version
- run:
name: Running Unit Tests
command: yarn test-unit --clean false
- persist_to_workspace:
root: .
paths:
- packages/now-cli/.nyc_output
coverage:
docker:
- image: circleci/node:10
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Run coverage report
command: yarn workspace now run coverage
source-maps:
docker:
- image: circleci/node:10
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Installing Sentry CLI
command: npm install -g @sentry/cli
- run:
name: Creating a New Sentry Release
command: sentry-cli releases new now-cli@`git describe --tags`
- run:
name: Upload Sourcemap Files
command: sentry-cli releases files now-cli@`git describe --tags` upload-sourcemaps ./dist
- run:
name: Finalize Sentry Release
command: sentry-cli releases finalize now-cli@`git describe --tags`
workflows:
version: 2
unscheduled:
jobs:
- install:
filters:
tags:
only: /.*/
- build:
requires:
- install
filters:
tags:
only: /.*/
- test-lint:
requires:
- build
filters:
tags:
only: /.*/
- test-integration-macos-node-8:
requires:
- build
filters:
tags:
only: /.*/
- test-integration-macos-node-10:
requires:
- build
filters:
tags:
only: /.*/
- test-integration-macos-node-12:
requires:
- build
filters:
tags:
only: /.*/
- test-integration-linux-node-8:
requires:
- build
filters:
tags:
only: /.*/
- test-integration-linux-node-10:
requires:
- build
filters:
tags:
only: /.*/
- test-integration-linux-node-12:
requires:
- build
filters:
tags:
only: /.*/
- test-integration-macos-now-dev-node-8:
requires:
- build
filters:
tags:
only: /.*/
- test-integration-macos-now-dev-node-10:
requires:
- build
filters:
tags:
only: /.*/
- test-integration-macos-now-dev-node-12:
requires:
- build
filters:
tags:
only: /.*/
- test-integration-linux-now-dev-node-8:
requires:
- build
filters:
tags:
only: /.*/
- test-integration-linux-now-dev-node-10:
requires:
- build
filters:
tags:
only: /.*/
- test-integration-linux-now-dev-node-12:
requires:
- build
filters:
tags:
only: /.*/
- test-integration-once:
requires:
- build
- test-unit:
requires:
- build
filters:
tags:
only: /.*/
- coverage:
requires:
- test-integration-macos-node-8
- test-integration-macos-node-10
- test-integration-macos-node-12
- test-integration-linux-node-8
- test-integration-linux-node-10
- test-integration-linux-node-12
- test-integration-macos-now-dev-node-8
- test-integration-macos-now-dev-node-10
- test-integration-macos-now-dev-node-12
- test-integration-linux-now-dev-node-8
- test-integration-linux-now-dev-node-10
- test-integration-linux-now-dev-node-12
- test-integration-once
- test-unit
- test-lint
filters:
tags:
only: /.*/

9
.github/CODEOWNERS vendored
View File

@@ -2,6 +2,8 @@
# 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
/packages/now-cli/src/commands/domains/ @javivelasco @mglagola @anatrajkovska
@@ -10,13 +12,16 @@
/packages/now-build-utils @styfle @AndyBitz
/packages/now-node @styfle @tootallnate @lucleray
/packages/now-node-bridge @styfle @tootallnate @lucleray
/packages/now-next @Timer
/packages/now-next @Timer @ijjk
/packages/now-go @styfle @sophearak
/packages/now-python @styfle @sophearak
/packages/now-ruby @styfle @coetry @nathancahill
/packages/now-static-build @styfle @AndyBitz
/packages/now-routing-utils @dav-is
/packages/now-routing-utils @styfle @dav-is @ijjk
/examples @msweeneydev @timothyis
/examples/create-react-app @Timer
/examples/nextjs @timneutkens
/examples/hugo @msweeneydev @timothyis @styfle
/examples/jekyll @msweeneydev @timothyis @sarupbanskota
/examples/zola @msweeneydev @timothyis @styfle

View File

@@ -97,10 +97,13 @@ 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.
1. Add the framework to the `@now/frameworks` package.
The file is located in `packages/frameworks/frameworks.json`.
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 in `@now/frameworks`.
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. Finally, `@now/static-build` must be adjusted.
The files `packages/now-static-build/src/frameworks.ts` has to be extended.
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.

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

@@ -0,0 +1,17 @@
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@master
with:
workflow_id: 435869
access_token: ${{ secrets.GITHUB_WORKFLOW_TOKEN }}

View File

@@ -0,0 +1,101 @@
name: CI
on:
push:
branches:
- master
tags:
- '!*'
pull_request:
jobs:
test-unit:
name: Unit Tests
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-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
- run: yarn install && yarn run build
- run: yarn run test-lint
- run: yarn run test-unit --clean false
- 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
test-integration:
name: Integration Tests
timeout-minutes: 120
runs-on: ubuntu-latest
steps:
- 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 test-integration-once --clean false
test-now-cli:
name: Now CLI Tests
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
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
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: yarn test-integration --clean false
test-now-dev:
name: "`now dev` Tests"
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-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
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: yarn test-integration-now-dev --clean false
coverage:
name: Coverage
timeout-minutes: 10
needs: [test-unit, test-now-cli, test-now-dev, test-integration]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: git fetch origin master --depth=10
- run: git fetch origin ${{ github.ref }} --depth=10
- uses: actions/download-artifact@v1
with:
name: test-unit-output
path: packages/now-cli/.nyc_output
- run: yarn install
- run: yarn workspace now run coverage

View File

@@ -4,25 +4,18 @@ on:
push:
branches:
- master
- canary
tags:
- '!*'
jobs:
Publish:
publish:
name: Publish
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x]
steps:
- uses: actions/checkout@v1
- name: Checkout
uses: actions/setup-node@v1
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
node-version: 10
- name: Install
run: yarn install --check-files --frozen-lockfile
- name: Build

View File

@@ -1,6 +1,6 @@
![now](https://assets.zeit.co/image/upload/v1542240976/repositories/now-cli/now-cli-repo-banner-v3.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

@@ -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

@@ -26,17 +26,17 @@ You can deploy your new Jekyll project with a single command from your terminal
$ now
```
### Example Changes
### Build Command
This example adds a `package.json` file with the following:
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 && mv _site public"
"build": "jekyll build"
}
}
```
This instructs ZEIT Now to build the Jekyll website and move the output to the public directory.

View File

@@ -13,7 +13,6 @@
# misc
.DS_Store
.env
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -20,7 +20,7 @@
</h2>
<p>
<a
href="https://github.com/zeit/now/tree/master/examples/svelte-functions"
href="https://github.com/zeit/now/tree/master/examples/svelte"
target="_blank"
rel="noreferrer noopener">
This project

View File

@@ -10,6 +10,10 @@
"GITHUB_ACCESS_TOKEN": "@now-api-examples-github-token",
"SENTRY_DSN": "@sentry-product-dsn"
},
"github": {
"silent": true,
"autoJobCancelation": true
},
"headers": [
{
"source": "/api/frameworks",

View File

@@ -32,7 +32,7 @@
"bootstrap": "lerna bootstrap",
"publish-stable": "echo 'Run `yarn changelog` for instructions'",
"publish-canary": "git pull && lerna version prerelease --preid canary --message 'Publish Canary' --exact",
"publish-from-github": "./.circleci/publish.sh",
"publish-from-github": "./utils/publish.sh",
"changelog": "node utils/changelog.js",
"build": "node utils/run.js build all",
"test-lint": "node utils/run.js test-lint",
@@ -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

@@ -580,7 +580,7 @@
},
"settings": {
"buildCommand": {
"value": "hugo"
"value": "hugo -D --gc"
},
"devCommand": {
"value": "hugo server -D -w -p $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.6",
"version": "0.0.8",
"main": "frameworks.json",
"license": "UNLICENSED"
}

View File

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

View File

@@ -28,7 +28,7 @@ function getApiBuilders({ tag }: Pick<Options, 'tag'> = {}): Builder[] {
return [
{ src: 'api/**/*.js', use: `@now/node${withTag}`, config },
{ src: 'api/**/*.ts', use: `@now/node${withTag}`, config },
{ src: 'api/**/*.go', use: `@now/go${withTag}`, config },
{ src: 'api/**/!(*_test).go', use: `@now/go${withTag}`, config },
{ src: 'api/**/*.py', use: `@now/python${withTag}`, config },
{ src: 'api/**/*.rb', use: `@now/ruby${withTag}`, config },
];
@@ -423,10 +423,18 @@ export async function detectBuilders(
let frontendBuilder: Builder | null = null;
if (hasBuildScript(pkg) || buildCommand || framework) {
// Everything will be static if either is an empty string
const ignoreBuild = buildCommand === '' || outputDirectory === '';
if (!ignoreBuild && (hasBuildScript(pkg) || buildCommand || framework)) {
frontendBuilder = detectFrontBuilder(pkg, builders, files, options);
} else {
if (!options.ignoreBuildScript && pkg && builders.length === 0) {
if (
!ignoreBuild &&
!options.ignoreBuildScript &&
pkg &&
builders.length === 0
) {
// We only show this error when there are no api builders
// since the dependencies of the pkg could be used for those
errors.push({
@@ -442,7 +450,9 @@ export async function detectBuilders(
// when there are no build steps
const outDir = outputDirectory || 'public';
if (hasDirectory(outDir, files)) {
// If `outputDirectory` is an empty string,
// we'll default to the root directory.
if (hasDirectory(outDir, files) && outputDirectory !== '') {
frontendBuilder = {
use: '@now/static',
src: `${outDir}/**/*`,

View File

@@ -1,4 +1,4 @@
import { parse as parsePath } from 'path';
import { parse as parsePath, extname } from 'path';
import { Route, Source } from '@now/routing-utils';
import { Builder } from './types';
import { getIgnoreApiFilter, sortFiles } from './detect-builders';
@@ -331,8 +331,22 @@ export function detectOutputDirectory(builders: Builder[]): string | 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 isZeroConfig = builders.some(b => b.config && b.config.zeroConfig);
return isZeroConfig ? 'api' : null;
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(
@@ -361,12 +375,7 @@ export async function detectRoutes(
);
if (featHandleMiss) {
defaultRoutes.push({ handle: 'miss' });
const extSet = new Set(
builders
.filter(b => b.src && b.src.startsWith('api/'))
.map(b => parsePath(b.src).ext)
.filter(Boolean)
);
const extSet = detectApiExtensions(builders);
if (extSet.size > 0) {
const exts = Array.from(extSet)
.map(ext => ext.slice(1))

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,
@@ -66,6 +67,7 @@ export {
detectRoutes,
detectOutputDirectory,
detectApiDirectory,
detectApiExtensions,
} from './detect-routes';
export { detectBuilders } from './detect-builders';
export { detectFramework } from './detect-framework';

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

@@ -1,6 +1,10 @@
import { Source, Route } from '@now/routing-utils';
import { detectBuilders, detectRoutes } from '../src';
import { detectOutputDirectory, detectApiDirectory } from '../';
import {
detectOutputDirectory,
detectApiDirectory,
detectApiExtensions,
} from '../';
describe('Test `detectBuilders`', () => {
it('package.json + no build', async () => {
@@ -147,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'];
@@ -741,6 +766,53 @@ 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 () => {
@@ -1877,6 +1949,93 @@ describe('Test `detectApiDirectory`', () => {
const result = detectApiDirectory(builders);
expect(result).toBe('api');
});
it('should be `null` with zero config but without api directory', async () => {
const builders = [
{
use: '@now/next',
src: 'package.json',
config: { zeroConfig: true },
},
];
const result = detectApiDirectory(builders);
expect(result).toBe(null);
});
});
describe('Test `detectApiExtensions`', () => {
it('should have correct extensions', async () => {
const builders = [
{
use: '@now/node',
src: 'api/**/*.js',
config: {
zeroConfig: true,
},
},
{
use: '@now/python',
src: 'api/**/*.py',
config: {
zeroConfig: true,
},
},
{
use: '@now/go',
src: 'api/**/*.go',
config: {
zeroConfig: true,
},
},
{
use: '@now/ruby',
src: 'api/**/*.rb',
config: {
zeroConfig: true,
},
},
{
use: 'now-bash',
src: 'api/**/*.sh',
// No zero config so it should not be added
},
{
use: 'now-no-extension',
src: 'api/executable',
// No extension should not be added
config: {
zeroConfig: true,
},
},
{
use: '@now/next',
src: 'package.json',
// No api directory should not be added
config: {
zeroConfig: true,
},
},
{
use: 'now-rust@1.0.1',
src: 'api/user.rs',
config: {
zeroConfig: true,
functions: {
'api/**/*.rs': {
runtime: 'now-rust@1.0.1',
},
},
},
},
];
const result = detectApiExtensions(builders);
expect(result.size).toBe(5);
expect(result.has('.js')).toBe(true);
expect(result.has('.py')).toBe(true);
expect(result.has('.go')).toBe(true);
expect(result.has('.rb')).toBe(true);
expect(result.has('.rs')).toBe(true);
});
});
/**

View File

@@ -116,10 +116,7 @@ it('should throw for discontinued versions', async () => {
const realDateNow = Date.now.bind(global.Date);
global.Date.now = () => new Date('2020-02-14').getTime();
expect(getSupportedNodeVersion('', false)).rejects.toThrow();
expect(getSupportedNodeVersion('8.10.x', false)).rejects.toThrow();
expect(getSupportedNodeVersion('', true)).rejects.toThrow();
expect(getSupportedNodeVersion('8.10.x', true)).rejects.toThrow();
expect(getDiscontinuedNodeVersions().length).toBe(1);

View File

@@ -1,6 +1,6 @@
{
"name": "@now/cgi",
"version": "1.0.1",
"version": "1.0.2",
"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

@@ -0,0 +1,10 @@
declare module 'is-port-reachable' {
export interface IsPortReachableOptions {
timeout?: number | undefined;
host?: string;
}
export default function(
port: number | undefined,
options?: IsPortReachableOptions
): Promise<boolean>;
}

View File

@@ -1,6 +1,6 @@
{
"name": "now",
"version": "17.0.0-canary.14",
"version": "17.0.0-canary.34",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Now",
@@ -59,10 +59,11 @@
]
},
"engines": {
"node": ">= 8"
"node": ">= 10"
},
"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",
@@ -92,7 +93,7 @@
"@types/which": "1.3.1",
"@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",
@@ -126,11 +127,13 @@
"esm": "3.1.4",
"execa": "3.2.0",
"fs-extra": "7.0.1",
"get-port": "5.1.1",
"glob": "7.1.2",
"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",
"jsonlines": "0.1.1",

View File

@@ -50,21 +50,23 @@ async function createBuildersTarball() {
async function main() {
const isDev = process.argv[2] === '--dev';
// Create a tarball from all the `@now` scoped builders which will be bundled
// with Now CLI
await createBuildersTarball();
if (!isDev) {
// Create a tarball from all the `@now` scoped builders which will be bundled
// with Now CLI
await createBuildersTarball();
// `now dev` uses chokidar to watch the filesystem, but opts-out of the
// `fsevents` feature using `useFsEvents: false`, so delete the module here so
// that it is not compiled by ncc, which makes the npm package size larger
// than necessary.
await remove(join(dirRoot, '../../node_modules/fsevents'));
// `now dev` uses chokidar to watch the filesystem, but opts-out of the
// `fsevents` feature using `useFsEvents: false`, so delete the module here so
// that it is not compiled by ncc, which makes the npm package size larger
// than necessary.
await remove(join(dirRoot, '../../node_modules/fsevents'));
// Compile the `doT.js` template files for `now dev`
console.log();
await execa(process.execPath, [join(__dirname, 'compile-templates.js')], {
stdio: 'inherit',
});
// Compile the `doT.js` template files for `now dev`
console.log();
await execa(process.execPath, [join(__dirname, 'compile-templates.js')], {
stdio: 'inherit',
});
}
// Do the initial `ncc` build
console.log();

View File

@@ -1,25 +1,3 @@
#!/usr/bin/env node
// This should only be used for the integration tests
const { join } = require('path');
const { spawnSync } = require('child_process');
const match = process.version.match(/^v(\d+)\.(\d+)/);
const major = match && parseInt(match[1], 10);
// Must be above or equal to 10.10.0
// const isVersion = major > 10 || (major === 10 && minor >= 10);
if (major === 8 && process.platform === 'darwin') {
const [node, _, ...args] = process.argv; // eslint-disable-line @typescript-eslint/no-unused-vars
const script = join(__dirname, '../dist/index.js');
const { status } = spawnSync(node, ['--no-warnings', script, ...args], {
stdio: 'inherit'
});
process.exit(status);
} else {
require('../dist/index.js');
}
require('../dist/index.js');

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,14 +5,11 @@ 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';
import { Output } from '../../util/output';
import { DomainPermissionDenied, InvalidCert } from '../../util/errors-ts';
interface Options {
'--overwrite'?: boolean;
'--debug'?: boolean;
@@ -88,7 +85,7 @@ async function add(
}
// Create a custom certificate from the given file paths
cert = await createCertFromFile(now, keyPath, crtPath, caPath, contextName);
cert = await createCertFromFile(now, keyPath, crtPath, caPath);
} else {
output.warn(
`${chalk.cyan(
@@ -112,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

@@ -87,13 +87,7 @@ export default async function issue(
}
// Create a custom certificate from the given file paths
cert = await createCertFromFile(
client,
keyPath,
crtPath,
caPath,
contextName
);
cert = await createCertFromFile(client, keyPath, crtPath, caPath);
if (cert instanceof Error) {
output.error(cert.message);

View File

@@ -38,7 +38,6 @@ export const latestHelp = () => `
-h, --help Output usage information
-v, --version Output the version number
-V, --platform-version Set the platform version to deploy to
-n, --name Set the project name of the deployment
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'
)} Path to the local ${'`now.json`'} file
@@ -66,6 +65,7 @@ export const latestHelp = () => `
-S, --scope Set a custom scope
--regions Set default regions to enable the deployment on
--prod Create a production deployment
-c, --confirm Confirm default options and skip questions
${note(
`To view the usage information for Now 1.0, run ${code(
@@ -96,7 +96,6 @@ export const latestHelp = () => `
`;
export const latestArgs = {
'--name': String,
'--force': Boolean,
'--public': Boolean,
'--no-clipboard': Boolean,
@@ -107,10 +106,8 @@ export const latestArgs = {
// This is not an array in favor of matching
// the config property name.
'--regions': String,
'--target': String,
'--prod': Boolean,
'--confirm': Boolean,
'-n': '--name',
'-f': '--force',
'-p': '--public',
'-e': '--env',
@@ -118,6 +115,11 @@ export const latestArgs = {
'-C': '--no-clipboard',
'-m': '--meta',
'-c': '--confirm',
// deprecated
'--name': String,
'-n': '--name',
'--target': String,
};
export const legacyArgsMri = {

View File

@@ -187,7 +187,6 @@ export default async ctx => {
output,
stats,
localConfig || {},
isFile,
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,11 @@ 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';
const addProcessEnv = async (log, env) => {
let val;
@@ -90,7 +94,8 @@ const printDeploymentStatus = async (
},
deployStamp,
isClipboardEnabled,
quiet
quiet,
isFile
) => {
const isProdDeployment = target === 'production';
@@ -113,7 +118,7 @@ const printDeploymentStatus = async (
// print preview/production url
let previewUrl;
let isWildcard;
if (Array.isArray(aliasList) && aliasList.length > 0) {
if (!isFile && Array.isArray(aliasList) && aliasList.length > 0) {
// search for a non now.sh/non wildcard domain
// but fallback to the first alias in the list
const mainAlias =
@@ -130,10 +135,13 @@ const printDeploymentStatus = async (
}
// copy to clipboard
let isCopiedToClipboard = false;
if (isClipboardEnabled && !isWildcard) {
await copy(previewUrl).catch(error =>
output.debug(`Error copying to clipboard: ${error}`)
);
await copy(previewUrl)
.then(() => {
isCopiedToClipboard = true;
})
.catch(error => output.debug(`Error copying to clipboard: ${error}`));
}
// write to stdout
@@ -145,7 +153,9 @@ const printDeploymentStatus = async (
prependEmoji(
`${isProdDeployment ? 'Production' : 'Preview'}: ${chalk.bold(
previewUrl
)} ${deployStamp()}`,
)}${
isCopiedToClipboard ? chalk.gray(` [copied to clipboard]`) : ''
} ${deployStamp()}`,
emoji('success')
) + `\n`
);
@@ -201,7 +211,6 @@ export default async function main(
output,
stats,
localConfig,
isFile,
args
) {
let argv = null;
@@ -224,14 +233,17 @@ export default async function main(
// $FlowFixMe
const isTTY = process.stdout.isTTY;
const quiet = !isTTY;
const autoConfirm = argv['--confirm'];
// check paths
const path = await validatePaths(output, paths);
if (typeof path === 'number') {
return path;
const pathValidation = await validatePaths(output, paths);
if (!pathValidation.valid) {
return pathValidation.exitCode;
}
const { isFile, path } = pathValidation;
const autoConfirm = argv['--confirm'] || isFile;
// build `meta`
const meta = Object.assign(
{},
@@ -244,6 +256,27 @@ export default async function main(
warn(`The option --no-scale is only supported on Now 1.0 deployments`);
}
// deprecate --name
if (argv['--name']) {
output.print(
`${prependEmoji(
`The ${param('--name')} flag is deprecated (https://zeit.ink/1B)`,
emoji('warning')
)}\n`
);
}
if (localConfig && localConfig.name) {
output.print(
`${prependEmoji(
`The ${code('name')} property in ${highlight(
'now.json'
)} is deprecated (https://zeit.ink/5F)`,
emoji('warning')
)}\n`
);
}
// build `env`
const isObject = item =>
Object.prototype.toString.call(item) === '[object Object]';
@@ -347,21 +380,17 @@ export default async function main(
});
// retrieve `project` and `org` from .now
let [org, project] = await getLinkedProject(client, path);
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 (!org || !project) {
const { NOW_PROJECT_ID, NOW_ORG_ID } = process.env;
if (NOW_PROJECT_ID && NOW_ORG_ID) {
output.print(
`${chalk.red('Error!')} Project not found (${JSON.stringify({
NOW_PROJECT_ID,
NOW_ORG_ID,
})})\n`
);
return 1;
}
if (status === 'not_linked') {
const shouldStartSetup =
autoConfirm ||
(await confirm(
@@ -375,6 +404,7 @@ export default async function main(
}
org = await selectOrg(
output,
'Which scope do you want to deploy to?',
client,
ctx.config.currentTeam,
@@ -398,8 +428,10 @@ export default async function main(
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(
@@ -412,9 +444,26 @@ export default async function main(
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;
}
const currentTeam = org.type === 'team' ? org.id : undefined;
const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam });
let deployStamp = stamp();
@@ -442,11 +491,11 @@ export default async function main(
output,
now,
contextName,
[path],
[sourcePath],
createArgs,
org,
autoConfirm,
!!newProjectName
!project && !isFile,
path
);
if (
@@ -455,6 +504,10 @@ export default async function main(
) {
let { projectSettings, framework } = deployment;
if (rootDirectory) {
projectSettings.rootDirectory = rootDirectory;
}
const settings = await editProjectSettings(
output,
projectSettings,
@@ -470,11 +523,11 @@ export default async function main(
output,
now,
contextName,
[path],
[sourcePath],
createArgs,
org,
!!newProjectName,
false
false,
path
);
}
@@ -483,6 +536,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,
@@ -598,7 +659,8 @@ export default async function main(
deployment,
deployStamp,
!argv['--no-clipboard'],
quiet
quiet,
isFile
);
}
@@ -646,6 +708,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

@@ -1,4 +1,4 @@
import { resolve, basename, join } from 'path';
import { resolve, basename } from 'path';
import { eraseLines } from 'ansi-escapes';
// @ts-ignore
import { write as copy } from 'clipboardy';
@@ -70,7 +70,6 @@ import {
InvalidRegionOrDCForScale,
} from '../../util/errors';
import { SchemaValidationFailed } from '../../util/errors';
import readPackage from '../../util/read-package';
interface Env {
[name: string]: string | null | undefined;
@@ -213,43 +212,6 @@ const promptForEnvFields = async (list: string[]) => {
return answers;
};
async function canUseZeroConfig(cwd: string): Promise<boolean> {
try {
const pkg = await readPackage(join(cwd, 'package.json'));
if (!pkg || pkg instanceof Error) {
return false;
}
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
// We can only check for a few frontends,
// since can never be sure if APIs will work
// with zero-config
if (
deps.next ||
deps.nuxt ||
deps.gatsby ||
deps.hexo ||
deps.gridsome ||
deps.umi ||
deps.docusaurus ||
deps['@docusaurus/core'] ||
deps['preact-cli'] ||
deps['ember-cli'] ||
deps['@vue/cli-service'] ||
deps['@angular/cli'] ||
deps['polymer-cli'] ||
deps['sirv-cli'] ||
deps['react-scripts']
) {
return true;
}
} catch (_) {}
return false;
}
export default async function main(
ctx: any,
contextName: string,

View File

@@ -1,10 +1,15 @@
import path from 'path';
import chalk from 'chalk';
import pkg from '../../../package.json';
import DevServer from '../../util/dev/server';
import parseListen from '../../util/dev/parse-listen';
import { Output } from '../../util/output';
import { NowContext } from '../../types';
import Client from '../../util/client';
import { getLinkedProject } from '../../util/projects/link';
import { getFrameworks } from '../../util/get-frameworks';
import { isSettingValue } from '../../util/is-setting-value';
import cmd from '../../util/output/cmd';
type Options = {
'--debug'?: boolean;
@@ -18,10 +23,62 @@ 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;
const devServer = new DevServer(cwd, { output, debug });
const client = new Client({
apiUrl: ctx.apiUrl,
token: ctx.authConfig.token,
currentTeam: ctx.config.currentTeam,
debug,
});
// retrieve dev command
const [link, frameworks] = await Promise.all([
getLinkedProject(output, client, cwd),
getFrameworks(),
]);
if (link.status === 'error') {
return link.exitCode;
}
if (link.status === 'not_linked' && !process.env.__NOW_SKIP_DEV_COMMAND) {
output.print(
`${chalk.red(
'Error!'
)} Your codebase isnt linked to a project on ZEIT Now. Run ${cmd(
'now'
)} to link it.\n`
);
return 1;
}
let devCommand: undefined | string;
if (link.status === 'linked') {
const { project } = link;
if (project.devCommand) {
devCommand = project.devCommand;
} else if (project.framework) {
const framework = frameworks.find(f => f.slug === project.framework);
if (framework) {
const defaults = framework.settings.devCommand;
if (isSettingValue(defaults)) {
devCommand = defaults.value;
}
}
}
if (project.rootDirectory) {
cwd = path.join(cwd, project.rootDirectory);
}
}
const devServer = new DevServer(cwd, { output, debug, devCommand });
process.once('SIGINT', () => devServer.stop());

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,10 +8,7 @@ import chalk from 'chalk';
import ua from '../util/ua.ts';
import getArgs from '../util/get-args';
import error from '../util/output/error';
import aborted from '../util/output/aborted';
import wait from '../util/output/wait';
import highlight from '../util/output/highlight';
import info from '../util/output/info';
import ok from '../util/output/ok';
import cmd from '../util/output/cmd.ts';
import param from '../util/output/param.ts';
@@ -24,7 +21,8 @@ import hp from '../util/humanize-path';
import logo from '../util/output/logo';
import exit from '../util/exit';
import createOutput from '../util/output';
import executeLogin from '../util/login/login.ts'
import executeLogin from '../util/login/login.ts';
import { prependEmoji, emoji } from '../util/emoji';
const debug = debugFactory('now:sh:login');
@@ -57,7 +55,7 @@ const help = () => {
const verify = async ({ apiUrl, email, verificationToken }) => {
const query = {
email,
token: verificationToken
token: verificationToken,
};
debug('GET /now/registration/verify');
@@ -68,7 +66,7 @@ const verify = async ({ apiUrl, email, verificationToken }) => {
res = await fetch(
`${apiUrl}/now/registration/verify?${stringifyQuery(query)}`,
{
headers: { 'User-Agent': ua }
headers: { 'User-Agent': ua },
}
);
} catch (err) {
@@ -105,12 +103,12 @@ const readEmail = async () => {
let email;
try {
email = await promptEmail({ start: info('Enter your email: ') });
email = await promptEmail({ start: `Enter your email: ` });
} catch (err) {
console.log(); // \n
if (err.message === 'User abort') {
throw new Error(aborted('No changes made.'));
throw new Error(`${chalk.red('Aborted!')} No changes made`);
}
if (err.message === 'stdin lacks setRawMode support') {
@@ -192,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);
@@ -200,7 +198,7 @@ const login = async ctx => {
securityCode = data.securityCode;
} catch (err) {
stopSpinner();
console.log(error(err.message))
console.log(error(err.message));
return 1;
}
@@ -209,18 +207,15 @@ const login = async ctx => {
// Clear up `Sending email` success message
process.stdout.write(eraseLines(possibleAddress ? 1 : 2));
console.log(
info(
`We sent an email to ${highlight(
email
)}. Please follow the steps provided`,
` inside it and make sure the security code matches ${highlight(
securityCode
)}.`
)
output.print(
`We sent an email to ${highlight(
email
)}. Please follow the steps provided inside it and make sure the security code matches ${highlight(
securityCode
)}.\n`
);
stopSpinner = wait('Waiting for your confirmation');
stopSpinner = output.spinner('Waiting for your confirmation');
let token;
@@ -256,8 +251,15 @@ const login = async ctx => {
output.debug(`Saved credentials in "${hp(getNowDir())}"`);
console.log(
`${chalk.cyan('> Congratulations!')} ` +
`You are now logged in. In order to deploy something, run ${cmd('now')}.`
`${chalk.cyan('Congratulations!')} ` +
`You are now logged in. In order to deploy something, run ${cmd('now')}.`
);
output.print(
`${prependEmoji(
`Connect your Git Repositories to deploy every branch push automatically (https://zeit.ink/1X).`,
emoji('tip')
)}\n`
);
return ctx;

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

@@ -40,8 +40,8 @@ const main = async ctx => {
boolean: ['help', 'debug', 'all'],
alias: {
help: 'h',
debug: 'd'
}
debug: 'd',
},
});
argv._ = argv._.slice(1);
@@ -52,7 +52,10 @@ const main = async ctx => {
}
const debug = argv['--debug'];
const { authConfig: { token }, apiUrl } = ctx;
const {
authConfig: { token },
apiUrl,
} = ctx;
const output = createOutput({ debug });
const client = new Client({ apiUrl, token, debug });
let contextName = null;

View File

@@ -48,6 +48,7 @@ import { NowError } from './util/now-error';
import { SENTRY_DSN } from './util/constants.ts';
import getUpdateCommand from './util/get-update-command';
import { metrics, shouldCollectMetrics } from './util/metrics.ts';
import { getLinkedOrg } from './util/projects/link';
const NOW_DIR = getNowDir();
const NOW_CONFIG_PATH = configFiles.getConfigFilePath();
@@ -504,9 +505,6 @@ const main = async argv_ => {
}
}
const scope = argv['--scope'] || argv['--team'] || localConfig.scope;
const targetCommand = commands.get(subcommand);
if (argv['--team']) {
output.warn(
`The ${param('--team')} flag is deprecated. Please use ${param(
@@ -515,18 +513,35 @@ const main = async argv_ => {
);
}
const {
authConfig: { token },
} = ctx;
let scope = argv['--scope'] || argv['--team'] || localConfig.scope;
if (process.env.NOW_ORG_ID || !scope) {
const client = new Client({ apiUrl, token });
const link = await getLinkedOrg(client, output);
if (link.status === 'error') {
return link.exitCode;
}
if (link.status === 'linked') {
scope = link.org.slug;
}
}
const targetCommand = commands.get(subcommand);
if (
typeof scope === 'string' &&
targetCommand !== 'login' &&
targetCommand !== 'dev' &&
!(targetCommand === 'teams' && argv._[3] !== 'invite')
) {
const {
authConfig: { token },
} = ctx;
const client = new Client({ apiUrl, token });
let user = null;
const client = new Client({ apiUrl, token });
try {
user = await getUser(client);

View File

@@ -222,6 +222,9 @@ export interface Project {
accountId: string;
updatedAt: number;
createdAt: number;
devCommand?: string | null;
framework?: string | null;
rootDirectory?: string | null;
}
export interface Org {
@@ -233,6 +236,4 @@ export interface Org {
export interface ProjectLink {
projectId: string;
orgId: string;
orgSlug?: string;
projectName?: string;
}

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

@@ -8,8 +8,7 @@ export default async function createCertFromFile(
client: Client,
keyPath: string,
certPath: string,
caPath: string,
context: string
caPath: string
) {
const cancelWait = wait('Adding your custom certificate');

View File

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

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,9 +49,9 @@ function printInspectUrl(
export default async function processDeployment({
isLegacy,
org,
cwd,
projectName,
shouldLinkFolder,
isDetectingFramework,
isSettingUpProject,
skipAutoDetectionConfirmation,
...args
}: {
@@ -69,9 +68,9 @@ export default async function processDeployment({
force?: boolean;
org: Org;
projectName: string;
shouldLinkFolder: boolean;
isDetectingFramework: boolean;
isSettingUpProject: boolean;
skipAutoDetectionConfirmation?: boolean;
cwd?: string;
}) {
if (isLegacy) return processLegacyDeployment(args);
@@ -106,8 +105,8 @@ export default async function processDeployment({
let buildSpinner = null;
let deploySpinner = null;
let deployingSpinner = wait(
isDetectingFramework
let deployingSpinner = output.spinner(
isSettingUpProject
? `Setting up project`
: `Deploying ${chalk.bold(`${org.slug}/${projectName}`)}`,
0
@@ -130,7 +129,7 @@ export default async function processDeployment({
indications.push(event);
}
if (event.type === 'file_count') {
if (event.type === 'file-count') {
debug(
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
);
@@ -167,43 +166,38 @@ export default async function processDeployment({
now._host = event.payload.url;
if (shouldLinkFolder) {
await linkFolderToProject(
output,
paths[0],
{
orgId: org.id,
projectId: event.payload.projectId,
},
projectName,
org.slug
);
}
await linkFolderToProject(
output,
cwd || paths[0],
{
orgId: org.id,
projectId: event.payload.projectId,
},
projectName,
org.slug
);
printInspectUrl(output, event.payload.url, deployStamp, org.slug);
if (queuedSpinner === null) {
queuedSpinner =
event.payload.readyState === 'QUEUED'
? wait('Queued', 0)
: wait('Building', 0);
? output.spinner('Queued', 0)
: output.spinner('Building', 0);
}
}
if (
event.type === 'build-state-changed' &&
event.payload.readyState === 'BUILDING'
) {
if (event.type === 'building') {
if (queuedSpinner) {
queuedSpinner();
}
if (buildSpinner === null) {
buildSpinner = wait('Building', 0);
buildSpinner = output.spinner('Building', 0);
}
}
if (event.type === 'all-builds-completed') {
if (event.type === 'ready') {
if (queuedSpinner) {
queuedSpinner();
}
@@ -211,7 +205,7 @@ export default async function processDeployment({
buildSpinner();
}
deploySpinner = wait('Completing', 0);
deploySpinner = output.spinner('Completing', 0);
}
// Handle error events

View File

@@ -71,7 +71,7 @@ export default async function processLegacyDeployment({
hashes = event.payload;
}
if (event.type === 'file_count') {
if (event.type === 'file-count') {
debug(
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
);

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

@@ -7,8 +7,6 @@ import { delimiter, dirname, join } from 'path';
import { fork, ChildProcess } from 'child_process';
import { createFunction } from '@zeit/fun';
import { Builder, File, Lambda, FileBlob, FileFsRef } from '@now/build-utils';
import stripAnsi from 'strip-ansi';
import chalk from 'chalk';
import which from 'which';
import plural from 'pluralize';
import minimatch from 'minimatch';
@@ -57,8 +55,7 @@ async function createBuildProcess(
buildEnv: EnvConfig,
workPath: string,
output: Output,
yarnPath?: string,
debugEnabled: boolean = false
yarnPath?: string
): Promise<ChildProcess> {
if (!nodeBinPromise) {
nodeBinPromise = getNodeBin();
@@ -123,7 +120,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();
@@ -149,8 +146,7 @@ export async function executeBuild(
buildEnv,
workPath,
devServer.output,
yarnPath,
debug
yarnPath
);
}
@@ -278,20 +274,26 @@ export async function executeBuild(
}
const { output } = result;
const { cleanUrls } = nowConfig;
// Mimic fmeta-util and convert cleanUrls
if (nowConfig.cleanUrls) {
Object.entries(output)
.filter(([name, value]) => name.endsWith('.html'))
.forEach(([name, value]) => {
const cleanName = name.slice(0, -5);
delete output[name];
output[cleanName] = value;
if (value.type === 'FileBlob' || value.type === 'FileFsRef') {
value.contentType = value.contentType || 'text/html; charset=utf-8';
}
});
}
// Mimic fmeta-util and perform file renaming
Object.entries(output).forEach(([path, value]) => {
if (cleanUrls && path.endsWith('.html')) {
path = path.slice(0, -5);
if (value.type === 'FileBlob' || value.type === 'FileFsRef') {
value.contentType = value.contentType || 'text/html; charset=utf-8';
}
}
const extensionless = devServer.getExtensionlessFile(path);
if (extensionless) {
path = extensionless;
}
delete output[path];
output[path] = value;
});
// Convert the JSON-ified output map back into their corresponding `File`
// subclass type instances.
@@ -398,6 +400,7 @@ export async function getBuildMatches(
cwd: string,
yarnDir: string,
output: Output,
devServer: DevServer,
fileList: string[]
): Promise<BuildMatch[]> {
const matches: BuildMatch[] = [];
@@ -429,6 +432,14 @@ export async function getBuildMatches(
// try to find a group otherwise
src = src.replace(/(\[|\])/g, '[$1]');
// 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
.filter(name => name === src || minimatch(name, src))
.map(name => join(cwd, name));
@@ -443,6 +454,7 @@ export async function getBuildMatches(
matches.push({
...buildConfig,
src,
entrypoint: mapToEntrypoint.get(src) || src,
builderWithPkg,
buildOutput: {},
buildResults: new Map(),

View File

@@ -4,13 +4,8 @@ import PCRE from 'pcre-to-regexp';
import isURL from './is-url';
import DevServer from './server';
import {
HttpHeadersConfig,
RouteConfig,
RouteResult,
NowConfig,
} from './types';
import { isHandler } from '@now/routing-utils';
import { HttpHeadersConfig, RouteConfig, RouteResult } from './types';
import { isHandler, Route, HandleValue } from '@now/routing-utils';
export function resolveRouteParameters(
str: string,
@@ -31,27 +26,47 @@ export function resolveRouteParameters(
});
}
export default async function(
export function getRoutesTypes(routes: Route[] = []) {
const handleMap = new Map<HandleValue | null, Route[]>();
let prevHandle: HandleValue | null = null;
routes.forEach(route => {
if (isHandler(route)) {
prevHandle = route.handle;
} else {
const routes = handleMap.get(prevHandle);
if (!routes) {
handleMap.set(prevHandle, [route]);
} else {
routes.push(route);
}
}
});
return handleMap;
}
export async function devRouter(
reqUrl: string = '/',
reqMethod?: string,
routes?: RouteConfig[],
devServer?: DevServer
devServer?: DevServer,
previousHeaders?: HttpHeadersConfig,
missRoutes?: RouteConfig[],
phase?: HandleValue | null
): Promise<RouteResult> {
let found: RouteResult | undefined;
let { query, pathname: reqPathname = '/' } = url.parse(reqUrl, true);
const combinedHeaders: HttpHeadersConfig = {};
const combinedHeaders: HttpHeadersConfig = { ...previousHeaders };
let status: number | undefined;
// Try route match
if (routes) {
let idx = -1;
for (const routeConfig of routes) {
idx++;
if (isHandler(routeConfig)) {
if (routeConfig.handle === 'filesystem' && devServer) {
if (await devServer.hasFilesystem(reqPathname)) {
break;
}
}
// We don't expect any Handle, only Source routes
continue;
}
@@ -74,41 +89,74 @@ export default async function(
}
if (headers) {
// Create a clone of the `headers` object to not mutate the original one
headers = { ...headers };
for (const key of Object.keys(headers)) {
headers[key] = resolveRouteParameters(headers[key], match, keys);
for (const originalKey of Object.keys(headers)) {
const lowerKey = originalKey.toLowerCase();
if (
previousHeaders &&
Object.prototype.hasOwnProperty.call(previousHeaders, lowerKey) &&
(phase === 'hit' || phase === 'miss')
) {
// don't override headers in the hit or miss phase
} else {
const originalValue = headers[originalKey];
const value = resolveRouteParameters(originalValue, match, keys);
combinedHeaders[lowerKey] = value;
}
}
Object.assign(combinedHeaders, headers);
}
if (routeConfig.continue) {
if (routeConfig.status) {
status = routeConfig.status;
}
reqPathname = destPath;
continue;
}
if (routeConfig.check && devServer) {
if (routeConfig.check && devServer && phase !== 'hit') {
const { pathname = '/' } = url.parse(destPath);
const hasDestFile = await devServer.hasFilesystem(pathname);
if (!hasDestFile) {
// If the file is not found, `check: true` will
// behave the same as `continue: true`
reqPathname = destPath;
continue;
if (routeConfig.status && phase !== 'miss') {
// Equivalent to now-proxy exit_with_status() function
} else if (missRoutes && missRoutes.length > 0) {
// Trigger a 'miss'
const missResult = await devRouter(
destPath,
reqMethod,
missRoutes,
devServer,
previousHeaders,
[],
'miss'
);
if (missResult.found) {
return missResult;
}
} else {
if (routeConfig.status && phase === 'miss') {
status = routeConfig.status;
}
reqPathname = destPath;
continue;
}
}
}
if (isURL(destPath)) {
const isDestUrl = isURL(destPath);
if (isDestUrl) {
found = {
found: true,
dest: destPath,
userDest: false,
status: routeConfig.status,
isDestUrl,
status: routeConfig.status || status,
headers: combinedHeaders,
uri_args: query,
matched_route: routeConfig,
matched_route_idx: idx,
phase,
};
break;
} else {
@@ -120,11 +168,13 @@ export default async function(
found: true,
dest: pathname || '/',
userDest: Boolean(routeConfig.dest),
status: routeConfig.status,
isDestUrl,
status: routeConfig.status || status,
headers: combinedHeaders,
uri_args: query,
matched_route: routeConfig,
matched_route_idx: idx,
phase,
};
break;
}
@@ -136,8 +186,11 @@ export default async function(
found = {
found: false,
dest: reqPathname,
status,
isDestUrl: false,
uri_args: query,
headers: combinedHeaders,
phase,
};
}

View File

@@ -1,4 +1,3 @@
import ms from 'ms';
import url from 'url';
import http from 'http';
import fs from 'fs-extra';
@@ -12,9 +11,12 @@ 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 } from 'path';
import { getTransformedRoutes } from '@now/routing-utils';
import { basename, dirname, extname, join, delimiter } 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 {
Builder,
@@ -22,6 +24,10 @@ import {
PackageJson,
detectBuilders,
detectRoutes,
detectApiDirectory,
detectApiExtensions,
execAsync,
spawnCommand,
} from '@now/build-utils';
import { once } from '../once';
@@ -48,17 +54,12 @@ import {
validateNowConfigFunctions,
} from './validate';
import isURL from './is-url';
import devRouter from './router';
import { devRouter, getRoutesTypes } from './router';
import getMimeType from './mime-type';
import { getYarnPath } from './yarn-installer';
import { executeBuild, getBuildMatches, shutdownBuilder } from './builder';
import { generateErrorMessage, generateHttpStatusDescription } from './errors';
import {
builderDirPromise,
installBuilders,
updateBuilders,
} from './builder-cache';
import { installBuilders, updateBuilders } from './builder-cache';
// HTML templates
import errorTemplate from './templates/error';
@@ -111,6 +112,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;
@@ -122,6 +125,9 @@ export default class DevServer {
private watchAggregationTimeout: number;
private filter: (path: string) => boolean;
private podId: string;
private devCommand?: string;
private devProcess?: ChildProcess;
private devProcessPort?: number;
private getNowConfigPromise: Promise<NowConfig> | null;
private blockingBuildsPromise: Promise<void> | null;
@@ -135,11 +141,14 @@ export default class DevServer {
this.buildEnv = {};
this.files = {};
this.address = '';
this.devCommand = options.devCommand;
// This gets updated when `start()` is invoked
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;
@@ -286,6 +295,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) {
@@ -306,6 +319,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(
@@ -338,6 +356,7 @@ export default class DevServer {
this.cwd,
this.yarnPath,
this.output,
this,
fileList
);
const sources = matches.map(m => m.src);
@@ -526,9 +545,10 @@ export default class DevServer {
// no builds -> zero config
if (!config.builds || config.builds.length === 0) {
const { projectSettings } = config;
const featHandleMiss = true; // enable for zero config
const { projectSettings, cleanUrls, trailingSlash } = config;
const { builders, warnings, errors } = await detectBuilders(files, pkg, {
let { builders, warnings, errors } = await detectBuilders(files, pkg, {
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest',
functions: config.functions,
...(projectSettings ? { projectSettings } : {}),
@@ -544,11 +564,21 @@ export default class DevServer {
}
if (builders) {
if (this.devCommand) {
builders = builders.filter(filterFrontendBuilds);
}
const {
defaultRoutes,
redirectRoutes,
error: routesError,
} = await detectRoutes(files, builders);
} = await detectRoutes(
files,
builders,
featHandleMiss,
cleanUrls,
trailingSlash
);
config.builds = config.builds || [];
config.builds.push(...builders);
@@ -568,10 +598,14 @@ export default class DevServer {
}
if (!config.builds || config.builds.length === 0) {
if (isInitialLoad) {
if (isInitialLoad && !this.devCommand) {
this.output.note(`Serving all files as static`);
}
} else if (Array.isArray(config.builds)) {
if (this.devCommand) {
config.builds = config.builds.filter(filterFrontendBuilds);
}
// `@now/static-build` needs to be the last builder
// since it might catch all other requests
config.builds.sort(sortBuilders);
@@ -580,6 +614,8 @@ export default class DevServer {
await this.validateNowConfig(config);
this.cachedNowConfig = config;
this.apiDir = detectApiDirectory(config.builds || []);
this.apiExtensions = detectApiExtensions(config.builds || []);
return config;
}
@@ -719,18 +755,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 } = {};
this.files = {};
for (const fsPath of files) {
const path = relative(this.cwd, fsPath);
let path = relative(this.cwd, fsPath);
const { mode } = await fs.stat(fsPath);
results[path] = new FileFsRef({ mode, fsPath });
this.files[path] = new FileFsRef({ mode, fsPath });
const extensionless = this.getExtensionlessFile(path);
if (extensionless) {
this.files[extensionless] = 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);
@@ -795,6 +834,8 @@ export default class DevServer {
// Wait for "ready" event of the watcher
await once(this.watcher, 'ready');
const devCommandPromise = this.runDevCommand();
let address: string | null = null;
while (typeof address !== 'string') {
try {
@@ -826,6 +867,8 @@ export default class DevServer {
.replace('[::]', 'localhost')
.replace('127.0.0.1', 'localhost');
await devCommandPromise;
this.output.ready(`Available at ${link(this.address)}`);
this.serverUrlPrinted = true;
}
@@ -850,6 +893,18 @@ export default class DevServer {
ops.push(shutdownBuilder(match, this.output));
}
ops.push(
new Promise(resolve => {
if (!this.devProcess) {
resolve();
return;
}
this.devProcess.on('exit', () => resolve());
process.kill(this.devProcess.pid, exitCode);
})
);
ops.push(close(this.server));
if (this.watcher) {
@@ -1041,7 +1096,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
@@ -1083,6 +1140,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
*/
@@ -1155,55 +1225,145 @@ export default class DevServer {
await this.blockingBuildsPromise;
}
const { dest, status, headers, uri_args } = await devRouter(
req.url,
req.method,
routes,
this
);
const handleMap = getRoutesTypes(routes);
const missRoutes = handleMap.get('miss') || [];
const hitRoutes = handleMap.get('hit') || [];
handleMap.delete('miss');
handleMap.delete('hit');
const phases: (HandleValue | null)[] = [null, 'filesystem'];
let routeResult: RouteResult | null = null;
let match: BuildMatch | null = null;
let statusCode: number | undefined;
for (const phase of phases) {
statusCode = undefined;
const phaseRoutes = handleMap.get(phase) || [];
routeResult = await devRouter(
req.url,
req.method,
phaseRoutes,
this,
undefined,
missRoutes,
phase
);
if (routeResult.isDestUrl) {
// Mix the `routes` result dest query params into the req path
const destParsed = url.parse(routeResult.dest, true);
delete destParsed.search;
Object.assign(destParsed.query, routeResult.uri_args);
const destUrl = url.format(destParsed);
this.output.debug(`ProxyPass: ${destUrl}`);
this.setResponseHeaders(res, nowRequestId);
return proxyPass(req, res, destUrl, this.output);
}
match = await findBuildMatch(
this.buildMatches,
this.files,
routeResult.dest,
this
);
if (!match && missRoutes.length > 0) {
// Since there was no build match, enter the miss phase
routeResult = await devRouter(
routeResult.dest || req.url,
req.method,
missRoutes,
this,
routeResult.headers,
[],
'miss'
);
match = await findBuildMatch(
this.buildMatches,
this.files,
routeResult.dest,
this
);
} else if (match && hitRoutes.length > 0) {
// Since there was a build match, enter the hit phase.
// The hit phase must not set status code.
const prevStatus = routeResult.status;
routeResult = await devRouter(
routeResult.dest || req.url,
req.method,
hitRoutes,
this,
routeResult.headers,
[],
'hit'
);
routeResult.status = prevStatus;
}
statusCode = routeResult.status;
if (match && statusCode === 404 && routeResult.phase === 'miss') {
statusCode = undefined;
}
const location = routeResult.headers['location'] || routeResult.dest;
if (statusCode && location && (300 <= statusCode && statusCode <= 399)) {
// Equivalent to now-proxy exit_with_status() function
this.output.debug(
`Route found with redirect status code ${statusCode}`
);
await this.sendRedirect(req, res, nowRequestId, location, statusCode);
return;
}
if (!match && statusCode && routeResult.phase !== 'miss') {
// Equivalent to now-proxy exit_with_status() function
this.output.debug(`Route found with with status code ${statusCode}`);
await this.sendError(req, res, nowRequestId, '', statusCode);
return;
}
if (match) {
// end the phase
break;
}
}
if (!routeResult) {
throw new Error('Expected Route Result but none was found.');
}
const { dest, headers, uri_args } = routeResult;
// Set any headers defined in the matched `route` config
Object.entries(headers).forEach(([name, value]) => {
res.setHeader(name, value);
});
if (isURL(dest)) {
// Mix the `routes` result dest query params into the req path
const destParsed = url.parse(dest, true);
delete destParsed.search;
Object.assign(destParsed.query, uri_args);
const destUrl = url.format(destParsed);
this.output.debug(`ProxyPass: ${destUrl}`);
this.setResponseHeaders(res, nowRequestId);
return proxyPass(req, res, destUrl, this.output);
}
if (status) {
res.statusCode = status;
if (300 <= status && status <= 399) {
await this.sendRedirect(
req,
res,
nowRequestId,
res.getHeader('location') as string,
status
);
return;
}
if (statusCode) {
res.statusCode = statusCode;
}
const requestPath = dest.replace(/^\//, '');
const match = await findBuildMatch(
this.buildMatches,
this.files,
requestPath,
this
);
if (!match) {
// if the dev command is started, proxy to it
if (this.devProcessPort) {
this.output.debug('Proxy to dev command server');
return proxyPass(
req,
res,
`http://localhost:${this.devProcessPort}`,
this.output,
false
);
}
if (
status === 404 ||
(statusCode === 404 && routeResult.phase === 'miss') ||
!this.renderDirectoryListing(req, res, requestPath, nowRequestId)
) {
await this.send404(req, res, nowRequestId);
@@ -1259,6 +1419,18 @@ export default class DevServer {
}
if (!foundAsset) {
// if the dev command is started, proxy to it
if (this.devProcessPort) {
this.output.debug('Proxy to dev command server');
return proxyPass(
req,
res,
`http://localhost:${this.devProcessPort}`,
this.output,
false
);
}
await this.send404(req, res, nowRequestId);
return;
}
@@ -1349,7 +1521,7 @@ export default class DevServer {
return;
}
if (!status) {
if (!statusCode) {
res.statusCode = result.statusCode;
}
this.setResponseHeaders(res, nowRequestId, result.headers);
@@ -1453,20 +1625,60 @@ export default class DevServer {
}
async hasFilesystem(dest: string): Promise<boolean> {
const requestPath = dest.replace(/^\//, '');
if (
await findBuildMatch(
this.buildMatches,
this.files,
requestPath,
this,
true
)
) {
if (await findBuildMatch(this.buildMatches, this.files, dest, this, true)) {
return true;
}
return false;
}
async runDevCommand() {
if (!this.devCommand) return;
const cwd = this.cwd;
const { stdout: yarnBinStdout } = await execAsync('yarn', ['bin'], {
cwd,
});
const yarnBinPath = yarnBinStdout.trim();
this.output.log(
`Running Dev Command ${chalk.cyan.bold(`${this.devCommand}`)}`
);
const port = await getPort();
const env: EnvConfig = {
...process.env,
...this.buildEnv,
PATH: `${yarnBinPath}${delimiter}${process.env.PATH}`,
NOW_REGION: 'dev1',
PORT: `${port}`,
};
this.output.debug(
`Starting dev command with parameters : ${JSON.stringify({
cwd: this.cwd,
devCommand: this.devCommand,
port,
})}`
);
const p = spawnCommand(this.devCommand, {
stdio: 'inherit',
cwd,
env,
});
p.on('exit', () => {
this.devProcessPort = undefined;
});
await checkForPort(port, 1000 * 60 * 5);
this.devProcessPort = port;
this.devProcess = p;
}
}
/**
@@ -1476,13 +1688,14 @@ function proxyPass(
req: http.IncomingMessage,
res: http.ServerResponse,
dest: string,
output: Output
output: Output,
ignorePath: boolean = true
): void {
const proxy = httpProxy.createProxyServer({
changeOrigin: true,
ws: true,
xfwd: true,
ignorePath: true,
ignorePath,
target: dest,
});
@@ -1552,6 +1765,7 @@ async function findBuildMatch(
devServer: DevServer,
isFilesystem?: boolean
): Promise<BuildMatch | null> {
requestPath = requestPath.replace(/^\//, '');
for (const match of matches.values()) {
if (await shouldServe(match, files, requestPath, devServer, isFilesystem)) {
return match;
@@ -1720,3 +1934,27 @@ function needsBlockingBuild(buildMatch: BuildMatch): boolean {
const { builder } = buildMatch.builderWithPkg;
return typeof builder.shouldServe !== 'function';
}
async function sleep(n: number) {
return new Promise(resolve => setTimeout(resolve, n));
}
async function checkForPort(
port: number | undefined,
timeout: number
): Promise<void> {
const start = Date.now();
while (!(await isPortReachable(port))) {
if (Date.now() - start > timeout) {
throw new Error(`Detecting port ${port} timed out after ${timeout}ms`);
}
await sleep(100);
}
}
function filterFrontendBuilds(build: Builder) {
return (
!build.use.startsWith('@now/static-build') &&
!build.use.startsWith('@now/next')
);
}

View File

@@ -3,11 +3,22 @@ import { BuilderParams, BuildResult, ShouldServeParams } from './types';
export const version = 2;
export function build({ files, entrypoint }: BuilderParams): BuildResult {
export function build({
files,
entrypoint,
config,
}: BuilderParams): BuildResult {
let path = entrypoint;
const outputDir = config.zeroConfig ? config.outputDirectory : '';
const outputMatch = outputDir + '/';
if (outputDir && path.startsWith(outputMatch)) {
// static output files are moved to the root directory
path = path.slice(outputMatch.length);
}
const output = {
[entrypoint]: files[entrypoint],
[path]: files[entrypoint],
};
const watch = [entrypoint];
const watch = [path];
return { output, routes: [], watch };
}
@@ -16,14 +27,25 @@ export function shouldServe({
entrypoint,
files,
requestPath,
config = {},
}: ShouldServeParams) {
let outputPrefix = '';
const outputDir = config.zeroConfig ? config.outputDirectory : '';
const outputMatch = outputDir + '/';
if (outputDir && entrypoint.startsWith(outputMatch)) {
// static output files are moved to the root directory
entrypoint = entrypoint.slice(outputMatch.length);
outputPrefix = outputMatch;
}
const isMatch = (f: string) => entrypoint === f && outputPrefix + f in files;
if (isIndex(entrypoint)) {
const indexPath = join(requestPath, basename(entrypoint));
if (entrypoint === indexPath && indexPath in files) {
if (isMatch(indexPath)) {
return true;
}
}
return entrypoint === requestPath && requestPath in files;
return isMatch(requestPath);
}
function isIndex(path: string): boolean {

View File

@@ -7,10 +7,10 @@ import {
FileFsRef,
Lambda,
PackageJson,
BuilderFunctions,
Config,
} from '@now/build-utils';
import { NowConfig } from 'now-client';
import { NowRedirect, NowRewrite, NowHeader, Route } from '@now/routing-utils';
import { HandleValue, Route } from '@now/routing-utils';
import { Output } from '../output';
export { NowConfig };
@@ -18,6 +18,7 @@ export { NowConfig };
export interface DevServerOptions {
output: Output;
debug: boolean;
devCommand?: string;
}
export interface EnvConfig {
@@ -25,6 +26,7 @@ export interface EnvConfig {
}
export interface BuildMatch extends BuildConfig {
entrypoint: string;
builderWithPkg: BuilderWithPackage;
buildOutput: BuilderOutputs;
buildResults: Map<string | null, BuildResult>;
@@ -61,7 +63,7 @@ export interface CacheOutputs {
export interface BuilderParamsBase {
files: BuilderInputs;
entrypoint: string;
config: object;
config: Config;
meta?: {
isDev?: boolean;
requestPath?: string | null;
@@ -124,7 +126,7 @@ export interface BuildResultV4 {
export interface ShouldServeParams {
files: BuilderInputs;
entrypoint: string;
config?: object;
config?: Config;
requestPath: string;
workPath: string;
}
@@ -156,6 +158,10 @@ export interface RouteResult {
matched_route_idx?: number;
// "userDest": <boolean in case the destination was user defined>
userDest?: boolean;
// url as destination should end routing
isDestUrl: boolean;
// the phase that this route is defined in
phase?: HandleValue | null;
}
export interface InvokePayload {

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,4 +1,12 @@
export function emoji(label: string): string | undefined {
export type EmojiLabel =
| 'notice'
| 'tip'
| 'warning'
| 'link'
| 'inspect'
| 'success';
export function emoji(label: EmojiLabel): string | undefined {
switch (label) {
case 'notice':
return '📝';

View File

@@ -1213,7 +1213,7 @@ export class BuildError extends NowError<'BUILD_ERROR', {}> {
meta,
}: {
message: string;
meta: { entrypoint: string };
meta: { entrypoint?: string };
}) {
super({
code: 'BUILD_ERROR',

View File

@@ -0,0 +1,14 @@
import fetch from 'node-fetch';
import { Framework } from '@now/frameworks';
export async function getFrameworks(): Promise<Framework[]> {
const res = await fetch('https://api-frameworks.zeit.sh/api/frameworks');
if (!res.ok) {
throw new Error('Could not retrieve frameworks');
}
const json: Framework[] = await res.json();
return json;
}

View File

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

View File

@@ -1,8 +1,7 @@
import { Stats } from 'fs';
import {sep, dirname, join, resolve } from 'path';
import { sep, dirname, join, resolve } from 'path';
import { readJSON, lstat, readlink, readFile, realpath } from 'fs-extra';
import { version } from '../../package.json';
import { isCanary } from './is-canary';
// `npm` tacks a bunch of extra properties on the `package.json` file,
// so check for one of them to determine yarn vs. npm.
@@ -20,7 +19,7 @@ async function isYarn(): Promise<boolean> {
}
}
const pkgPath = join(dirname(binPath), '..', 'package.json');
const pkg = await readJSON(pkgPath);
const pkg = await readJSON(pkgPath).catch(() => ({}));
return !('_id' in pkg);
}
@@ -28,7 +27,7 @@ async function getConfigPrefix() {
const paths = [
process.env.npm_config_userconfig || process.env.NPM_CONFIG_USERCONFIG,
join(process.env.HOME || '/', '.npmrc'),
process.env.npm_config_globalconfig || process.env.NPM_CONFIG_GLOBALCONFIG
process.env.npm_config_globalconfig || process.env.NPM_CONFIG_GLOBALCONFIG,
].filter(Boolean);
for (const configPath of paths) {
@@ -64,21 +63,22 @@ async function isGlobal() {
}
const isWindows = process.platform === 'win32';
const defaultPath = isWindows ? process.env.APPDATA : '/usr/local/lib'
const defaultPath = isWindows ? process.env.APPDATA : '/usr/local/lib';
const installPath = await realpath(resolve(__dirname));
if (installPath.includes(['', 'yarn', 'global', 'node_modules', ''].join(sep))) {
if (
installPath.includes(['', 'yarn', 'global', 'node_modules', ''].join(sep))
) {
return true;
}
const prefixPath = (
const prefixPath =
process.env.PREFIX ||
process.env.npm_config_prefix ||
process.env.NPM_CONFIG_PREFIX ||
await getConfigPrefix() ||
defaultPath
);
(await getConfigPrefix()) ||
defaultPath;
if (!prefixPath) {
return true;
@@ -92,7 +92,7 @@ async function isGlobal() {
}
export default async function getUpdateCommand(): Promise<string> {
const tag = version.includes('canary') ? 'canary' : 'latest';
const tag = isCanary() ? 'canary' : 'latest';
if (await isGlobal()) {
return (await isYarn())
@@ -100,7 +100,5 @@ export default async function getUpdateCommand(): Promise<string> {
: `npm i -g now@${tag}`;
}
return (await isYarn())
? `yarn add now@${tag}`
: `npm i now@${tag}`;
return (await isYarn()) ? `yarn add now@${tag}` : `npm i now@${tag}`;
}

View File

@@ -2,23 +2,27 @@ import Client from './client';
import { User } from '../types';
import { APIError, InvalidToken, MissingUser } from './errors-ts';
let user: User | undefined;
export default async function getUser(client: Client) {
let user;
if (user) return user;
try {
({ user } = await client.fetch<{ user: User }>('/www/user', {
useCurrentTeam: false
}));
const res = await client.fetch<{ user: User }>('/www/user', {
useCurrentTeam: false,
});
if (!res.user) {
throw new MissingUser();
}
user = res.user;
return user;
} catch (error) {
if (error instanceof APIError && error.status === 403) {
throw new InvalidToken();
}
throw error;
}
if (!user) {
throw new MissingUser();
}
return user;
}

View File

@@ -70,11 +70,11 @@ export default class Now extends EventEmitter {
skipAutoDetectionConfirmation,
},
org,
shouldLinkFolder,
isDetectingFramework
isSettingUpProject,
cwd
) {
const opts = { output: this._output, hasNowJson };
const { log, warn, debug } = this._output;
const { log, warn } = this._output;
const isLegacy = type !== null;
let files = [];
@@ -182,9 +182,9 @@ export default class Now extends EventEmitter {
force: forceNew,
org,
projectName: name,
shouldLinkFolder,
isDetectingFramework,
isSettingUpProject,
skipAutoDetectionConfirmation,
cwd,
});
// We report about files whose sizes are too big
@@ -319,6 +319,13 @@ export default class Now extends EventEmitter {
});
}
if (error.errorCode && error.errorCode === 'BUILD_FAILED') {
return new BuildError({
message: error.errorMessage,
meta: {},
});
}
return new Error(error.message);
}

View File

@@ -2,7 +2,8 @@ import inquirer from 'inquirer';
import confirm from './confirm';
import chalk from 'chalk';
import { Output } from '../output';
import { Framework, SettingValue } from '@now/frameworks';
import { Framework } from '@now/frameworks';
import { isSettingValue } from '../is-setting-value';
export interface ProjectSettings {
buildCommand: string | null;
@@ -10,37 +11,45 @@ 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' },
{ name: 'Development Command', value: 'devCommand' },
];
function isSettingValue(setting: any): setting is SettingValue {
return setting && typeof setting.value === 'string';
}
export default async function editProjectSettings(
output: Output,
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

@@ -0,0 +1,5 @@
import pkg from '../../package.json';
export function isCanary() {
return pkg.version.includes('canary');
}

View File

@@ -0,0 +1,5 @@
import { SettingValue } from '@now/frameworks';
export function isSettingValue(setting: any): setting is SettingValue {
return setting && typeof setting.value === 'string';
}

View File

@@ -1,5 +0,0 @@
import { red } from 'chalk';
const error = msg => `${red('> Aborted!')} ${msg}`;
export default error;

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

@@ -0,0 +1,11 @@
> Why do I have a folder named ".now" in my project?
The ".now" folder is created when you link a directory to a ZEIT Now project.
> What does the "project.json" file contain?
The "project.json" file contains:
- The ID of the ZEIT Now project that you linked ("projectId")
- The ID of the user or team your ZEIT Now project is owned by ("orgId")
> Should I commit the ".now" folder?
No, you should not share the ".now" folder with anyone.
Upon creation, it will be automatically added to your ".gitignore" file.

View File

@@ -12,15 +12,42 @@ 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);
export const NOW_FOLDER = '.now';
export const NOW_FOLDER_README = 'README.txt';
export const NOW_PROJECT_LINK_FILE = 'project.json';
async function getOrg(client: Client, orgId: string): Promise<Org | null> {
async function getLink(path?: string): Promise<ProjectLink | null> {
try {
const json = await readFile(
join(path || process.cwd(), NOW_FOLDER, NOW_PROJECT_LINK_FILE),
{ encoding: 'utf8' }
);
const link: ProjectLink = JSON.parse(json);
return link;
} catch (error) {
// link file does not exists, project is not linked
if (['ENOENT', 'ENOTDIR'].includes(error.code)) {
return null;
}
// link file can't be read
if (error.name === 'SyntaxError') {
throw new Error(
'Now project settings could not be retrieved. To link your project again, remove the `.now` directory.'
);
}
throw error;
}
}
async function getOrgById(client: Client, orgId: string): Promise<Org | null> {
if (orgId.startsWith('team_')) {
const team = await getTeamById(client, orgId);
if (!team) return null;
@@ -32,60 +59,121 @@ async function getOrg(client: Client, orgId: string): Promise<Org | null> {
return { type: 'user', id: orgId, slug: user.username };
}
export async function getLinkedProject(
export async function getLinkedOrg(
client: Client,
path: string
): Promise<[Org | null, Project | null]> {
try {
let link: ProjectLink;
output: Output,
path?: string
): Promise<
| { status: 'linked'; org: Org }
| { status: 'not_linked'; org: null }
| { status: 'error'; exitCode: number }
> {
const { NOW_ORG_ID } = process.env;
const { NOW_ORG_ID, NOW_PROJECT_ID } = process.env;
if (NOW_ORG_ID && NOW_PROJECT_ID) {
link = {
orgId: NOW_ORG_ID,
projectId: NOW_PROJECT_ID,
};
} else {
const json = await readFile(
join(path, NOW_FOLDER, NOW_PROJECT_LINK_FILE),
{ encoding: 'utf8' }
);
let orgId: string | null = null;
if (NOW_ORG_ID) {
orgId = NOW_ORG_ID;
} else {
const link = await getLink(path);
link = JSON.parse(json);
if (link) {
orgId = link.orgId;
}
const spinner = wait('Retrieving project…', 1000);
let org: Org | null;
let project: Project | ProjectNotFound | null;
try {
[org, project] = await Promise.all([
getOrg(client, link.orgId),
getProjectByIdOrName(client, link.projectId, link.orgId),
]);
} finally {
spinner();
}
if (project instanceof ProjectNotFound || org === null) {
return [null, null];
}
return [org, project];
} catch (error) {
// link file does not exists, project is not linked
if (['ENOENT', 'ENOTDIR'].includes(error.code)) {
return [null, null];
}
// link file can't be read
if (error.name === 'SyntaxError') {
throw new Error(
'Now project settings could not be retrieved. To link your project again, remove .now'
);
}
throw error;
}
if (!orgId) {
return { status: 'not_linked', org: null };
}
const spinner = output.spinner('Retrieving scope…', 1000);
try {
const org = await getOrgById(client, orgId);
if (!org && NOW_ORG_ID) {
output.print(
`${chalk.red('Error!')} Organization not found (${JSON.stringify({
NOW_ORG_ID,
})})\n`
);
return { status: 'error', exitCode: 1 };
}
if (!org) {
return { status: 'not_linked', org: null };
}
return { status: 'linked', org };
} finally {
spinner();
}
}
export async function getLinkedProject(
output: Output,
client: Client,
path?: string
): Promise<
| { status: 'linked'; org: Org; project: Project }
| { status: 'not_linked'; org: null; project: null }
| { status: 'error'; exitCode: number }
> {
const { NOW_ORG_ID, NOW_PROJECT_ID } = process.env;
const shouldUseEnv = Boolean(NOW_ORG_ID && NOW_PROJECT_ID);
if ((NOW_ORG_ID || NOW_PROJECT_ID) && !shouldUseEnv) {
output.print(
`${chalk.red('Error!')} You specified ${
NOW_ORG_ID ? '`NOW_ORG_ID`' : '`NOW_PROJECT_ID`'
} but you forgot to specify ${
NOW_ORG_ID ? '`NOW_PROJECT_ID`' : '`NOW_ORG_ID`'
}. You need to specify both to deploy to a custom project.\n`
);
return { status: 'error', exitCode: 1 };
}
const link =
NOW_ORG_ID && NOW_PROJECT_ID
? { orgId: NOW_ORG_ID, projectId: NOW_PROJECT_ID }
: await getLink(path);
if (!link) {
return { status: 'not_linked', org: null, project: null };
}
const spinner = output.spinner('Retrieving project…', 1000);
let org: Org | null;
let project: Project | ProjectNotFound | null;
try {
[org, project] = await Promise.all([
getOrgById(client, link.orgId),
getProjectByIdOrName(client, link.projectId, link.orgId),
]);
} finally {
spinner();
}
if (!org || !project || project instanceof ProjectNotFound) {
if (shouldUseEnv) {
output.print(
`${chalk.red('Error!')} Project not found (${JSON.stringify({
NOW_PROJECT_ID,
NOW_ORG_ID,
})})\n`
);
return { status: 'error', exitCode: 1 };
} else {
output.print(
prependEmoji(
'Your project was either removed from ZEIT Now or youre not a member of it anymore.\n',
emoji('warning')
)
);
}
return { status: 'not_linked', org: null, project: null };
}
return { status: 'linked', org, project };
}
export async function linkFolderToProject(
@@ -95,6 +183,22 @@ export async function linkFolderToProject(
projectName: string,
orgSlug: string
) {
// if NOW_ORG_ID or NOW_PROJECT_ID are used, we skip linking
const { NOW_ORG_ID, NOW_PROJECT_ID } = process.env;
if (NOW_ORG_ID || NOW_PROJECT_ID) {
return;
}
// if the project is already linked, we skip linking
const link = await getLink(path);
if (
link &&
link.orgId === projectLink.orgId &&
link.projectId === projectLink.projectId
) {
return;
}
try {
await ensureDir(join(path, NOW_FOLDER));
} catch (error) {
@@ -112,6 +216,12 @@ export async function linkFolderToProject(
{ encoding: 'utf8' }
);
await writeFile(
join(path, NOW_FOLDER, NOW_FOLDER_README),
await readFile(join(__dirname, 'NOW_DIR_README.txt'), 'utf-8'),
{ encoding: 'utf-8' }
);
// update .gitignore
let isGitIgnoreUpdated = false;
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

@@ -4,34 +4,88 @@ import { Output } from './output';
import chalk from 'chalk';
import { homedir } from 'os';
import confirm from './input/confirm';
import { prependEmoji, emoji } from './emoji';
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[]
): Promise<number | string> {
): Promise<
| { valid: true; path: string; isFile: boolean }
| { valid: false; exitCode: number }
> {
// can't deploy more than 1 path
if (paths.length > 1) {
output.print(`${chalk.red('Error!')} Can't deploy more than one path.\n`);
return 1;
return { valid: false, exitCode: 1 };
}
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 || !pathStat.isDirectory()) {
if (!pathStat) {
output.print(
`${chalk.red(
'Error!'
)} The path you are trying to deploy is not a directory.\n`
`${chalk.red('Error!')} Could not find ${chalk.cyan(
`${toHumanPath(path)}`
)}\n`
);
return { valid: false, exitCode: 1 };
}
const isFile = pathStat && !pathStat.isDirectory();
if (isFile) {
output.print(
`${prependEmoji(
'Deploying files with ZEIT Now is deprecated (https://zeit.ink/3Z)',
emoji('warning')
)}\n`
);
return 1;
}
// ask confirmation if the directory is home
@@ -43,9 +97,9 @@ export default async function validatePaths(
if (!shouldDeployHomeDirectory) {
output.print(`Aborted\n`);
return 0;
return { valid: false, exitCode: 0 };
}
}
return path;
return { valid: true, path, isFile };
}

View File

@@ -1,5 +1,5 @@
import test from 'ava';
import devRouter from '../src/util/dev/router';
import { devRouter } from '../src/util/dev/router';
test('[dev-router] 301 redirection', async t => {
const routesConfig = [
@@ -11,11 +11,13 @@ test('[dev-router] 301 redirection', async t => {
found: true,
dest: '/redirect',
status: 301,
headers: { Location: 'https://zeit.co' },
headers: { location: 'https://zeit.co' },
uri_args: {},
matched_route: routesConfig[0],
matched_route_idx: 0,
userDest: false,
isDestUrl: false,
phase: undefined,
});
});
@@ -32,6 +34,8 @@ test('[dev-router] captured groups', async t => {
matched_route: routesConfig[0],
matched_route_idx: 0,
userDest: true,
isDestUrl: false,
phase: undefined,
});
});
@@ -48,6 +52,8 @@ test('[dev-router] named groups', async t => {
matched_route: routesConfig[0],
matched_route_idx: 0,
userDest: true,
isDestUrl: false,
phase: undefined,
});
});
@@ -69,6 +75,8 @@ test('[dev-router] optional named groups', async t => {
matched_route: routesConfig[0],
matched_route_idx: 0,
userDest: true,
isDestUrl: false,
phase: undefined,
});
});
@@ -86,6 +94,8 @@ test('[dev-router] proxy_pass', async t => {
matched_route: routesConfig[0],
matched_route_idx: 0,
userDest: false,
isDestUrl: true,
phase: undefined,
});
});
@@ -105,6 +115,8 @@ test('[dev-router] methods', async t => {
matched_route: routesConfig[1],
matched_route_idx: 1,
userDest: true,
isDestUrl: false,
phase: undefined,
});
result = await devRouter('/', 'POST', routesConfig);
@@ -117,6 +129,8 @@ test('[dev-router] methods', async t => {
matched_route: routesConfig[0],
matched_route_idx: 0,
userDest: true,
isDestUrl: false,
phase: undefined,
});
});
@@ -133,6 +147,8 @@ test('[dev-router] match without prefix slash', async t => {
matched_route: routesConfig[0],
matched_route_idx: 0,
userDest: true,
isDestUrl: false,
phase: undefined,
});
});
@@ -149,6 +165,8 @@ test('[dev-router] match with needed prefixed slash', async t => {
found: true,
dest: '/some/dest',
userDest: true,
isDestUrl: false,
phase: undefined,
status: undefined,
headers: {},
uri_args: {},
@@ -179,6 +197,9 @@ test('[dev-router] `continue: true` with fallthrough', async t => {
t.deepEqual(result, {
found: false,
dest: '/_next/static/chunks/0.js',
isDestUrl: false,
phase: undefined,
status: undefined,
uri_args: {},
headers: {
'cache-control': 'immutable,max-age=31536000',
@@ -211,6 +232,8 @@ test('[dev-router] `continue: true` with match', async t => {
dest: '/hi',
status: undefined,
userDest: true,
isDestUrl: false,
phase: undefined,
uri_args: {},
headers: {
'cache-control': 'immutable,max-age=31536000',
@@ -231,6 +254,8 @@ test('[dev-router] match with catch-all with prefix slash', async t => {
found: true,
dest: '/www/',
userDest: true,
isDestUrl: false,
phase: undefined,
status: undefined,
headers: {},
uri_args: {},
@@ -247,6 +272,8 @@ test('[dev-router] match with catch-all with no prefix slash', async t => {
found: true,
dest: '/www/',
userDest: true,
isDestUrl: false,
phase: undefined,
status: undefined,
headers: {},
uri_args: {},
@@ -274,5 +301,7 @@ test('[dev-router] `continue: true` with `dest`', async t => {
matched_route: routesConfig[1],
matched_route_idx: 1,
userDest: false,
isDestUrl: true,
phase: undefined,
});
});

View File

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

View File

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

View File

@@ -3,10 +3,7 @@
"builds": [
{
"use": "@now/static-build",
"src": "package.json",
"config": {
"zeroConfig": true
}
"src": "package.json"
}
]
}

View File

@@ -0,0 +1 @@
Blog Post

View File

@@ -0,0 +1,17 @@
{
"version": 2,
"routes": [
{
"src": "/([^/]+)",
"headers": { "override": "one" },
"dest": "/blog/$1.html",
"check": true
},
{ "handle": "hit" },
{
"src": "^/.*",
"headers": { "test": "1", "override": "two" },
"continue": true
}
]
}

View File

@@ -0,0 +1 @@
Blog Page

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