Compare commits

..

77 Commits

Author SHA1 Message Date
Steven
8043e25d6d Publish Stable
- @now/frameworks@0.0.13
 - @now/build-utils@2.2.1
 - now@18.0.0
 - now-client@7.1.0
 - @now/go@1.0.7
 - @now/next@2.5.3
 - @now/node@1.5.1
 - @now/python@1.1.6
 - @now/routing-utils@1.8.1
 - @now/static-build@0.16.0
2020-04-13 20:19:14 -04:00
Steven
73b4e7aea4 Publish Canary
- now@17.1.2-canary.16
2020-04-13 19:56:50 -04:00
Steven
de7e063c9b [now-cli] Fix now env stdin detection and don't throw for known errors (#4060)
- Capitalize `Environment` in all outputs
- Fix stdin detection so there is no need for timeouts
- Dont throw if the error is bad user input, only throw for unexpected errors
- Fix tests so waiting for prompt will throw if expected output is never received
2020-04-13 19:56:22 -04:00
Steven
e7c30deee8 [now-cli] Change now env pull console output (#4059)
Change from `Created file .env` to `Create .env file` per @leo
2020-04-13 15:50:06 -04:00
Steven
7464fac792 Publish Canary
- now@17.1.2-canary.15
2020-04-13 14:26:03 -04:00
Steven
252363cce4 [now-cli] Prefer .env over .env.build during now dev (#4057)
The latest `now env` subcommand no longer makes the distinction between build time and runtime environment variables so this PR updates `now dev` to no longer make the distinction either.

The only exception is that some builders such as `@now/next` might still rely on the separation so we must preserve the distinction for legacy builders.
2020-04-13 18:22:21 +00:00
Steven
d7dceeb2a1 [now-cli] Update now env to support empty values (#4046)
This PR adds support for empty values when using `now env add|ls|rm|pull`.

This is necessary when using system environment variables such as `NOW_GITHUB_COMMIT_SHA` which will not receive a value from the user but will instead assign a value automatically during deployment.
2020-04-13 15:21:20 +00:00
Ana Trajkovska
6b5b9e8b2f Publish Canary
- @now/build-utils@2.2.1-canary.1
 - now@17.1.2-canary.14
2020-04-10 18:51:20 +02:00
Ana Trajkovska
addd036149 Improve pagination message (#4035) 2020-04-10 18:46:32 +02:00
Steven
7ca672a470 [now-build-utils] Exclude now.json from fallback build src (#4042)
https://github.com/zeit/now/discussions/4016
2020-04-10 16:06:02 +00:00
Ana Trajkovska
3af0e99689 Publish Canary
- now@17.1.2-canary.13
 - @now/python@1.1.6-canary.0
2020-04-10 17:21:15 +02:00
Ana Trajkovska
b14068de8a [now-cli] Fix removing a deployment (#4036)
* Fix removing a deployment

* Add test for removing a deployment

* Add logs

* Fix assertion
2020-04-10 17:19:48 +02:00
Nguyễn Hồng Quân
c9437e714a [now-python] Fix missing sys modules for relative module import (#4024)
Without this fix, Python app which uses [Pydantic](https://pydantic-docs.helpmanual.io/usage/postponed_annotations/) for data validation will crash with log like this:

```py
  File "pydantic/main.py", line 175, in pydantic.main.ModelMetaclass.__new__
    annotations = resolve_annotations(namespace.get('__annotations__', {}), namespace.get('__module__', None))
  File "pydantic/typing.py", line 136, in pydantic.typing.resolve_annotations
    annotations = resolve_annotations(namespace.get('__annotations__', {}), namespace.get('__module__', None))
  File "pydantic/typing.py", line 136, in pydantic.typing.resolve_annotations
    base_globals: Optional[Dict[str, Any]] = sys.modules[module_name].__dict__
KeyError: 'api.main'
```

The added code also follow Python official documentation: https://docs.python.org/3.6/library/importlib.html#importing-a-source-file-directly
2020-04-10 09:53:24 -04:00
Steven
c0aff5cf4a [now-cli] Clean up tests for now dev (#4038)
This PR does a few things:

- Change `dev.unref()` when possible and use `testFixtureStdio` instead
- Replace `fetch()` with `fetchWithRetry()` when possible
- Remove commented-out tests
2020-04-09 23:30:22 +00:00
Allen Hai
9110b14bb4 Publish Canary
- now@17.1.2-canary.12
2020-04-09 17:07:06 -05:00
Steven
40e4b69267 [now-cli] Fix now dev routing for continue: true (#4033)
There was a bug preventing `continue: true` from working between (null => filesystem) phases.

This PR fixes that bug and adds a test to ensure users can rewrite to dynamic path segments.
2020-04-09 21:35:13 +00:00
Ana Trajkovska
6c4934dcc4 Publish Canary
- @now/frameworks@0.0.13-canary.1
 - now@17.1.2-canary.11
 - @now/static-build@0.15.2-canary.4
2020-04-09 22:26:22 +02:00
Ana Trajkovska
d64e54d61a Implement pagination for now ls (#4027) 2020-04-09 22:24:25 +02:00
Allen Hai
0b75bf07f7 [examples][now-static-build] Add Ionic Angular framework (#3970)
This PR adds framework config for Ionic Angular and also updates a mistake found in the dev script of Ionic React, which mistakenly was using the same dev script as Stencil.
2020-04-09 18:44:46 +00:00
Ana Trajkovska
a861a8f3f7 Publish Canary
- now@17.1.2-canary.10
2020-04-09 16:45:12 +02:00
Ana Trajkovska
6407b17b7f Improve pagination message (#4026) 2020-04-09 16:43:26 +02:00
Steven
558463a988 Publish Canary
- now@17.1.2-canary.9
2020-04-08 08:34:50 -04:00
Steven
1a133995b8 [now-cli] Add now env command (#3999)
This PR adds a new command `now env` for managing environment variables for a given project. This will complement the changes in the Dashboard and will provide 3 possible environments: `production`, `preview`, and `development`.

- [x] Add `now env ls`
- [x] Add `now env add`
- [x] Add `now env rm`
- [x] Add `now env pull`
- [x] Add  test for `now env ls`
- [x] Add  test for `now env add`
- [x] Add  test for `now env rm -y`
- [x] Add  test for `now env pull -y`
- [x] Add  test for `now env add FOO preview < secret.txt`
- [x] Add test deployment to verify env var is assigned to both build and runtime
- [x] Test on Windows


```
  ▲ now env [options] <command>

  Commands:

    ls      [environment]              List all variables for the specified environment
    add     [name] [environment]       Add an environment variable (see examples below)
    rm      [name] [environment]       Remove an environment variable (see examples below)
    pull    [filename]                 Read development environment from the cloud and write to a file [.env]

  Options:

    -h, --help                     Output usage information
    -A FILE, --local-config=FILE   Path to the local `now.json` file
    -Q DIR, --global-config=DIR    Path to the global `.now` directory
    -d, --debug                    Debug mode [off]
    -t TOKEN, --token=TOKEN        Login token

  Examples:

  – Add a new variable to multiple environments

      $ now env add <name>
      $ now env add API_TOKEN

  – Add a new variable for a specific environment

      $ now env add <name> <production | preview | development>
      $ now env add DB_CONNECTION production

  – Add a new environment variable from stdin

      $ cat <file> | now env add <name> <production | preview | development>
      $ cat ~/.npmrc | now env add NPM_RC preview
      $ now env add DB_PASS production < secret.txt

  – Remove a variable from multiple environments

      $ now env rm <name>
      $ now env rm API_TOKEN

  – Remove a variable from a specific environment

      $ now env rm <name> <production | preview | development>
      $ now env rm NPM_RC preview
```
2020-04-07 22:49:48 +00:00
Steven
a83eecf674 Publish Canary
- @now/routing-utils@1.8.1-canary.1
2020-04-06 18:42:01 -04:00
Steven
a932ed65fa [now-routing-utils] Catch error from compile route (#4019)
Some inputs cause `compile()` to throw but its not clear which route caused it to fail.

> TypeError: Unexpected MODIFIER at 29, expected END [see source](https://sentry.io/organizations/zeithq/issues/1593291118/?project=1351065)

This PR adds logs so we can see the bad user input and correct accordingly.
2020-04-06 22:40:30 +00:00
JJ Kasper
e2ae497762 Publish Stable
- @now/next@2.5.2
2020-04-06 15:35:12 -05:00
JJ Kasper
89989719c2 Publish Canary
- @now/next@2.5.2-canary.0
 - @now/node@1.5.1-canary.0
 - @now/static-build@0.15.2-canary.3
2020-04-06 15:27:03 -05:00
Joe Haddad
8166b8e1e7 [now-next] Correctly Exclude API Routes from Pages (#4018)
This pull request correctly omits dependencies for API Routes from pages.
2020-04-06 20:19:08 +00:00
Logan McAnsh
1ceeac498c [now-node] Add NowApiHandler type (#4001)
* add NowApiHandler type

`now` equivalent of `NextApiHandler` introduced https://github.com/zeit/next.js/pull/10573

* chore: add fixture

Signed-off-by: Logan McAnsh <logan@mcan.sh>
2020-04-03 18:29:56 -04:00
Arunoda Susiripala
1c47d1360d [now-next] Fix some typos related to fixtures (#3995)
They should be `probes` I guess.
2020-04-03 13:43:22 +00:00
Leo Lamprecht
ddcd0918e9 Removed Environment Variables (#3997) 2020-04-03 01:52:02 +02:00
Steven
573b6b8110 [now-static-build] Fix 12-creact-react-app test fixture (#3990)
Since enabling `CI` environment variable for cloud builds, this test fails because it is meant to emit a warning however that warning has turned into an error.

```
05:40:53.148  Treating warnings as errors because process.env.CI = true.
05:40:53.148  Most CI servers set it automatically.
05:40:53.148  Failed to compile.
05:40:53.149  ./src/App.js
05:40:53.149    Line 1:  'useState' is defined but never used  no-unused-vars
05:40:53.172  error Command failed with exit code 1.
```

We can again treat lint errors as warnings by setting `CI=false`.
2020-04-01 14:27:11 +00:00
Steven
40039d7f9b Publish Canary
- @now/build-utils@2.2.1-canary.0
2020-03-31 17:18:32 -04:00
Steven
dcb37e92f5 [now-build-utils] Hide internal stack trace for errors (#3988)
Example build output given a user's build script named `shouldfail.js`:

## Before

```
/zeit/4af70cdc/shouldfail.js:3
throw new Error('This is my failure')
^
Error: This is my failure
    at Object.<anonymous> (/zeit/4af70cdc/shouldfail.js:3:7)
    at Module._compile (internal/modules/cjs/loader.js:955:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:991:10)
    at Module.load (internal/modules/cjs/loader.js:811:32)
    at Function.Module._load (internal/modules/cjs/loader.js:723:14)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1043:10)
    at internal/main/run_main_module.js:17:11
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Error: Exited with 1
    at ChildProcess.<anonymous> (/zeit/687b1c64/.build-utils/node_modules/@now/build-utils/dist/index.js:31350:24)
    at ChildProcess.emit (events.js:223:5)
    at ChildProcess.EventEmitter.emit (domain.js:475:20)
    at maybeClose (internal/child_process.js:1021:16)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:283:5)
worker exited with code 20 and signal null
Done with "package.json"
```

## After 

```
/zeit/255bfdd/shouldfail.js:3
throw new Error('This is my failure')
^
Error: This is my failure
    at Object.<anonymous> (/zeit/255bfdd/shouldfail.js:3:7)
    at Module._compile (internal/modules/cjs/loader.js:955:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:991:10)
    at Module.load (internal/modules/cjs/loader.js:811:32)
    at Function.Module._load (internal/modules/cjs/loader.js:723:14)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1043:10)
    at internal/main/run_main_module.js:17:11
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Error: Command "yarn run build" exited with 1
worker exited with code 20 and signal null
Done with "package.json"
```
2020-03-31 21:09:57 +00:00
Steven
fe7f875549 Publish Canary
- now@17.1.2-canary.8
2020-03-31 12:20:59 -04:00
Steven
a516ed6fb8 [now-cli] Fix error message link to build logs (#3986)
The current error message prints a link that is not clickable from the terminal.

This PR adds the missing `https://` protocol prefix, so that the link is clickable.
2020-03-31 12:09:11 -04:00
Steven
ca2c5f85ef [now-cli] Fix for adding secret with hyphen prefix (#3983)
Follow up to #3982 which didn't actually fix the secret value. Instead it was adding `true` as the value.
2020-03-31 13:06:46 +00:00
Tim Neutkens
adb5a01cc0 Upgrade Next.js (#3984) 2020-03-31 12:24:21 +02:00
Steven
6b4d39ab4d Publish Canary
- now@17.1.2-canary.7
2020-03-30 18:27:55 -04:00
Steven
07ce3d2e34 [now-cli] Bump mri to 1.1.5 (#3982)
This fixes the error when attempting to add a secret with a hyphen and underscore such as the following:

```
$ now secret add name '-foo_bar'
Error! argv._.slice is not a function
```
2020-03-30 22:13:00 +00:00
JJ Kasper
93ffcf487b Publish Canary
- @now/routing-utils@1.8.1-canary.0
2020-03-30 14:18:01 -05:00
JJ Kasper
3631f0f4cf [now-routing-utils] Update to not add path segments to redirect query automatically (#3981)
As discussed this removes automatically adding path segments to redirect's destination query and only adds them if manually specified

x-ref: https://github.com/zeit/next.js/pull/11497
2020-03-30 18:57:24 +00:00
Steven
b67b5be8a9 Publish Canary
- now@17.1.2-canary.6
 - now-client@7.0.2-canary.2
 - @now/static-build@0.15.2-canary.2
2020-03-30 14:06:57 -04:00
Steven
bf67b1a29e [now-static-build][now-client] Ignore known static outputs (#3980)
We already ignore specific files such as `node_modules` and `.env` during the upload phase so these never make it to the build. However, if those files are generated during the build, that are still emitted.

This PR will ignore these specific files even if they end up in the output directory (for example, when the user assigns `outputDirectory='.'` in project settings)
2020-03-30 17:53:47 +00:00
Max Rovensky
ed86473f74 Publish Canary
- now@17.1.2-canary.5
 - now-client@7.0.2-canary.1
2020-03-30 17:57:56 +08:00
Max
399a3cd114 [now-cli][now-client] Change forceNewWithCache to withCache (#3966)
Follow up to https://github.com/zeit/now/pull/3953 that makes the flag composable
2020-03-30 09:51:05 +00:00
Steven
d0fd09810a Publish Stable
- @now/go@1.0.6
2020-03-28 18:33:40 -04:00
Steven
f298f2e894 Publish Canary
- @now/go@1.0.6-canary.0
2020-03-28 18:21:06 -04:00
Steven
569200ae0e [now-go] Fix import for go-bridge (#3976)
Somehow, PR #3973 broke Go since the bridge is imported from this repo's master branch.

Go has very strict file name constraints and the file `[...path].js` is not compatible.


Here's what a `@now/go` deployment error message looks like:

```
Error: Command failed: go mod tidy
go: finding github.com/zeit/now latest
go: downloading github.com/zeit/now v0.0.0-20200326223129-c91495338d5e
go: extracting github.com/zeit/now v0.0.0-20200326223129-c91495338d5e
-> unzip /tmp/5a0676f5/pkg/mod/cache/download/github.com/zeit/now/@v/v0.0.0-20200326223129-c91495338d5e.zip: malformed file path "packages/now-next/test/fixtures/22-ssg-v2-catchall/pages/[...path].js": double dot
handler imports
github.com/zeit/now/utils/go/bridge: unzip /tmp/5a0676f5/pkg/mod/cache/download/github.com/zeit/now/@v/v0.0.0-20200326223129-c91495338d5e.zip: malformed file path "packages/now-next/test/fixtures/22-ssg-v2-catchall/pages/[...path].js": double dot
```

The solution is to move Go Bridge into a separate repository: https://github.com/zeit/now-go-bridge

This will also have the side effect of speeding up Go imports because the repo will be much smaller.
2020-03-28 22:16:42 +00:00
Nathan Rajlich
c91495338d Update signal-exit to v3.0.3 (#3974)
@tootallnate's bug fix for `SIGHUB` on Windows has been merged and
published as `signal-exit@3.0.3`, so no more need for the "resolutions"
field in `package.json`.

The `yarn.lock` file has been updated accordingly.
2020-03-26 22:31:29 +00:00
JJ Kasper
7eed5574e0 Publish Stable
- @now/next@2.5.1
2020-03-26 16:12:48 -05:00
JJ Kasper
91e6b85cec Publish Canary
- @now/next@2.5.1-canary.0
 - @now/static-build@0.15.2-canary.1
2020-03-26 16:01:00 -05:00
JJ Kasper
3ae83172ec [now-next] Fix dynamic routes and data routes order (#3973)
When using a catch-all route at the base of the project it would cause it to be prioritized over any GS(S)P `/_next/data` routes. This fixes the order putting `/_next/data` routes first as they have higher specificity and adds tests to ensure we don't regress on this
2020-03-26 20:08:51 +00:00
Steven
7c51446e5e [tests] Add additional env vars (#3968)
These are used for testing and health checks.
2020-03-25 22:42:37 +00:00
Steven
400a5c73e8 [examples][now-static-build] Bump ionic-react to latest typescript (#3967)
Fixes an issue with a dependency that was bumped but typescript was pinned in `ionic-react`.


```
$ react-scripts build
Creating an optimized production build...
Failed to compile.
/zeit/333ecfab/node_modules/@types/testing-library__react/node_modules/pretty-format/build/index.d.ts
TypeScript error in /zeit/333ecfab/node_modules/@types/testing-library__react/node_modules/pretty-format/build/index.d.ts(7,13):
'=' expected.  TS1005
     5 |  * LICENSE file in the root directory of this source tree.
     6 |  */
  >  7 | import type * as PrettyFormat from './types';
       |             ^
     8 | /**
     9 |  * Returns a presentation string of your `val` object
    10 |  * @param val any potential JavaScript object
error Command failed with exit code 1.
```
2020-03-25 19:20:59 +00:00
Steven
ec917ace69 Publish Canary
- @now/frameworks@0.0.13-canary.0
 - now@17.1.2-canary.4
 - @now/static-build@0.15.2-canary.0
2020-03-25 13:41:04 -04:00
Anthony Gubler
f5e0afdd7e [examples] Add Dojo Example (#3882)
Adds an example for Dojo applications with ZEIT Now.

Co-Authored-By: Steven <steven@ceriously.com>
Co-Authored-By: Andy <AndyBitz@users.noreply.github.com>
2020-03-25 13:32:33 -04:00
Steven
c1b4c62714 [tests] Cancel previous workflows on push (#3965)
Follow up to #3961

Workflow IDs are found here: from https://api.github.com/repos/zeit/now/actions/workflows
2020-03-25 16:05:32 +00:00
Steven
5e4bdfbe11 [tests] Separate into workflows (#3961)
This PR does a few things:

- Separate tests into multiple workflows
- Rename a few package.json scripts to make naming consistent
- Rename workflows to be uppercase and jobs to be lowercase

The benefits here are:
- Restart a workflow if it fails, for example only restart `now dev` tests
- Easier to read when we need to understand a workflow or modify env vars

After merging, we'll need to modify the required checks in the repo settings.
We'll also need to update the cancel workflow (that will need to be a separate PR).
2020-03-25 11:40:11 -04:00
Andy Bitz
bd4a0cbd32 Publish Stable
- @now/frameworks@0.0.12
 - @now/static-build@0.15.1
2020-03-25 15:57:19 +01:00
Andy Bitz
7ff9adc90e Publish Canary
- @now/frameworks@0.0.12-canary.1
 - @now/static-build@0.15.1-canary.1
2020-03-25 15:56:44 +01:00
Andy
b279f1ffae [frameworks][now-static-build] Support Docusaurus v2 and v1 (#3964)
Support Docusaurus v2 and v1
2020-03-25 14:42:56 +00:00
Ana Trajkovska
344cc103ee Publish Canary
- now@17.1.2-canary.3
2020-03-25 14:11:11 +01:00
Ana Trajkovska
83249b3685 [now-cli] Add pagination for now alias ls (#3915)
* Implement pagination for `now alias ls`

* Fix issue retrieving aliases

* Add help text for pagiting aliases
2020-03-25 14:08:26 +01:00
Andy
79e7a9f477 [now-cli] Update docusaurus test (#3963) 2020-03-25 13:43:53 +01:00
Steven
b3dce70271 Publish Canary
- @now/frameworks@0.0.12-canary.0
 - now@17.1.2-canary.2
 - @now/static-build@0.15.1-canary.0
2020-03-24 19:24:57 -04:00
Steven
cff8d8b8a0 [tests] Fix test initialize existing directory --f -> -f (#3960)
Fixes test after PR #3958
2020-03-24 22:46:42 +00:00
Andy
da892100d9 [frameworks][now-static-build] Fix Docusaurus build command (#3959)
* [frameworks][now-static-build] Fix Docusaurus build command

* Update dev command
2020-03-24 23:03:31 +01:00
Max Leiter
72e87ee6e4 Fix slight spelling mistake: --f -> -f in now --force error (#3958) 2020-03-24 12:51:15 -07:00
Max Rovensky
3f9afad167 Publish Canary
- now@17.1.2-canary.1
 - now-client@7.0.2-canary.0
2020-03-25 03:12:23 +08:00
Max
1527447914 Add --force-with-cache flag support (#3953)
Adds support for an upcoming `forceNewWithCache` API flag
2020-03-24 19:09:10 +00:00
Steven
9ca35df5fb [tests] Fix coverage checks (#3957)
The CodeCov GitHub integration is confused about the monorepo and started reporting failures recently.

<img src="https://user-images.githubusercontent.com/229881/77462927-2983f280-6ddb-11ea-9ee2-38b660b2fd2f.png" height=120 />

We run `codecov` from the CLI so we can disable the integration. 

## References

- https://docs.codecov.io/docs/codecovyml-reference
- https://docs.codecov.io/docs/commit-status#section-disabling-a-status
2020-03-24 18:47:25 +00:00
Ana Trajkovska
05b2e2216c Publish Canary
- now@17.1.2-canary.0
2020-03-24 19:12:47 +01:00
Ana Trajkovska
167fd5750a [now-cli] Add pagination for now domains ls (#3922)
* Implement pagination for listing domains

* Add helpi list for paginating domains
2020-03-24 19:08:52 +01:00
Andy
4a3cd7ec72 [now-cli] Ignore 404 for aliases and certs when removing a domain (#3955) 2020-03-24 01:56:13 +01:00
Andy Bitz
9aef718917 Publish Stable
- now@17.1.1
2020-03-24 00:16:28 +01:00
Ana Trajkovska
3cdc261802 [now-cli] Fix broken message when a deployment is canceled (#3954)
* Fix broken message when a deployment is canceled

* Add \n in the message
2020-03-24 00:14:52 +01:00
403 changed files with 19088 additions and 1297 deletions

1
.github/CODEOWNERS vendored
View File

@@ -8,6 +8,7 @@
/packages/now-cli/src/util/dev/ @tootallnate @leo @styfle @AndyBitz
/packages/now-cli/src/commands/domains/ @javivelasco @mglagola @anatrajkovska
/packages/now-cli/src/commands/certs/ @javivelasco @mglagola @anatrajkovska
/packages/now-cli/src/commands/env @styfle @lucleray
/packages/now-client @leo @rdev
/packages/now-build-utils @styfle @AndyBitz
/packages/now-node @styfle @tootallnate @lucleray

View File

@@ -1,7 +1,7 @@
name: Cancel
on:
push:
branches:
branches:
- '*'
- '!master'
@@ -11,8 +11,8 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- uses: styfle/cancel-workflow-action@0.2.0
- uses: styfle/cancel-workflow-action@0.3.1
with:
workflow_id: 435869
workflow_id: 849295, 849296, 849297, 849298
access_token: ${{ secrets.GITHUB_WORKFLOW_TOKEN }}

View File

@@ -1,117 +0,0 @@
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, windows-latest]
node: [10, 12]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- run: git fetch origin master --depth=10
- run: git fetch origin ${{ github.ref }} --depth=10
- uses: actions/setup-node@v1
- run: yarn install
- run: 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
- run: yarn run build
- run: yarn test-integration-once --clean false
env:
ZEIT_TEAM_TOKEN: ${{ secrets.ZEIT_TEAM_TOKEN }}
ZEIT_REGISTRATION_URL: ${{ secrets.ZEIT_REGISTRATION_URL }}
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
- name: Install Hugo
if: matrix.os == 'macos-latest'
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
- run: yarn install
- run: yarn run build
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: yarn test-integration --clean false
env:
ZEIT_TEAM_TOKEN: ${{ secrets.ZEIT_TEAM_TOKEN }}
ZEIT_REGISTRATION_URL: ${{ secrets.ZEIT_REGISTRATION_URL }}
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
- name: Install Hugo
if: matrix.os == 'macos-latest'
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
- run: yarn install
- run: yarn run build
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: yarn test-integration-now-dev --clean false
coverage:
name: Coverage
timeout-minutes: 5
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
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -0,0 +1,36 @@
name: CLI
on:
push:
branches:
- master
tags:
- '!*'
pull_request:
jobs:
test:
name: CLI
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
- name: Install Hugo
if: matrix.os == 'macos-latest'
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
- run: yarn install
- run: yarn run build
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: yarn test-integration-cli --clean false
env:
ZEIT_TEAM_TOKEN: ${{ secrets.ZEIT_TEAM_TOKEN }}
ZEIT_REGISTRATION_URL: ${{ secrets.ZEIT_REGISTRATION_URL }}

View File

@@ -0,0 +1,33 @@
name: Dev
on:
push:
branches:
- master
tags:
- '!*'
pull_request:
jobs:
test:
name: Dev
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
- name: Install Hugo
if: matrix.os == 'macos-latest'
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
- run: yarn install
- run: yarn run build
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: yarn test-integration-dev --clean false

View File

@@ -0,0 +1,25 @@
name: E2E
on:
push:
branches:
- master
tags:
- '!*'
pull_request:
jobs:
test:
name: E2E
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
- run: yarn run build
- run: yarn test-integration-once --clean false
env:
ZEIT_TEAM_TOKEN: ${{ secrets.ZEIT_TEAM_TOKEN }}
ZEIT_REGISTRATION_URL: ${{ secrets.ZEIT_REGISTRATION_URL }}

33
.github/workflows/test-unit.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Unit
on:
push:
branches:
- master
tags:
- '!*'
pull_request:
jobs:
test:
name: Unit
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [10, 12]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- run: git fetch origin master --depth=10
- run: git fetch origin ${{ github.ref }} --depth=10
- uses: actions/setup-node@v1
- run: yarn install
- run: yarn run build
- run: yarn run test-lint
- run: yarn run test-unit --clean false
- run: yarn workspace now run coverage
if: matrix.os == 'ubuntu-latest' && matrix.node == 12 # only run once
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

8
codecov.yml Normal file
View File

@@ -0,0 +1,8 @@
codecov:
require_ci_to_pass: yes
coverage:
status:
project: off
patch: off

6
examples/dojo/.dojorc Normal file
View File

@@ -0,0 +1,6 @@
{
"build-app": {},
"test-intern": {},
"create-app": {},
"create-widget": {}
}

4
examples/dojo/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
_build/
output/
.cert/

25
examples/dojo/README.md Normal file
View File

@@ -0,0 +1,25 @@
# Dojo Example
This directory is a brief example of a [Dojo](https://dojo.io) site that can be deployed with ZEIT Now and zero configuration.
## Deploy Your Own
Deploy your own Dojo project with ZEIT Now.
[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/import/project?template=https://github.com/zeit/now-examples/tree/master/dojo)
### How We Created This Example
To get started with Dojo on Now, you can use the [Dojo CLI](https://github.com/dojo/cli) to initialize the project:
```shell
$ now init dojo
```
### Deploying From Your Terminal
Once initialized, you can deploy the Dojo example with just a single command:
```shell
$ now
```

View File

@@ -0,0 +1,26 @@
{
"name": "dojo",
"version": "1.0.0",
"scripts": {
"dev": "dojo build --mode dev --watch --serve",
"build": "dojo build --mode dist",
"build:dev": "dojo build --mode dev",
"test": "dojo test",
"test:unit": "dojo build --mode unit && dojo test --unit --config local",
"test:functional": "dojo build --mode functional && dojo test --functional --config local",
"test:all": "dojo build --mode unit && dojo build --mode functional && dojo test --all --config local"
},
"dependencies": {
"@dojo/framework": "^6.0.0",
"@dojo/themes": "^6.0.0",
"@dojo/widgets": "^6.0.0",
"tslib": "~1.9.1"
},
"devDependencies": {
"@dojo/cli": "^6.0.0",
"@dojo/cli-build-app": "^6.0.0",
"@dojo/cli-test-intern": "^6.0.0",
"@types/node": "~9.6.5",
"typescript": "~3.4.5"
}
}

View File

@@ -0,0 +1,3 @@
.root {
}

1
examples/dojo/src/App.m.css.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export const root: string;

27
examples/dojo/src/App.ts Normal file
View File

@@ -0,0 +1,27 @@
import { create, v, w } from '@dojo/framework/core/vdom';
import theme from '@dojo/framework/core/middleware/theme';
import Outlet from '@dojo/framework/routing/Outlet';
import dojo from '@dojo/themes/dojo';
import Menu from './widgets/Menu';
import Home from './widgets/Home';
import About from './widgets/About';
import Profile from './widgets/Profile';
import * as css from './App.m.css';
const factory = create({ theme });
export default factory(function App({ middleware: { theme } }) {
if (!theme.get()) {
theme.set(dojo);
}
return v('div', { classes: [css.root] }, [
w(Menu, {}),
v('div', [
w(Outlet, { key: 'home', id: 'home', renderer: () => w(Home, {}) }),
w(Outlet, { key: 'about', id: 'about', renderer: () => w(About, {}) }),
w(Outlet, { key: 'profile', id: 'profile', renderer: () => w(Profile, { username: 'Dojo User' }) })
])
]);
});

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en-us" dir="ltr">
<head>
<meta charset="utf-8">
<title>dojo</title>
<meta name="theme-color" content="#222127">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,6 @@
/* Put your styles and imports here */
body {
margin: 0;
padding: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}

13
examples/dojo/src/main.ts Normal file
View File

@@ -0,0 +1,13 @@
import renderer, { w } from '@dojo/framework/core/vdom';
import Registry from '@dojo/framework/core/Registry';
import { registerRouterInjector } from '@dojo/framework/routing/RouterInjector';
import '@dojo/themes/dojo/index.css';
import routes from './routes';
import App from './App';
const registry = new Registry();
registerRouterInjector(routes, registry);
const r = renderer(() => w(App, {}));
r.mount({ registry });

View File

@@ -0,0 +1,15 @@
export default [
{
path: 'home',
outlet: 'home',
defaultRoute: true
},
{
path: 'about',
outlet: 'about'
},
{
path: 'profile',
outlet: 'profile'
}
];

View File

@@ -0,0 +1,9 @@
import { v, create } from '@dojo/framework/core/vdom';
import * as css from './styles/About.m.css';
const factory = create();
export default factory(function Profile() {
return v('h1', { classes: [css.root] }, ['About Page']);
});

View File

@@ -0,0 +1,9 @@
import { v, create } from '@dojo/framework/core/vdom';
import * as css from './styles/Home.m.css';
const factory = create();
export default factory(function Profile() {
return v('h1', { classes: [css.root] }, ['Home Page']);
});

View File

@@ -0,0 +1,39 @@
import { create, w } from '@dojo/framework/core/vdom';
import Link from '@dojo/framework/routing/ActiveLink';
import Toolbar from '@dojo/widgets/toolbar';
import * as css from './styles/Menu.m.css';
const factory = create();
export default factory(function Menu() {
return w(Toolbar, { heading: 'My Dojo App!', collapseWidth: 600 }, [
w(
Link,
{
to: 'home',
classes: [css.link],
activeClasses: [css.selected]
},
['Home']
),
w(
Link,
{
to: 'about',
classes: [css.link],
activeClasses: [css.selected]
},
['About']
),
w(
Link,
{
to: 'profile',
classes: [css.link],
activeClasses: [css.selected]
},
['Profile']
)
]);
});

View File

@@ -0,0 +1,14 @@
import { v, create } from '@dojo/framework/core/vdom';
import * as css from './styles/Profile.m.css';
export interface ProfileProperties {
username: string;
}
const factory = create().properties<ProfileProperties>();
export default factory(function Profile({ properties }) {
const { username } = properties();
return v('h1', { classes: [css.root] }, [`Welcome ${username}!`]);
});

View File

@@ -0,0 +1,3 @@
.root {
}

View File

@@ -0,0 +1 @@
export const root: string;

View File

@@ -0,0 +1,3 @@
.root {
}

View File

@@ -0,0 +1 @@
export const root: string;

View File

@@ -0,0 +1,23 @@
.root {
flex: 1;
overflow: hidden;
}
.link:hover {
color: #4db3ff;
background-color:#ccddee;
}
.link {
min-width: 140px;
text-align: center;
text-transform: uppercase;
text-decoration: none;
padding: 18px;
color: black;
box-sizing: border-box;
}
.selected {
color: darkorange;
}

View File

@@ -0,0 +1,3 @@
export const root: string;
export const link: string;
export const selected: string;

View File

@@ -0,0 +1,3 @@
.root {
}

View File

@@ -0,0 +1 @@
export const root: string;

View File

@@ -0,0 +1 @@
import './main';

View File

@@ -0,0 +1 @@
/* Write your app tests here */

View File

@@ -0,0 +1,46 @@
const { describe, it } = intern.getInterface('bdd');
import harness from '@dojo/framework/testing/harness';
import { v, w } from '@dojo/framework/core/vdom';
import Outlet from '@dojo/framework/routing/Outlet';
import Menu from '../../src/widgets/Menu';
import Home from '../../src/widgets/Home';
import About from '../../src/widgets/About';
import Profile from '../../src/widgets/Profile';
import App from '../../src/App';
import * as css from '../../src/App.m.css';
describe('App', () => {
it('default renders correctly', () => {
const h = harness(() => w(App, {}));
h.expect(() =>
v('div', { classes: [css.root] }, [
w(Menu, {}),
v('div', [
w(Outlet, { key: 'home', id: 'home', renderer: () => w(Home, {}) }),
w(Outlet, { key: 'about', id: 'about', renderer: () => w(About, {}) }),
w(Outlet, { key: 'profile', id: 'profile', renderer: () => w(Profile, { username: 'Dojo User' }) })
])
])
);
});
it('home outlet renderer', () => {
const h = harness(() => w(App, {}));
const renderer = h.trigger('@home', 'renderer');
h.expect(() => w(Home, {}), () => renderer);
});
it('about outlet renderer', () => {
const h = harness(() => w(App, {}));
const renderer = h.trigger('@about', 'renderer');
h.expect(() => w(About, {}), () => renderer);
});
it('profile outlet renderer', () => {
const h = harness(() => w(App, {}));
const renderer = h.trigger('@profile', 'renderer');
h.expect(() => w(Profile, { username: 'Dojo User' }), () => renderer);
});
});

View File

@@ -0,0 +1,2 @@
import './App';
import './widgets/all';

View File

@@ -0,0 +1 @@
/* Write your app tests here */

View File

@@ -0,0 +1,13 @@
const { describe, it } = intern.getInterface('bdd');
import harness from '@dojo/framework/testing/harness';
import { w, v } from '@dojo/framework/core/vdom';
import About from '../../../src/widgets/About';
import * as css from '../../../src/widgets/styles/About.m.css';
describe('About', () => {
it('default renders correctly', () => {
const h = harness(() => w(About, {}));
h.expect(() => v('h1', { classes: [css.root] }, ['About Page']));
});
});

View File

@@ -0,0 +1,13 @@
const { describe, it } = intern.getInterface('bdd');
import harness from '@dojo/framework/testing/harness';
import { w, v } from '@dojo/framework/core/vdom';
import Home from '../../../src/widgets/Home';
import * as css from '../../../src/widgets/styles/Home.m.css';
describe('Home', () => {
it('default renders correctly', () => {
const h = harness(() => w(Home, {}));
h.expect(() => v('h1', { classes: [css.root] }, ['Home Page']));
});
});

View File

@@ -0,0 +1,45 @@
const { describe, it } = intern.getInterface('bdd');
import harness from '@dojo/framework/testing/harness';
import { w } from '@dojo/framework/core/vdom';
import Link from '@dojo/framework/routing/ActiveLink';
import Toolbar from '@dojo/widgets/toolbar';
import Menu from '../../../src/widgets/Menu';
import * as css from '../../../src/widgets/styles/Menu.m.css';
describe('Menu', () => {
it('default renders correctly', () => {
const h = harness(() => w(Menu, {}));
h.expect(() =>
w(Toolbar, { heading: 'My Dojo App!', collapseWidth: 600 }, [
w(
Link,
{
to: 'home',
classes: [css.link],
activeClasses: [css.selected]
},
['Home']
),
w(
Link,
{
to: 'about',
classes: [css.link],
activeClasses: [css.selected]
},
['About']
),
w(
Link,
{
to: 'profile',
classes: [css.link],
activeClasses: [css.selected]
},
['Profile']
)
])
);
});
});

View File

@@ -0,0 +1,13 @@
const { describe, it } = intern.getInterface('bdd');
import harness from '@dojo/framework/testing/harness';
import { w, v } from '@dojo/framework/core/vdom';
import Profile from '../../../src/widgets/Profile';
import * as css from '../../../src/widgets/styles/Profile.m.css';
describe('Profile', () => {
it('default renders correctly', () => {
const h = harness(() => w(Profile, { username: 'Dojo User' }));
h.expect(() => v('h1', { classes: [css.root] }, ['Welcome Dojo User!']));
});
});

View File

@@ -0,0 +1,4 @@
import './About';
import './Home';
import './Profile';
import './Menu';

View File

@@ -0,0 +1,31 @@
{
"compilerOptions": {
"declaration": false,
"experimentalDecorators": true,
"jsx": "react",
"jsxFactory": "tsx",
"lib": [
"dom",
"es5",
"es2015.promise",
"es2015.iterable",
"es2015.symbol",
"es2015.symbol.wellknown"
],
"module": "umd",
"moduleResolution": "node",
"noUnusedLocals": true,
"outDir": "_build/",
"removeComments": false,
"importHelpers": true,
"downlevelIteration": true,
"sourceMap": true,
"strict": true,
"target": "es5",
"types": [ "intern" ]
},
"include": [
"./src/**/*.ts",
"./tests/**/*.ts"
]
}

View File

@@ -20,7 +20,7 @@
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.0",
"typescript": "3.7.4"
"typescript": "3.8.3"
},
"scripts": {
"start": "react-scripts start",

View File

@@ -8,8 +8,8 @@
"start": "next start"
},
"dependencies": {
"next": "9.2.2",
"react": "16.13.0",
"react-dom": "16.13.0"
"next": "^9.3.3",
"react": "^16.13.0",
"react-dom": "^16.13.0"
}
}

View File

@@ -9,10 +9,6 @@
"destination": "/api/frameworks"
}
],
"env": {
"GITHUB_ACCESS_TOKEN": "@now-api-examples-github-token",
"SENTRY_DSN": "@sentry-product-dsn"
},
"github": {
"silent": true,
"autoJobCancelation": true

View File

@@ -38,9 +38,9 @@
"build": "node utils/run.js build all",
"test-lint": "node utils/run.js test-lint",
"test-unit": "node utils/run.js test-unit",
"test-integration": "node utils/run.js test-integration",
"test-integration-cli": "node utils/run.js test-integration-cli",
"test-integration-once": "node utils/run.js test-integration-once",
"test-integration-now-dev": "node utils/run.js test-integration-now-dev",
"test-integration-dev": "node utils/run.js test-integration-dev",
"lint": "eslint . --ext .ts,.js"
},
"lint-staged": {
@@ -59,9 +59,6 @@
"pre-commit": "lint-staged"
}
},
"resolutions": {
"signal-exit": "TooTallNate/signal-exit#update/sighub-to-sigint-on-windows"
},
"prettier": {
"trailingComma": "es5",
"singleQuote": true

View File

@@ -112,7 +112,35 @@
}
},
{
"name": "Docusaurus",
"name": "Docusaurus 2",
"slug": "docusaurus-2",
"demo": "https://docusaurus.now-examples.now.sh",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/docusaurus.svg",
"tagline": "Docusaurus makes it easy to maintain Open Source documentation websites.",
"description": "A static Docusaurus site that makes it easy to maintain OSS documentation.",
"website": "https://v2.docusaurus.io",
"detectors": {
"some": [
{
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@docusaurus\\/core\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`npm run build` or `docusaurus build`"
},
"devCommand": {
"value": "docusaurus start --port $PORT"
},
"outputDirectory": {
"value": "build"
}
}
},
{
"name": "Docusaurus 1",
"slug": "docusaurus",
"demo": "https://docusaurus.now-examples.now.sh",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/docusaurus.svg",
@@ -124,10 +152,6 @@
{
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"docusaurus\":\\s*\".+?\"[^}]*}"
},
{
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@docusaurus\\/core\":\\s*\".+?\"[^}]*}"
}
]
},
@@ -171,6 +195,37 @@
}
}
},
{
"name": "Dojo",
"slug": "dojo",
"demo": "https://dojo.now-examples.now.sh",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/dojo.svg",
"tagline": "Dojo is a modern progressive, TypeScript first framework.",
"description": "A Dojo app, created with the Dojo CLI's cli-create-app command.",
"website": "https://dojo.io",
"detectors": {
"some": [
{
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@dojo\\/framework\":\\s*\".+?\"[^}]*}"
},
{
"path": ".dojorc"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`npm run build` or `dojo build`"
},
"devCommand": {
"value": "dojo build -m dev -w -s -p $PORT"
},
"outputDirectory": {
"value": "output/dist"
}
}
},
{
"name": "Ember",
"slug": "ember",
@@ -254,6 +309,34 @@
}
}
},
{
"name": "Ionic Angular",
"slug": "ionic-angular",
"demo": "https://ionic-angular.now-examples.now.sh",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic.svg",
"tagline": "Ionic Angular allows you to build mobile PWAs with Angular and the Ionic Framework.",
"description": "An Ionic Angular site, created with the Ionic CLI.",
"website": "https://ionicframework.com",
"detectors": {
"every": [
{
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@ionic\\/angular\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`npm run build` or `ng build`"
},
"devCommand": {
"value": "ng start"
},
"outputDirectory": {
"value": "www"
}
}
},
{
"name": "Angular",
"slug": "angular",
@@ -342,7 +425,7 @@
"name": "Ionic React",
"slug": "ionic-react",
"demo": "https://ionic-react.now-examples.now.sh",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic-react.svg",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic.svg",
"tagline": "Ionic React allows you to build mobile PWAs with React and the Ionic Framework.",
"description": "An Ionic React site, created with the Ionic CLI.",
"website": "https://ionicframework.com",
@@ -356,13 +439,13 @@
},
"settings": {
"buildCommand": {
"placeholder": "npm run build"
"placeholder": "`npm run build` or `react-scripts build`"
},
"devCommand": {
"value": "stencil build --dev --watch --serve --port $PORT"
"value": "react-scripts start"
},
"outputDirectory": {
"value": "public"
"value": "build"
}
}
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 1003 B

After

Width:  |  Height:  |  Size: 1003 B

View File

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

View File

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

View File

@@ -190,7 +190,12 @@ export async function detectBuilders(
hasNextApiFiles = true;
}
if (!fallbackEntrypoint && buildCommand && !fileName.includes('/')) {
if (
!fallbackEntrypoint &&
buildCommand &&
!fileName.includes('/') &&
fileName !== 'now.json'
) {
fallbackEntrypoint = fileName;
}
}

View File

@@ -6,13 +6,18 @@ import spawn from 'cross-spawn';
import { SpawnOptions } from 'child_process';
import { deprecate } from 'util';
import { cpus } from 'os';
import { NowBuildError } from '../errors';
import { Meta, PackageJson, NodeVersion, Config } from '../types';
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
interface SpawnOptionsExtended extends SpawnOptions {
prettyCommand?: string;
}
export function spawnAsync(
command: string,
args: string[],
opts: SpawnOptions = {}
opts: SpawnOptionsExtended = {}
) {
return new Promise<void>((resolve, reject) => {
const stderrLogs: Buffer[] = [];
@@ -29,12 +34,18 @@ export function spawnAsync(
return resolve();
}
const errorLogs = stderrLogs.map(line => line.toString()).join('');
if (opts.stdio !== 'inherit') {
reject(new Error(`Exited with ${code || signal}\n${errorLogs}`));
} else {
reject(new Error(`Exited with ${code || signal}`));
}
const cmd = opts.prettyCommand
? `Command "${opts.prettyCommand}"`
: 'Command';
reject(
new NowBuildError({
code: `NOW_BUILD_UTILS_SPAWN_${code || signal}`,
message:
opts.stdio === 'inherit'
? `${cmd} exited with ${code || signal}`
: stderrLogs.map(line => line.toString()).join(''),
})
);
});
});
}
@@ -42,7 +53,7 @@ export function spawnAsync(
export function execAsync(
command: string,
args: string[],
opts: SpawnOptions = {}
opts: SpawnOptionsExtended = {}
) {
return new Promise<{ stdout: string; stderr: string; code: number }>(
(resolve, reject) => {
@@ -64,10 +75,15 @@ export function execAsync(
child.on('error', reject);
child.on('close', (code, signal) => {
if (code !== 0) {
const cmd = opts.prettyCommand
? `Command "${opts.prettyCommand}"`
: 'Command';
return reject(
new Error(
`Program "${command}" exited with non-zero exit code ${code} ${signal}.`
)
new NowBuildError({
code: `NOW_BUILD_UTILS_EXEC_${code || signal}`,
message: `${cmd} exited with ${code || signal}`,
})
);
}
@@ -82,18 +98,20 @@ export function execAsync(
}
export function spawnCommand(command: string, options: SpawnOptions = {}) {
const opts = { ...options, prettyCommand: command };
if (process.platform === 'win32') {
return spawn('cmd.exe', ['/C', command], options);
return spawn('cmd.exe', ['/C', command], opts);
}
return spawn('sh', ['-c', command], options);
return spawn('sh', ['-c', command], opts);
}
export async function execCommand(command: string, options: SpawnOptions = {}) {
const opts = { ...options, prettyCommand: command };
if (process.platform === 'win32') {
await spawnAsync('cmd.exe', ['/C', command], options);
await spawnAsync('cmd.exe', ['/C', command], opts);
} else {
await spawnAsync('sh', ['-c', command], options);
await spawnAsync('sh', ['-c', command], opts);
}
return true;
@@ -120,9 +138,11 @@ export async function runShellScript(
assert(path.isAbsolute(fsPath));
const destPath = path.dirname(fsPath);
await chmodPlusX(fsPath);
await spawnAsync(`./${path.basename(fsPath)}`, args, {
cwd: destPath,
const command = `./${path.basename(fsPath)}`;
await spawnAsync(command, args, {
...spawnOpts,
cwd: destPath,
prettyCommand: command,
});
return true;
}
@@ -249,7 +269,7 @@ export async function runNpmInstall(
debug(`Installing to ${destPath}`);
const { hasPackageLockJson } = await scanParentDirs(destPath);
const opts: SpawnOptions = { cwd: destPath, ...spawnOpts };
const opts: SpawnOptionsExtended = { cwd: destPath, ...spawnOpts };
const env = opts.env ? { ...opts.env } : { ...process.env };
delete env.NODE_ENV;
opts.env = env;
@@ -258,11 +278,13 @@ export async function runNpmInstall(
let commandArgs: string[];
if (hasPackageLockJson) {
opts.prettyCommand = 'npm install';
command = 'npm';
commandArgs = args
.filter(a => a !== '--prefer-offline')
.concat(['install', '--no-audit', '--unsafe-perm']);
} else {
opts.prettyCommand = 'yarn install';
command = 'yarn';
commandArgs = args.concat(['install', '--ignore-engines']);
}
@@ -285,7 +307,7 @@ export async function runBundleInstall(
}
assert(path.isAbsolute(destPath));
const opts = { cwd: destPath, ...spawnOpts };
const opts = { ...spawnOpts, cwd: destPath, prettyCommand: 'bundle install' };
await spawnAsync(
'bundle',
@@ -313,7 +335,7 @@ export async function runPipInstall(
}
assert(path.isAbsolute(destPath));
const opts = { cwd: destPath, ...spawnOpts };
const opts = { ...spawnOpts, cwd: destPath, prettyCommand: 'pip3 install' };
await spawnAsync(
'pip3',
@@ -340,18 +362,22 @@ export async function runPackageJsonScript(
);
if (!hasScript) return false;
const opts = { cwd: destPath, ...spawnOpts };
if (hasPackageLockJson) {
console.log(`Running "npm run ${scriptName}"`);
await spawnAsync('npm', ['run', scriptName], opts);
const prettyCommand = `npm run ${scriptName}`;
console.log(`Running "${prettyCommand}"`);
await spawnAsync('npm', ['run', scriptName], {
...spawnOpts,
cwd: destPath,
prettyCommand,
});
} else {
console.log(`Running "yarn run ${scriptName}"`);
await spawnAsync(
'yarn',
['--ignore-engines', '--cwd', destPath, 'run', scriptName],
opts
);
const prettyCommand = `yarn run ${scriptName}`;
console.log(`Running "${prettyCommand}"`);
await spawnAsync('yarn', ['--ignore-engines', 'run', scriptName], {
...spawnOpts,
cwd: destPath,
prettyCommand,
});
}
return true;

View File

@@ -7,6 +7,19 @@ import {
} from '../';
describe('Test `detectBuilders`', () => {
it('should never select now.json src', async () => {
const files = ['docs/index.md', 'mkdocs.yml', 'now.json'];
const { builders, errors } = await detectBuilders(files, null, {
projectSettings: {
buildCommand: 'mkdocs build',
outputDirectory: 'site',
},
});
expect(errors).toBe(null);
expect(builders).toBeDefined();
expect(builders![0].src).not.toBe('now.json');
});
it('package.json + no build', async () => {
const pkg = { dependencies: { next: '9.0.0' } };
const files = ['package.json', 'pages/index.js', 'public/index.html'];
@@ -817,6 +830,21 @@ describe('Test `detectBuilders`', () => {
describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
const featHandleMiss = true;
it('should never select now.json src', async () => {
const files = ['docs/index.md', 'mkdocs.yml', 'now.json'];
const { builders, errors } = await detectBuilders(files, null, {
featHandleMiss,
projectSettings: {
buildCommand: 'mkdocs build',
outputDirectory: 'site',
},
});
expect(errors).toBe(null);
expect(builders).toBeDefined();
expect(builders![0].src).not.toBe('now.json');
});
it('package.json + no build', async () => {
const pkg = { dependencies: { next: '9.0.0' } };
const files = ['package.json', 'pages/index.js', 'public/index.html'];

View File

@@ -21,7 +21,6 @@ describe('Test `walkParentDirs`', () => {
await walkParentDirs({ base, start, filename });
fail('Expected error');
} catch (error) {
console.log(error);
deepEqual(
(error as Error).message,
'Expected "base" to be absolute path'
@@ -36,7 +35,6 @@ describe('Test `walkParentDirs`', () => {
await walkParentDirs({ base, start, filename });
fail('Expected error');
} catch (error) {
console.log(error);
deepEqual(
(error as Error).message,
'Expected "start" to be absolute path'

View File

@@ -1,6 +1,6 @@
{
"name": "now",
"version": "17.1.0",
"version": "18.0.0",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Now",
@@ -13,9 +13,9 @@
"scripts": {
"preinstall": "node ./scripts/preinstall.js",
"test-unit": "nyc ava test/unit.js test/dev-builder.unit.js test/dev-router.unit.js test/dev-server.unit.js --serial --fail-fast --verbose",
"test-integration": "ava test/integration.js --serial --fail-fast",
"test-integration-cli": "ava test/integration.js --serial --fail-fast --verbose",
"test-integration-v1": "ava test/integration-v1.js --serial --fail-fast",
"test-integration-now-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
"prepublishOnly": "yarn build",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"build": "ts-node ./scripts/build.ts",
@@ -63,7 +63,7 @@
},
"devDependencies": {
"@sentry/node": "5.5.0",
"@sindresorhus/slugify": "0.10.0",
"@sindresorhus/slugify": "0.11.0",
"@types/ansi-escapes": "3.0.0",
"@types/ansi-regex": "4.0.0",
"@types/async-retry": "1.2.1",
@@ -141,8 +141,9 @@
"micro": "9.1.2",
"mime-types": "2.1.24",
"minimatch": "3.0.4",
"mri": "1.1.0",
"mri": "1.1.5",
"ms": "2.1.2",
"nanoid": "3.0.2",
"node-fetch": "2.6.0",
"npm-package-arg": "6.1.0",
"nyc": "13.2.0",

View File

@@ -40,7 +40,7 @@ const help = () => {
)} Login token
-S, --scope Set a custom scope
-n, --no-verify Don't wait until instance count meets the previous alias constraints
-N, --next Show next page of results
${chalk.dim('Examples:')}
${chalk.gray('')} Add a new alias to ${chalk.underline('my-api.now.sh')}
@@ -81,6 +81,12 @@ const help = () => {
${chalk.cyan(
`$ now alias ls aliasId --json > ${chalk.underline('rules.json')}`
)}
${chalk.gray('')} Paginate results, where ${chalk.dim(
'`1584722256178`'
)} is the time in milliseconds since the UNIX epoch.
${chalk.cyan(`$ now alias ls --next 1584722256178`)}
`);
};
@@ -100,9 +106,11 @@ export default async function main(ctx) {
'--no-verify': Boolean,
'--rules': String,
'--yes': Boolean,
'--next': Number,
'-n': '--no-verify',
'-r': '--rules',
'-y': '--yes',
'-N': '--next',
});
} catch (err) {
handleError(err);

View File

@@ -8,6 +8,8 @@ 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 getCommandFlags from '../../util/get-command-flags';
import cmd from '../../util/output/cmd.ts';
export default async function ls(ctx, opts, args, output) {
const {
@@ -16,7 +18,7 @@ export default async function ls(ctx, opts, args, output) {
} = ctx;
const { currentTeam } = config;
const { apiUrl } = ctx;
const { '--debug': debugEnabled } = opts;
const { '--debug': debugEnabled, '--next': nextTimestamp } = opts;
const client = new Client({
apiUrl,
token,
@@ -36,8 +38,17 @@ export default async function ls(ctx, opts, args, output) {
throw err;
}
// $FlowFixMe
const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam });
if (typeof nextTimestamp !== undefined && Number.isNaN(nextTimestamp)) {
output.error('Please provide a number for flag --next');
return 1;
}
const now = new Now({
apiUrl,
token,
debug: debugEnabled,
currentTeam,
});
const lsStamp = stamp();
let cancelWait;
@@ -58,7 +69,11 @@ export default async function ls(ctx, opts, args, output) {
: `Fetching aliases under ${chalk.bold(contextName)}`
);
const aliases = await getAliases(now);
const { aliases, pagination } = await getAliases(
now,
undefined,
nextTimestamp
);
if (cancelWait) cancelWait();
if (args[0]) {
@@ -84,15 +99,19 @@ export default async function ls(ctx, opts, args, output) {
output.print(`${printPathAliasTable(rules)}\n`);
}
} else {
aliases.sort((a, b) => new Date(b.created) - new Date(a.created));
output.log(
`${plural('alias', aliases.length, true)} found under ${chalk.bold(
contextName
)} ${lsStamp()}`
);
output.log(`aliases found under ${chalk.bold(contextName)} ${lsStamp()}`);
console.log(printAliasTable(aliases));
}
if (pagination && pagination.count === 20) {
const flags = getCommandFlags(opts, ['_', '--next']);
output.log(
`To display the next page run ${cmd(
`now alias ls${flags} --next ${pagination.next}`
)}`
);
}
now.close();
return 0;
}
@@ -111,7 +130,7 @@ function printAliasTable(aliases) {
? a.deployment.url
: chalk.gray(''),
a.alias,
ms(Date.now() - new Date(a.created)),
ms(Date.now() - new Date(a.createdAt)),
]),
],
{

View File

@@ -14,6 +14,7 @@ export const latestHelp = () => `
'(default)'
)}
dev Start a local development server
env Manages the Environment Variables for your current Project
init [example] Initialize an example project
ls | list [app] Lists deployments
inspect [id] Displays information related to a deployment
@@ -28,7 +29,7 @@ export const latestHelp = () => `
domains [name] Manages your domain names
dns [name] Manages your DNS records
certs [cmd] Manages your SSL certificates
secrets [name] Manages your secret environment variables
secrets [name] Manages your global Secrets, for use in Environment Variables
logs [url] Displays the logs for a deployment
teams Manages your teams
whoami Shows the username of the currently logged in user
@@ -46,6 +47,7 @@ export const latestHelp = () => `
)} Path to the global ${'`.now`'} directory
-d, --debug Debug mode [off]
-f, --force Force a new deployment even if nothing has changed
--with-cache Retain build cache when using "--force"
-t ${chalk.underline('TOKEN')}, --token=${chalk.underline(
'TOKEN'
)} Login token
@@ -83,7 +85,7 @@ export const latestHelp = () => `
${chalk.cyan('$ now /usr/src/project')}
${chalk.gray('')} Deploy with environment variables
${chalk.gray('')} Deploy with Environment Variables
${chalk.cyan('$ now -e NODE_ENV=production -e SECRET=@mysql-secret')}
@@ -97,6 +99,7 @@ export const latestHelp = () => `
export const latestArgs = {
'--force': Boolean,
'--with-cache': Boolean,
'--public': Boolean,
'--no-clipboard': Boolean,
'--env': [String],

View File

@@ -506,6 +506,7 @@ export default async function main(
env: deploymentEnv,
build: { env: deploymentBuildEnv },
forceNew: argv['--force'],
withCache: argv['--with-cache'],
quiet,
wantsPublic: argv['--public'] || localConfig.public,
isFile,
@@ -576,7 +577,7 @@ export default async function main(
}
if (deployment.readyState === 'CANCELED') {
output.log('The deployment has been canceled.');
output.print('The deployment has been canceled.\n');
return 1;
}
@@ -665,7 +666,7 @@ export default async function main(
if (err instanceof BuildError) {
output.error('Build failed');
output.error(
`Check your logs at ${now.url}/_logs or run ${code(
`Check your logs at https://${now.url}/_logs or run ${code(
`now logs ${now.url}`,
{
// Backticks are interpreted as part of the URL, causing CMD+Click

View File

@@ -93,6 +93,7 @@ let paths: string[];
// Options
let forceNew: boolean;
let withCache: boolean;
let deploymentName: string;
let sessionAffinity: string;
let log: any;
@@ -238,6 +239,7 @@ export default async function main(
// Options
forceNew = argv.force;
withCache = argv['with-cache'];
deploymentName = argv.name;
sessionAffinity = argv['session-affinity'];
debugEnabled = argv.debug;
@@ -741,6 +743,7 @@ async function sync({
meta: metadata,
followSymlinks,
forceNew,
withCache,
forwardNpm,
quiet,
scale,

View File

@@ -45,6 +45,7 @@ const help = () => {
'TOKEN'
)} Login token
-S, --scope Set a custom scope
-N, --next Show next page of results
${chalk.dim('Examples:')}
@@ -61,6 +62,12 @@ const help = () => {
if it's configured with these nameservers (no need to ${chalk.dim(
'`domain add`'
)}).
${chalk.gray('')} Paginate results, where ${chalk.dim(
'`1584722256178`'
)} is the time in milliseconds since the UNIX epoch.
${chalk.cyan(`$ now domains ls --next 1584722256178`)}
`);
};
@@ -72,7 +79,7 @@ const COMMAND_CONFIG = {
move: ['move'],
rm: ['rm', 'remove'],
transferIn: ['transfer-in'],
verify: ['verify']
verify: ['verify'],
};
export default async function main(ctx: NowContext) {
@@ -83,7 +90,9 @@ export default async function main(ctx: NowContext) {
'--cdn': Boolean,
'--code': String,
'--no-cdn': Boolean,
'--yes': Boolean
'--yes': Boolean,
'--next': Number,
'-N': '--next',
});
} catch (error) {
handleError(error);

View File

@@ -1,6 +1,5 @@
import ms from 'ms';
import chalk from 'chalk';
import plural from 'pluralize';
import table from 'text-table';
import Client from '../../util/client';
@@ -10,9 +9,12 @@ import stamp from '../../util/output/stamp';
import strlen from '../../util/strlen';
import { Output } from '../../util/output';
import { Domain, NowContext } from '../../types';
import getCommandFlags from '../../util/get-command-flags';
import cmd from '../../util/output/cmd';
type Options = {
'--debug': boolean;
'--next': number;
};
export default async function ls(
@@ -21,13 +23,21 @@ export default async function ls(
args: string[],
output: Output
) {
const { authConfig: { token }, config } = ctx;
const {
authConfig: { token },
config,
} = ctx;
const { currentTeam } = config;
const { apiUrl } = ctx;
const debug = opts['--debug'];
const { '--debug': debug, '--next': nextTimestamp } = opts;
const client = new Client({ apiUrl, token, currentTeam, debug });
let contextName = null;
if (typeof nextTimestamp !== undefined && Number.isNaN(nextTimestamp)) {
output.error('Please provide a number for flag --next');
return 1;
}
try {
({ contextName } = await getScope(client));
} catch (err) {
@@ -48,16 +58,27 @@ export default async function ls(
return 1;
}
const domains = await getDomains(client, contextName);
const { domains, pagination } = await getDomains(
client,
contextName,
nextTimestamp
);
output.log(
`${plural('domain', domains.length, true)} found under ${chalk.bold(
contextName
)} ${chalk.gray(lsStamp())}\n`
`Domains found under ${chalk.bold(contextName)} ${chalk.gray(lsStamp())}\n`
);
if (domains.length > 0) {
console.log(`${formatDomainsTable(domains)}\n`);
}
if (pagination && pagination.count === 20) {
const flags = getCommandFlags(opts, ['_', '--next']);
output.log(
`To display the next page run ${cmd(
`now domains ls${flags} --next ${pagination.next}`
)}`
);
}
return 0;
}
@@ -71,18 +92,18 @@ function formatDomainsTable(domains: Domain[]) {
chalk.gray('serviceType'),
chalk.gray('verified'),
chalk.gray('cdn'),
chalk.gray('age')
chalk.gray('age'),
].map(s => chalk.dim(s)),
...domains.map(domain => {
const url = chalk.bold(domain.name);
const time = chalk.gray(ms(current.getTime() - domain.createdAt));
return ['', url, domain.serviceType, domain.verified, true, time];
})
}),
],
{
align: ['l', 'l', 'l', 'l', 'l'],
hsep: ' '.repeat(4),
stringLength: strlen
stringLength: strlen,
}
);
}

View File

@@ -111,12 +111,26 @@ async function removeDomain(
for (const id of aliasIds) {
output.debug(`Removing alias ${id}`);
await removeAliasById(client, id);
try {
await removeAliasById(client, id);
} catch (error) {
// Ignore if the alias does not exist anymore
if (error.status !== 404) {
throw error;
}
}
}
for (const id of certIds) {
output.debug(`Removing cert ${id}`);
await deleteCertById(output, client, id);
try {
await deleteCertById(output, client, id);
} catch (error) {
// Ignore if the cert does not exist anymore
if (error.status !== 404) {
throw error;
}
}
}
if (suffix) {

172
packages/now-cli/src/commands/env/add.ts vendored Normal file
View File

@@ -0,0 +1,172 @@
import chalk from 'chalk';
import inquirer from 'inquirer';
import { NowContext, ProjectEnvTarget } from '../../types';
import { Output } from '../../util/output';
import Client from '../../util/client';
import stamp from '../../util/output/stamp';
import { getLinkedProject } from '../../util/projects/link';
import addEnvRecord from '../../util/env/add-env-record';
import getEnvVariables from '../../util/env/get-env-records';
import {
isValidEnvTarget,
getEnvTargetPlaceholder,
getEnvTargetChoices,
} from '../../util/env/env-target';
import readStandardInput from '../../util/input/read-standard-input';
import cmd from '../../util/output/cmd';
import param from '../../util/output/param';
import withSpinner from '../../util/with-spinner';
import { emoji, prependEmoji } from '../../util/emoji';
import { isKnownError } from '../../util/env/known-error';
type Options = {
'--debug': boolean;
};
export default async function add(
ctx: NowContext,
opts: Options,
args: string[],
output: Output
) {
const {
authConfig: { token },
config,
} = ctx;
const { currentTeam } = config;
const { apiUrl } = ctx;
const debug = opts['--debug'];
const client = new Client({ apiUrl, token, currentTeam, debug });
const link = await getLinkedProject(output, client);
if (link.status === 'error') {
return link.exitCode;
} else if (link.status === 'not_linked') {
output.print(
`${chalk.red(
'Error!'
)} Your codebase isnt linked to a project on ZEIT Now. Run ${cmd(
'now'
)} to link it.\n`
);
return 1;
} else {
const { project } = link;
const stdInput = await readStandardInput();
let [envName, envTarget] = args;
if (args.length > 2) {
output.error(
`Invalid number of arguments. Usage: ${cmd(
`now env add <name> ${getEnvTargetPlaceholder()}`
)}`
);
return 1;
}
if (stdInput && (!envName || !envTarget)) {
output.error(
`Invalid number of arguments. Usage: ${cmd(
`now env add <name> <target> < <file>`
)}`
);
return 1;
}
let envTargets: ProjectEnvTarget[] = [];
if (envTarget) {
if (!isValidEnvTarget(envTarget)) {
output.error(
`The Environment ${param(
envTarget
)} is invalid. It must be one of: ${getEnvTargetPlaceholder()}.`
);
return 1;
}
envTargets.push(envTarget);
}
while (!envName) {
const { inputName } = await inquirer.prompt({
type: 'input',
name: 'inputName',
message: `Whats the name of the variable?`,
});
envName = inputName;
if (!inputName) {
output.error('Name cannot be empty');
}
}
const envs = await getEnvVariables(output, client, project.id);
const existing = new Set(
envs.filter(r => r.key === envName).map(r => r.target)
);
const choices = getEnvTargetChoices().filter(c => !existing.has(c.value));
if (choices.length === 0) {
output.error(
`The variable ${param(
envName
)} has already been added to all Environments. To remove, run ${cmd(
`now env rm ${envName}`
)}.`
);
return 1;
}
let envValue: string;
if (stdInput) {
envValue = stdInput;
} else {
const { inputValue } = await inquirer.prompt({
type: 'password',
name: 'inputValue',
message: `Whats the value of ${envName}?`,
});
envValue = inputValue || '';
}
while (envTargets.length === 0) {
const { inputTargets } = await inquirer.prompt({
name: 'inputTargets',
type: 'checkbox',
message: `Add ${envName} to which Environments (select multiple)?`,
choices,
});
envTargets = inputTargets;
if (inputTargets.length === 0) {
output.error('Please select at least one Environment');
}
}
const addStamp = stamp();
try {
await withSpinner('Saving', () =>
addEnvRecord(output, client, project.id, envName, envValue, envTargets)
);
} catch (error) {
if (isKnownError(error) && error.serverMessage) {
output.error(error.serverMessage);
return 1;
}
throw error;
}
output.print(
`${prependEmoji(
`Added Environment Variable ${chalk.bold(
envName
)} to Project ${chalk.bold(project.name)} ${chalk.gray(addStamp())}`,
emoji('success')
)}\n`
);
return 0;
}
}

View File

@@ -0,0 +1,114 @@
import chalk from 'chalk';
import { NowContext } from '../../types';
import createOutput from '../../util/output';
import getArgs from '../../util/get-args';
import getSubcommand from '../../util/get-subcommand';
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
import { getEnvTargetPlaceholder } from '../../util/env/env-target';
import handleError from '../../util/handle-error';
import logo from '../../util/output/logo';
import add from './add';
import pull from './pull';
import ls from './ls';
import rm from './rm';
const help = () => {
const placeholder = getEnvTargetPlaceholder();
console.log(`
${chalk.bold(`${logo} now env`)} [options] <command>
${chalk.dim('Commands:')}
ls [environment] List all variables for the specified Environment
add [name] [environment] Add an Environment Variable (see examples below)
rm [name] [environment] Remove an Environment Variable (see examples below)
pull [filename] Pull all Development Environment Variables from the cloud and write to a file [.env]
${chalk.dim('Options:')}
-h, --help Output usage information
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'
)} Path to the local ${'`now.json`'} file
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
'DIR'
)} Path to the global ${'`.now`'} directory
-d, --debug Debug mode [off]
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN'
)} Login token
${chalk.dim('Examples:')}
${chalk.gray('')} Add a new variable to multiple Environments
${chalk.cyan('$ now env add <name>')}
${chalk.cyan('$ now env add API_TOKEN')}
${chalk.gray('')} Add a new variable for a specific Environment
${chalk.cyan(`$ now env add <name> ${placeholder}`)}
${chalk.cyan('$ now env add DB_CONNECTION production')}
${chalk.gray('')} Add a new Environment Variable from stdin
${chalk.cyan(`$ cat <file> | now env add <name> ${placeholder}`)}
${chalk.cyan('$ cat ~/.npmrc | now env add NPM_RC preview')}
${chalk.cyan('$ now env add DB_PASS production < secret.txt')}
${chalk.gray('')} Remove an variable from multiple Environments
${chalk.cyan('$ now env rm <name>')}
${chalk.cyan('$ now env rm API_TOKEN')}
${chalk.gray('')} Remove a variable from a specific Environment
${chalk.cyan(`$ now env rm <name> ${placeholder}`)}
${chalk.cyan('$ now env rm NPM_RC preview')}
`);
};
const COMMAND_CONFIG = {
ls: ['ls', 'list'],
add: ['add'],
rm: ['rm', 'remove'],
pull: ['pull'],
};
export default async function main(ctx: NowContext) {
let argv;
try {
argv = getArgs(ctx.argv.slice(2), {
'--yes': Boolean,
'-y': '--yes',
});
} catch (error) {
handleError(error);
return 1;
}
if (argv['--help']) {
help();
return 2;
}
const output = createOutput({ debug: argv['--debug'] });
const { subcommand, args } = getSubcommand(argv._.slice(1), COMMAND_CONFIG);
switch (subcommand) {
case 'ls':
return ls(ctx, argv, args, output);
case 'add':
return add(ctx, argv, args, output);
case 'rm':
return rm(ctx, argv, args, output);
case 'pull':
return pull(ctx, argv, args, output);
default:
output.error(getInvalidSubcommand(COMMAND_CONFIG));
help();
return 2;
}
}

117
packages/now-cli/src/commands/env/ls.ts vendored Normal file
View File

@@ -0,0 +1,117 @@
import chalk from 'chalk';
import ms from 'ms';
import plural from 'pluralize';
import { Output } from '../../util/output';
import { ProjectEnvVariable, ProjectEnvTarget, NowContext } from '../../types';
import Client from '../../util/client';
import formatTable from '../../util/format-table';
import getEnvVariables from '../../util/env/get-env-records';
import {
isValidEnvTarget,
getEnvTargetPlaceholder,
} from '../../util/env/env-target';
import { getLinkedProject } from '../../util/projects/link';
import stamp from '../../util/output/stamp';
import cmd from '../../util/output/cmd';
import param from '../../util/output/param';
type Options = {
'--debug': boolean;
};
export default async function ls(
ctx: NowContext,
opts: Options,
args: string[],
output: Output
) {
const {
authConfig: { token },
config,
} = ctx;
const { currentTeam } = config;
const { apiUrl } = ctx;
const debug = opts['--debug'];
const client = new Client({ apiUrl, token, currentTeam, debug });
const link = await getLinkedProject(output, client);
if (link.status === 'error') {
return link.exitCode;
} else if (link.status === 'not_linked') {
output.print(
`${chalk.red(
'Error!'
)} Your codebase isnt linked to a project on ZEIT Now. Run ${cmd(
'now'
)} to link it.\n`
);
return 1;
} else {
if (args.length > 1) {
output.error(
`Invalid number of arguments. Usage: ${cmd(
`now env ls ${getEnvTargetPlaceholder()}`
)}`
);
return 1;
}
const { project } = link;
const envTarget = args[0] as ProjectEnvTarget | undefined;
if (!isValidEnvTarget(envTarget)) {
output.error(
`The Environment ${param(
envTarget
)} is invalid. It must be one of: ${getEnvTargetPlaceholder()}.`
);
return 1;
}
const lsStamp = stamp();
const records = await getEnvVariables(
output,
client,
project.id,
envTarget
);
output.log(
`${plural(
'Environment Variable',
records.length,
true
)} found in Project ${chalk.bold(project.name)} ${chalk.gray(lsStamp())}`
);
console.log(getTable(records));
return 0;
}
}
function getTable(records: ProjectEnvVariable[]) {
return formatTable(
['name', 'value', 'environment', 'created'],
['l', 'l', 'l', 'l', 'l'],
[
{
name: '',
rows: records.map(getRow),
},
]
);
}
function getRow({
key,
system = false,
target,
createdAt = 0,
}: ProjectEnvVariable) {
const now = Date.now();
return [
chalk.bold(key),
chalk.gray(chalk.italic(system ? 'Populated by System' : 'Encrypted')),
target || '',
`${ms(now - createdAt)} ago`,
];
}

View File

@@ -0,0 +1,120 @@
import chalk from 'chalk';
import { NowContext, ProjectEnvTarget } from '../../types';
import { Output } from '../../util/output';
import promptBool from '../../util/prompt-bool';
import Client from '../../util/client';
import stamp from '../../util/output/stamp';
import getEnvVariables from '../../util/env/get-env-records';
import getDecryptedSecret from '../../util/env/get-decrypted-secret';
import { getLinkedProject } from '../../util/projects/link';
import cmd from '../../util/output/cmd';
import param from '../../util/output/param';
import withSpinner from '../../util/with-spinner';
import { join } from 'path';
import { promises, existsSync } from 'fs';
import { emoji, prependEmoji } from '../../util/emoji';
const { writeFile } = promises;
type Options = {
'--debug': boolean;
'--yes': boolean;
};
export default async function pull(
ctx: NowContext,
opts: Options,
args: string[],
output: Output
) {
const {
authConfig: { token },
config,
} = ctx;
const { currentTeam } = config;
const { apiUrl } = ctx;
const debug = opts['--debug'];
const client = new Client({ apiUrl, token, currentTeam, debug });
const link = await getLinkedProject(output, client);
if (link.status === 'error') {
return link.exitCode;
} else if (link.status === 'not_linked') {
output.print(
`${chalk.red(
'Error!'
)} Your codebase isnt linked to a project on ZEIT Now. Run ${cmd(
'now'
)} to link it.\n`
);
return 1;
} else {
if (args.length > 1) {
output.error(
`Invalid number of arguments. Usage: ${cmd('now env pull <file>')}`
);
return 1;
}
const { project } = link;
const [filename = '.env'] = args;
const fullPath = join(process.cwd(), filename);
const exists = existsSync(fullPath);
const skipConfirmation = opts['--yes'];
if (
exists &&
!skipConfirmation &&
!(await promptBool(
output,
`Found existing file ${param(filename)}. Do you want to overwrite?`
))
) {
output.log('Aborted');
return 0;
}
output.print(
`Downloading Development Environment Variables for Project ${chalk.bold(
project.name
)}\n`
);
const pullStamp = stamp();
const records = await withSpinner('Downloading', async () => {
const dev = ProjectEnvTarget.Development;
const envs = await getEnvVariables(output, client, project.id, dev);
const values = await Promise.all(
envs.map(env => getDecryptedSecret(output, client, env.value))
);
const results: { key: string; value: string }[] = [];
for (let i = 0; i < values.length; i++) {
results.push({ key: envs[i].key, value: values[i] });
}
return results;
});
const contents =
records
.map(({ key, value }) => `${key}="${escapeValue(value)}"`)
.join('\n') + '\n';
await writeFile(fullPath, contents, 'utf8');
output.print(
`${prependEmoji(
`${exists ? 'Updated' : 'Created'} ${chalk.bold(
filename
)} file ${chalk.gray(pullStamp())}`,
emoji('success')
)}\n`
);
return 0;
}
}
function escapeValue(value: string) {
return value
.replace(new RegExp('\\"', 'g'), '\\"') // escape quotes
.replace(new RegExp('\n', 'g'), '\\n') // combine newlines (unix) into one line
.replace(new RegExp('\r', 'g'), '\\r'); // combine newlines (windows) into one line
}

168
packages/now-cli/src/commands/env/rm.ts vendored Normal file
View File

@@ -0,0 +1,168 @@
import chalk from 'chalk';
import inquirer from 'inquirer';
import { NowContext, ProjectEnvTarget } from '../../types';
import { Output } from '../../util/output';
import promptBool from '../../util/prompt-bool';
import { getLinkedProject } from '../../util/projects/link';
import removeEnvRecord from '../../util/env/remove-env-record';
import getEnvVariables from '../../util/env/get-env-records';
import {
isValidEnvTarget,
getEnvTargetPlaceholder,
getEnvTargetChoices,
} from '../../util/env/env-target';
import Client from '../../util/client';
import stamp from '../../util/output/stamp';
import cmd from '../../util/output/cmd';
import param from '../../util/output/param';
import withSpinner from '../../util/with-spinner';
import { emoji, prependEmoji } from '../../util/emoji';
import { isKnownError } from '../../util/env/known-error';
type Options = {
'--debug': boolean;
'--yes': boolean;
};
export default async function rm(
ctx: NowContext,
opts: Options,
args: string[],
output: Output
) {
const {
authConfig: { token },
config,
} = ctx;
const { currentTeam } = config;
const { apiUrl } = ctx;
const debug = opts['--debug'];
const client = new Client({ apiUrl, token, currentTeam, debug });
const link = await getLinkedProject(output, client);
if (link.status === 'error') {
return link.exitCode;
} else if (link.status === 'not_linked') {
output.print(
`${chalk.red(
'Error!'
)} Your codebase isnt linked to a project on ZEIT Now. Run ${cmd(
'now'
)} to link it.\n`
);
return 1;
} else {
if (args.length > 2) {
output.error(
`Invalid number of arguments. Usage: ${cmd(
`now env rm <name> ${getEnvTargetPlaceholder()}`
)}`
);
return 1;
}
const { project } = link;
let [envName, envTarget] = args;
let envTargets: ProjectEnvTarget[] = [];
if (envTarget) {
if (!isValidEnvTarget(envTarget)) {
output.error(
`The Environment ${param(
envTarget
)} is invalid. It must be one of: ${getEnvTargetPlaceholder()}.`
);
return 1;
}
envTargets.push(envTarget);
}
while (!envName) {
const { inputName } = await inquirer.prompt({
type: 'input',
name: 'inputName',
message: `Whats the name of the variable?`,
});
if (!inputName) {
output.error(`Name cannot be empty`);
continue;
}
envName = inputName;
}
const envs = await getEnvVariables(output, client, project.id);
const existing = new Set(
envs.filter(r => r.key === envName).map(r => r.target)
);
if (existing.size === 0) {
output.error(
`The Environment Variable ${param(envName)} was not found.\n`
);
return 1;
}
if (envTargets.length === 0) {
const choices = getEnvTargetChoices().filter(c => existing.has(c.value));
if (choices.length === 0) {
output.error(
`The Environment Variable ${param(
envName
)} was found but it is not assigned to any Environments.\n`
);
return 1;
} else if (choices.length === 1) {
envTargets = [choices[0].value];
} else {
const { inputTargets } = await inquirer.prompt({
name: 'inputTargets',
type: 'checkbox',
message: `Remove ${envName} from which Environments (select multiple)?`,
choices,
});
envTargets = inputTargets;
}
}
const skipConfirmation = opts['--yes'];
if (
!skipConfirmation &&
!(await promptBool(
output,
`Removing Environment Variable ${param(
envName
)} from Project ${chalk.bold(project.name)}. Are you sure?`
))
) {
output.log('Aborted');
return 0;
}
const rmStamp = stamp();
try {
await withSpinner('Removing', async () => {
for (const target of envTargets) {
await removeEnvRecord(output, client, project.id, envName, target);
}
});
} catch (error) {
if (isKnownError(error) && error.serverMessage) {
output.error(error.serverMessage);
return 1;
}
throw error;
}
output.print(
`${prependEmoji(
`Removed Environment Variable ${chalk.gray(rmStamp())}`,
emoji('success')
)}\n`
);
return 0;
}
}

View File

@@ -13,6 +13,7 @@ export default new Map([
['domain', 'domains'],
['domains', 'domains'],
['downgrade', 'upgrade'],
['env', 'env'],
['help', 'help'],
['init', 'init'],
['inspect', 'inspect'],

View File

@@ -185,7 +185,7 @@ function prepareFolder(cwd: string, folder: string, force?: boolean) {
folder
)}" already exists and is not an empty directory. You may use ${cmd(
'--force'
)} or ${cmd('--f')} to override it.`
)} or ${cmd('-f')} to override it.`
);
}
} else if (dest !== cwd) {

View File

@@ -17,6 +17,7 @@ import getScope from '../util/get-scope.ts';
import toHost from '../util/to-host';
import parseMeta from '../util/parse-meta';
import { isValidName } from '../util/is-valid-name';
import getCommandFlags from '../util/get-command-flags';
const help = () => {
console.log(`
@@ -222,7 +223,7 @@ export default async function main(ctx) {
debug(
'No deployments: attempting to find aliases that matches supplied app name'
);
const aliases = await getAliases(now);
const { aliases } = await getAliases(now);
const item = aliases.find(e => e.uid === app || e.alias === app);
if (item) {
@@ -332,11 +333,16 @@ export default async function main(ctx) {
hsep: ' '.repeat(4),
stringLength: strlen,
}
).replace(/^/gm, ' ')}\n\n`
).replace(/^/gm, ' ')}\n`
);
if (pagination && deployments.length === 20) {
log(`To display the next page use the flag --next ${pagination.next}`);
if (pagination && pagination.count === 20) {
const flags = getCommandFlags(argv, ['_', '--next']);
log(
`To display the next page run ${cmd(
`now ls${app ? ' ' + app : ''}${flags} --next ${pagination.next}`
)}`
);
}
}

View File

@@ -190,7 +190,10 @@ export default async function main(ctx) {
}
aliases = await Promise.all(
deployments.map(depl => getAliases(client, depl.uid))
deployments.map(async depl => {
const { aliases } = await getAliases(client, depl.uid);
return aliases;
})
);
} finally {
cancelWait();

View File

@@ -110,7 +110,7 @@ const main = async ctx => {
}
try {
await run({ output, token, contextName, currentTeam });
await run({ output, token, contextName, currentTeam, ctx });
} catch (err) {
handleError(err);
exit(1);
@@ -126,7 +126,7 @@ export default async ctx => {
}
};
async function run({ output, token, contextName, currentTeam }) {
async function run({ output, token, contextName, currentTeam, ctx }) {
const secrets = new NowSecrets({ apiUrl, token, debug, currentTeam });
const args = argv._.slice(1);
const start = Date.now();
@@ -260,7 +260,28 @@ async function run({ output, token, contextName, currentTeam }) {
return exit(1);
}
const [name, value] = args;
const [name, parsedValue] = args;
const [originalName, originalValue] = ctx.argv.slice(-2);
let value = parsedValue;
if (
name === originalName &&
typeof parsedValue === 'boolean' &&
parsedValue !== originalValue
) {
// Corner case where `mri` transforms the secret value into a boolean because
// it starts with a `-` so it thinks its a flag, so we use the original value instead.
value = originalValue;
}
if (typeof value === 'boolean') {
const example = chalk.cyan(`$ now secret add -- "${name}"`);
console.log(
`If your secret starts with '-', make sure to terminate command options with double dash and wrap it in quotes. Example: \n ${example} `
);
return exit(1);
}
await secrets.add(name, value);
const elapsed = ms(new Date() - start);

View File

@@ -216,6 +216,33 @@ export type DNSRecordData =
| SRVRecordData
| MXRecordData;
export interface Secret {
uid: string;
name: string;
value: string;
teamId?: string;
userId?: string;
projectId?: string;
created: string;
createdAt: number;
}
export enum ProjectEnvTarget {
Production = 'production',
Preview = 'preview',
Development = 'development',
}
export interface ProjectEnvVariable {
key: string;
value: string;
configurationId?: string | null;
createdAt?: number;
updatedAt?: number;
target?: ProjectEnvTarget;
system?: boolean;
}
export interface Project {
id: string;
name: string;
@@ -238,3 +265,9 @@ export interface ProjectLink {
projectId: string;
orgId: string;
}
export interface PaginationOptions {
prev: number;
count: number;
next?: number;
}

View File

@@ -6,6 +6,6 @@ export default async function deploymentIsAliased(
client: Client,
deployment: Deployment
) {
const aliases = await getAliases(client);
const { aliases } = await getAliases(client);
return aliases.some(alias => alias.deploymentId === deployment.uid);
}

View File

@@ -7,11 +7,16 @@ type Response = {
export default async function getAliases(
client: Client,
deploymentId?: string
deploymentId?: string,
next?: number
) {
let aliasUrl = `/v3/now/aliases?limit=20`;
if (next) {
aliasUrl += `&until=${next}`;
}
const to = deploymentId
? `/now/deployments/${deploymentId}/aliases`
: '/now/aliases';
: aliasUrl;
const payload = await client.fetch<Response>(to);
return payload.aliases || [];
return payload;
}

View File

@@ -3,6 +3,6 @@ import getAliases from './get-aliases';
import { Alias } from '../../types';
export default async function getDomainAliases(client: Client, domain: string) {
const aliases = await getAliases(client);
const { aliases } = await getAliases(client);
return aliases.filter((alias: Alias) => alias.alias.endsWith(domain));
}

View File

@@ -21,6 +21,7 @@ export default class Client extends EventEmitter {
_apiUrl: string;
_debug: boolean;
_forceNew: boolean;
_withCache: boolean;
_output: Output;
_token: string;
currentTeam?: string;
@@ -30,18 +31,21 @@ export default class Client extends EventEmitter {
token,
currentTeam,
forceNew = false,
withCache = false,
debug = false,
}: {
apiUrl: string;
token: string;
currentTeam?: string;
forceNew?: boolean;
withCache?: boolean;
debug?: boolean;
}) {
super();
this._token = token;
this._debug = debug;
this._forceNew = forceNew;
this._withCache = withCache;
this._output = createOutput({ debug });
this._apiUrl = apiUrl;
this._onRetry = this._onRetry.bind(this);

View File

@@ -66,6 +66,7 @@ export default async function processDeployment({
quiet: boolean;
nowConfig?: NowConfig;
force?: boolean;
withCache?: boolean;
org: Org;
projectName: string;
isSettingUpProject: boolean;
@@ -82,6 +83,7 @@ export default async function processDeployment({
requestBody,
deployStamp,
force,
withCache,
nowConfig,
quiet,
} = args;
@@ -99,6 +101,7 @@ export default async function processDeployment({
userAgent: ua,
path: paths[0],
force,
withCache,
skipAutoDetectionConfirmation,
};
@@ -205,6 +208,9 @@ export default async function processDeployment({
}
if (event.type === 'canceled') {
if (queuedSpinner) {
queuedSpinner();
}
if (buildSpinner) {
buildSpinner();
}

View File

@@ -27,6 +27,8 @@ import {
BuilderOutput,
BuildResultV3,
BuilderOutputs,
BuilderParams,
EnvConfigs,
} from './types';
import { normalizeRoutes } from '@now/routing-utils';
import getUpdateCommand from '../get-update-command';
@@ -45,7 +47,7 @@ const treeKill = promisify(_treeKill);
async function createBuildProcess(
match: BuildMatch,
buildEnv: EnvConfig,
envConfigs: EnvConfigs,
workPath: string,
output: Output,
yarnPath?: string
@@ -64,7 +66,7 @@ async function createBuildProcess(
const env: EnvConfig = {
...process.env,
PATH,
...buildEnv,
...envConfigs.allEnv,
NOW_REGION: 'dev1',
};
@@ -109,7 +111,7 @@ export async function executeBuild(
builderWithPkg: { runInProcess, builder, package: pkg },
} = match;
const { entrypoint } = match;
const { env, debug, buildEnv, yarnPath, cwd: workPath } = devServer;
const { debug, envConfigs, yarnPath, cwd: workPath } = devServer;
const startTime = Date.now();
const showBuildTimestamp =
@@ -131,14 +133,14 @@ export async function executeBuild(
devServer.output.debug(`Creating build process for ${entrypoint}`);
buildProcess = await createBuildProcess(
match,
buildEnv,
envConfigs,
workPath,
devServer.output,
yarnPath
);
}
const buildParams = {
const buildParams: BuilderParams = {
files,
entrypoint,
workPath,
@@ -148,8 +150,10 @@ export async function executeBuild(
requestPath,
filesChanged,
filesRemoved,
env,
buildEnv,
// This env distiniction is only necessary to maintain
// backwards compatibility with the `@now/next` builder.
env: envConfigs.runEnv,
buildEnv: envConfigs.buildEnv,
},
};
@@ -361,7 +365,7 @@ export async function executeBuild(
Variables: {
...nowConfig.env,
...asset.environment,
...env,
...envConfigs.runEnv,
NOW_REGION: 'dev1',
},
},

View File

@@ -54,16 +54,18 @@ export async function devRouter(
missRoutes?: RouteConfig[],
phase?: HandleValue | null
): Promise<RouteResult> {
let found: RouteResult | undefined;
let result: RouteResult | undefined;
let { query, pathname: reqPathname = '/' } = url.parse(reqUrl, true);
const combinedHeaders: HttpHeadersConfig = { ...previousHeaders };
let status: number | undefined;
let isContinue = false;
// Try route match
if (routes) {
let idx = -1;
for (const routeConfig of routes) {
idx++;
isContinue = false;
if (isHandler(routeConfig)) {
// We don't expect any Handle, only Source routes
@@ -110,6 +112,7 @@ export async function devRouter(
status = routeConfig.status;
}
reqPathname = destPath;
isContinue = true;
continue;
}
@@ -149,9 +152,10 @@ export async function devRouter(
const isDestUrl = isURL(destPath);
if (isDestUrl) {
found = {
result = {
found: true,
dest: destPath,
continue: isContinue,
userDest: false,
isDestUrl,
status: routeConfig.status || status,
@@ -167,9 +171,10 @@ export async function devRouter(
destPath = `/${destPath}`;
}
const { pathname, query } = url.parse(destPath, true);
found = {
result = {
found: true,
dest: pathname || '/',
continue: isContinue,
userDest: Boolean(routeConfig.dest),
isDestUrl,
status: routeConfig.status || status,
@@ -185,10 +190,11 @@ export async function devRouter(
}
}
if (!found) {
found = {
if (!result) {
result = {
found: false,
dest: reqPathname,
continue: isContinue,
status,
isDestUrl: false,
uri_args: query,
@@ -197,5 +203,5 @@ export async function devRouter(
};
}
return found;
return result;
}

View File

@@ -85,6 +85,8 @@ import {
ListenSpec,
RouteConfig,
RouteResult,
HttpHeadersConfig,
EnvConfigs,
} from './types';
interface FSEvent {
@@ -108,8 +110,7 @@ export default class DevServer {
public cwd: string;
public debug: boolean;
public output: Output;
public env: EnvConfig;
public buildEnv: EnvConfig;
public envConfigs: EnvConfigs;
public frameworkSlug: string | null;
public files: BuilderInputs;
public yarnPath: string;
@@ -141,8 +142,7 @@ export default class DevServer {
this.cwd = cwd;
this.debug = options.debug;
this.output = options.output;
this.env = {};
this.buildEnv = {};
this.envConfigs = { buildEnv: {}, runEnv: {}, allEnv: {} };
this.files = {};
this.address = '';
this.devCommand = options.devCommand;
@@ -739,13 +739,13 @@ export default class DevServer {
// Retrieve the path of the native module
const nowConfig = await this.getNowConfig(false);
const nowConfigBuild = nowConfig.build || {};
const [env, buildEnv] = await Promise.all([
const [runEnv, buildEnv] = await Promise.all([
this.getLocalEnv('.env', nowConfig.env),
this.getLocalEnv('.env.build', nowConfigBuild.env),
]);
Object.assign(process.env, buildEnv);
this.env = env;
this.buildEnv = buildEnv;
const allEnv = { ...buildEnv, ...runEnv };
Object.assign(process.env, allEnv);
this.envConfigs = { buildEnv, runEnv, allEnv };
const opts = { output: this.output, isBuilds: true };
const files = await getFiles(this.cwd, nowConfig, opts);
@@ -1264,19 +1264,25 @@ export default class DevServer {
let routeResult: RouteResult | null = null;
let match: BuildMatch | null = null;
let statusCode: number | undefined;
let prevUrl = req.url;
let prevHeaders: HttpHeadersConfig = {};
for (const phase of phases) {
statusCode = undefined;
const phaseRoutes = handleMap.get(phase) || [];
routeResult = await devRouter(
req.url,
prevUrl,
req.method,
phaseRoutes,
this,
undefined,
prevHeaders,
missRoutes,
phase
);
prevUrl =
routeResult.continue && routeResult.dest ? routeResult.dest : req.url;
prevHeaders =
routeResult.continue && routeResult.headers ? routeResult.headers : {};
if (routeResult.isDestUrl) {
// Mix the `routes` result dest query params into the req path
@@ -1682,8 +1688,7 @@ export default class DevServer {
FORCE_COLOR: process.stdout.isTTY ? '1' : '0',
...(this.frameworkSlug === 'create-react-app' ? { BROWSER: 'none' } : {}),
...process.env,
...this.buildEnv,
...(this.frameworkSlug === 'nextjs' ? this.env : {}),
...this.envConfigs.allEnv,
NOW_REGION: 'dev1',
PORT: `${port}`,
};

View File

@@ -26,6 +26,23 @@ export interface EnvConfig {
[name: string]: string | undefined;
}
export interface EnvConfigs {
/**
* environment variables from `.env.build` file (deprecated)
*/
buildEnv: EnvConfig;
/**
* environment variables from `.env` file
*/
runEnv: EnvConfig;
/**
* environment variables from `.env` and `.env.build`
*/
allEnv: EnvConfig;
}
export interface BuildMatch extends BuildConfig {
entrypoint: string;
builderWithPkg: BuilderWithPackage;
@@ -147,6 +164,8 @@ export interface RouteResult {
found: boolean;
// "dest": <string of the dest, either file for lambda or full url for remote>
dest: string;
// `true` if last route in current phase matched but set `continue: true`
continue: boolean;
// "status": <integer in case exit code is intended to be changed>
status?: number;
// "headers": <object of the added response header values>

View File

@@ -42,6 +42,6 @@ function getAddDomainName(domainNames: string[]) {
}
async function getDomainNames(client: Client, contextName: string) {
const domains = await getDomains(client, contextName);
const { domains } = await getDomains(client, contextName);
return domains.map(domain => domain.name).sort((a, b) => a.localeCompare(b));
}

View File

@@ -1,17 +1,24 @@
import chalk from 'chalk';
import { Domain } from '../../types';
import { Domain, PaginationOptions } from '../../types';
import Client from '../client';
import wait from '../output/wait';
type Response = {
domains: Domain[];
pagination: PaginationOptions;
};
export default async function getDomains(client: Client, contextName: string) {
export default async function getDomains(
client: Client,
contextName: string,
next?: number
) {
let domainUrl = `/v5/domains?limit=20`;
if (next) {
domainUrl += `&until=${next}`;
}
const cancelWait = wait(`Fetching domains under ${chalk.bold(contextName)}`);
const { domains } = await client.fetch<Response>('/v4/domains');
const domains = await client.fetch<Response>(domainUrl);
cancelWait();
return domains.sort(
(domainA, domainB) => domainB.createdAt - domainA.createdAt
);
return domains;
}

View File

@@ -0,0 +1,61 @@
import { Output } from '../output';
import Client from '../client';
import { Secret, ProjectEnvTarget, ProjectEnvVariable } from '../../types';
import { customAlphabet } from 'nanoid';
import slugify from '@sindresorhus/slugify';
export default async function addEnvRecord(
output: Output,
client: Client,
projectId: string,
envName: string,
envValue: string | undefined,
targets: ProjectEnvTarget[]
): Promise<void> {
output.debug(
`Adding Environment Variable ${envName} to ${targets.length} targets`
);
let values: string[] | undefined;
if (envValue) {
const urlSecret = `/v2/now/secrets/${encodeURIComponent(envName)}`;
const secrets = await Promise.all(
targets.map(target =>
client.fetch<Secret>(urlSecret, {
method: 'POST',
body: JSON.stringify({
name: generateSecretName(envName, target),
value: envValue,
projectId: projectId,
decryptable: target === ProjectEnvTarget.Development,
}),
})
)
);
values = secrets.map(secret => secret.uid);
}
const body = targets.map((target, i) => ({
key: envName,
value: values ? values[i] : '',
target,
}));
const urlProject = `/v4/projects/${projectId}/env`;
await client.fetch<ProjectEnvVariable>(urlProject, {
method: 'POST',
body: JSON.stringify(body),
});
}
const randomSecretSuffix = customAlphabet(
'123456789abcdefghijklmnopqrstuvwxyz',
4
);
function generateSecretName(envName: string, target: ProjectEnvTarget) {
return `${
slugify(envName).substring(0, 80) // we truncate because the max secret length is 100
}-${target}-${randomSecretSuffix()}`;
}

View File

@@ -0,0 +1,22 @@
import { ProjectEnvTarget } from '../../types';
function envTargets(): string[] {
return Object.values(ProjectEnvTarget);
}
export function getEnvTargetChoices() {
return Object.entries(ProjectEnvTarget).map(([key, value]) => ({
name: key,
value: value,
}));
}
export function isValidEnvTarget(
target?: string
): target is ProjectEnvTarget | undefined {
return typeof target === 'undefined' || envTargets().includes(target);
}
export function getEnvTargetPlaceholder() {
return `<${envTargets().join(' | ')}>`;
}

View File

@@ -0,0 +1,17 @@
import { Output } from '../output';
import Client from '../client';
import { Secret } from '../../types';
export default async function getDecryptedSecret(
output: Output,
client: Client,
secretId: string
): Promise<string> {
if (!secretId) {
return '';
}
output.debug(`Fetching decrypted secret ${secretId}`);
const url = `/v2/now/secrets/${secretId}?decrypt=true`;
const secret = await client.fetch<Secret>(url);
return secret.value;
}

View File

@@ -0,0 +1,18 @@
import { Output } from '../output';
import Client from '../client';
import { ProjectEnvVariable, ProjectEnvTarget } from '../../types';
export default async function getEnvVariables(
output: Output,
client: Client,
projectId: string,
target?: ProjectEnvTarget
): Promise<ProjectEnvVariable[]> {
output.debug(
`Fetching Environment Variables of project ${projectId} and target ${target}`
);
const qs = target ? `?target=${encodeURIComponent(target)}` : '';
const url = `/v4/projects/${projectId}/env${qs}`;
const records = await client.fetch<ProjectEnvVariable[]>(url);
return records;
}

View File

@@ -0,0 +1,13 @@
const knownErrorsCodes = new Set([
'PAYMENT_REQUIRED',
'BAD_REQUEST',
'SYSTEM_ENV_WITH_VALUE',
'RESERVED_ENV_VARIABLE',
'ENV_ALREADY_EXISTS',
'ENV_SHOULD_BE_A_SECRET',
]);
export function isKnownError(error: { code?: string }) {
const code = error && typeof error.code === 'string' ? error.code : '';
return knownErrorsCodes.has(code.toUpperCase());
}

View File

@@ -0,0 +1,38 @@
import { Output } from '../output';
import Client from '../client';
import { ProjectEnvTarget, Secret, ProjectEnvVariable } from '../../types';
export default async function removeEnvRecord(
output: Output,
client: Client,
projectId: string,
envName: string,
target?: ProjectEnvTarget
): Promise<void> {
output.debug(
`Removing Environment Variable ${envName} from target ${target}`
);
const qs = target ? `?target=${encodeURIComponent(target)}` : '';
const urlProject = `/v4/projects/${projectId}/env/${encodeURIComponent(
envName
)}${qs}`;
const env = await client.fetch<ProjectEnvVariable>(urlProject, {
method: 'DELETE',
});
if (env && env.value) {
const idOrName = env.value.startsWith('@') ? env.value.slice(1) : env.value;
const urlSecret = `/v2/now/secrets/${idOrName}`;
const secret = await client.fetch<Secret>(urlSecret);
// Since integrations add global secrets, we must only delete if the secret was
// specifically added to this project
if (secret && secret.projectId === projectId) {
await client.fetch<Secret>(urlSecret, {
method: 'DELETE',
});
}
}
}

View File

@@ -0,0 +1,20 @@
/*
This function returns the provided arguments from a command in a string format.
Example: if `argv` is { '--debug': true, '--all': true, '--scope': 'zeit' },
the output will be '--debug --all --scope zeit'.
Flags can be excluded using the `excludeFlags` param.
*/
export default function getCommandFlags(
argv: { [key: string]: any },
excludeFlags: string[] = []
) {
const flags = Object.keys(argv)
.filter(key => !excludeFlags.includes(key))
.map(
key => `${key}${typeof argv[key] !== 'boolean' ? ' ' + argv[key] : ''}`
);
return flags.length > 0 ? ` ${flags.join(' ')}` : '';
}

View File

@@ -0,0 +1,9 @@
type CommandConfig = {
[command: string]: string[];
};
export default function getInvalidSubcommand(config: CommandConfig) {
return `Please specify a valid subcommand: ${Object.keys(config).join(
' | '
)}`;
}

View File

@@ -29,12 +29,20 @@ const IS_WIN = process.platform.startsWith('win');
const SEP = IS_WIN ? '\\' : '/';
export default class Now extends EventEmitter {
constructor({ apiUrl, token, currentTeam, forceNew = false, debug = false }) {
constructor({
apiUrl,
token,
currentTeam,
forceNew = false,
withCache = false,
debug = false,
}) {
super();
this._token = token;
this._debug = debug;
this._forceNew = forceNew;
this._withCache = withCache;
this._output = createOutput({ debug });
this._apiUrl = apiUrl;
this._onRetry = this._onRetry.bind(this);
@@ -64,6 +72,7 @@ export default class Now extends EventEmitter {
env,
build,
forceNew = false,
withCache = false,
target = null,
deployStamp,
projectSettings,
@@ -155,6 +164,7 @@ export default class Now extends EventEmitter {
meta,
public: wantsPublic || nowConfig.public,
forceNew,
withCache,
name,
project,
description,
@@ -180,6 +190,7 @@ export default class Now extends EventEmitter {
quiet,
nowConfig,
force: forceNew,
withCache,
org,
projectName: name,
isSettingUpProject,
@@ -377,7 +388,12 @@ export default class Now extends EventEmitter {
if (!app && !Object.keys(meta).length) {
// Get the 20 latest projects and their latest deployment
const query = new URLSearchParams({ limit: (20).toString() });
const projects = await fetchRetry(`/v2/projects/?${query}`);
if (nextTimestamp) {
query.set('until', String(nextTimestamp));
}
const { projects, pagination } = await fetchRetry(
`/v4/projects/?${query}`
);
const deployments = await Promise.all(
projects.map(async ({ id: projectId }) => {
@@ -389,7 +405,7 @@ export default class Now extends EventEmitter {
})
);
return { deployments: deployments.filter(x => x) };
return { deployments: deployments.filter(x => x), pagination };
}
const query = new URLSearchParams();

View File

@@ -0,0 +1,13 @@
export default async function readStandardInput(): Promise<string> {
return new Promise<string>(resolve => {
setTimeout(() => resolve(''), 500);
if (process.stdin.isTTY) {
// found tty so we know there is nothing piped to stdin
resolve('');
} else {
process.stdin.setEncoding('utf8');
process.stdin.once('data', resolve);
}
});
}

View File

@@ -1,8 +1,8 @@
import chalk from 'chalk';
import { Output } from './output';
async function promptBool(output: Output, message: string) {
return new Promise(resolve => {
async function promptBool(output: Output, message: string): Promise<boolean> {
return new Promise<boolean>(resolve => {
output.print(`${chalk.gray('>')} ${message} ${chalk.gray('[y/N] ')}`);
process.stdin
.on('data', d => {

View File

@@ -10,6 +10,7 @@ test('[dev-router] 301 redirection', async t => {
t.deepEqual(result, {
found: true,
dest: '/redirect',
continue: false,
status: 301,
headers: { location: 'https://zeit.co' },
uri_args: {},
@@ -28,6 +29,7 @@ test('[dev-router] captured groups', async t => {
t.deepEqual(result, {
found: true,
dest: '/endpoints/user.js',
continue: false,
status: undefined,
headers: {},
uri_args: {},
@@ -46,6 +48,7 @@ test('[dev-router] named groups', async t => {
t.deepEqual(result, {
found: true,
dest: '/user.js',
continue: false,
status: undefined,
headers: {},
uri_args: { id: '123' },
@@ -69,6 +72,7 @@ test('[dev-router] optional named groups', async t => {
t.deepEqual(result, {
found: true,
dest: '/api/functions/hello/index.js',
continue: false,
status: undefined,
headers: {},
uri_args: { name: '' },
@@ -88,6 +92,7 @@ test('[dev-router] proxy_pass', async t => {
t.deepEqual(result, {
found: true,
dest: 'https://zeit.co',
continue: false,
status: undefined,
headers: {},
uri_args: {},
@@ -109,6 +114,7 @@ test('[dev-router] methods', async t => {
t.deepEqual(result, {
found: true,
dest: '/get',
continue: false,
status: undefined,
headers: {},
uri_args: {},
@@ -123,6 +129,7 @@ test('[dev-router] methods', async t => {
t.deepEqual(result, {
found: true,
dest: '/post',
continue: false,
status: undefined,
headers: {},
uri_args: {},
@@ -141,6 +148,7 @@ test('[dev-router] match without prefix slash', async t => {
t.deepEqual(result, {
found: true,
dest: '/endpoints/user.js',
continue: false,
status: undefined,
headers: {},
uri_args: {},
@@ -164,6 +172,7 @@ test('[dev-router] match with needed prefixed slash', async t => {
t.deepEqual(result, {
found: true,
dest: '/some/dest',
continue: false,
userDest: true,
isDestUrl: false,
phase: undefined,
@@ -197,6 +206,7 @@ test('[dev-router] `continue: true` with fallthrough', async t => {
t.deepEqual(result, {
found: false,
dest: '/_next/static/chunks/0.js',
continue: true,
isDestUrl: false,
phase: undefined,
status: undefined,
@@ -230,6 +240,7 @@ test('[dev-router] `continue: true` with match', async t => {
t.deepEqual(result, {
found: true,
dest: '/hi',
continue: false,
status: undefined,
userDest: true,
isDestUrl: false,
@@ -253,6 +264,7 @@ test('[dev-router] match with catch-all with prefix slash', async t => {
t.deepEqual(result, {
found: true,
dest: '/www/',
continue: false,
userDest: true,
isDestUrl: false,
phase: undefined,
@@ -271,6 +283,7 @@ test('[dev-router] match with catch-all with no prefix slash', async t => {
t.deepEqual(result, {
found: true,
dest: '/www/',
continue: false,
userDest: true,
isDestUrl: false,
phase: undefined,
@@ -295,6 +308,7 @@ test('[dev-router] `continue: true` with `dest`', async t => {
t.deepEqual(result, {
found: true,
dest: 'http://localhost:5000/a/foo',
continue: false,
status: undefined,
headers: {},
uri_args: {},

View File

@@ -1 +1,2 @@
SKIP_PREFLIGHT_CHECK=true
BROWSER=none

View File

@@ -19,7 +19,7 @@
"url": "https://github.com/marko-js-samples/marko-starter-demo"
},
"scripts": {
"build": "NODE_ENV=production marko-starter build && mv dist public",
"build": "NODE_ENV=production marko-starter build && rm -rf public && mv dist public",
"format": "prettier src/**/*.{js,css,less} --write && marko-prettyprint src",
"lint": "eslint src",
"serve": "NODE_ENV=production marko-starter serve-static",

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