Compare commits

...

55 Commits

Author SHA1 Message Date
Steven
1781376d47 Publish Canary
- @now/build-utils@2.1.2-canary.3
 - @now/ruby@1.0.3-canary.4
 - @now/static-build@0.14.13-canary.6
2020-03-20 15:04:52 -04:00
Steven
d9fda14969 [now-ruby] Upgrade to Ruby 2.7 (#3872)
- Change default version to Ruby `2.7.x` to match our static builds such as jekyll
- Detect ruby version in `Gemfile` in case the user wishes to downgrade to Ruby `2.5.x`
- Print nicer error message in `now dev`

cc @nathancahill @m5o
2020-03-20 18:54:57 +00:00
Steven
a4de9272e7 [now-static-build] Add test for puppeteer during build (#3911)
We recently updated the build image to add the necessary dependencies so that `puppeteer` can run during the build step.

This PR adds a test that takes a screenshot and prints metrics during a static build.

This is necessary to support `react-snap` (along with a few flags in `package.json`).

```json
{
  "reactSnap": {
    "puppeteerArgs": [
      "--no-sandbox",
      "--disable-setuid-sandbox"
    ]
  }
}
```

- Fixes https://github.com/zeit/now-builders/issues/517
- Fixes #2357
2020-03-20 11:54:51 -04:00
Andy Bitz
9b9037de91 Publish Canary
- @now/build-utils@2.1.2-canary.2
 - @now/next@2.4.1-canary.4
 - @now/static-build@0.14.13-canary.5
2020-03-20 15:37:07 +01:00
Andy
8d18c65e3e [now-next][now-static-build][now-build-utils] Use util to get node .bin in path (#3946)
https://zeit.atlassian.net/browse/PRODUCT-1380

This makes `now-next` consider the `node_modules/.bin` path if a custom build command was specified, which makes it work like `now-static-build`.
2020-03-20 14:31:53 +00:00
Ana Trajkovska
e7d7de61b6 Publish Canary
- now@17.0.5-canary.13
 - @now/next@2.4.1-canary.3
2020-03-20 13:04:29 +01:00
Ana Trajkovska
11927883c3 Fix pagination on now ls project (#3945) 2020-03-20 13:03:03 +01:00
Andy
57d25b184b [now-next] Add support for the outputDirectory option (#3897)
* [now-next] Add support for the outputDirectory option

* Add test

* Remove build command

* Remove check

* Add build command

* Rename directory

* Rename

* Fix test and output directory
2020-03-20 11:42:54 +01:00
Steven
95f716fb3f Publish Canary
- now@17.0.5-canary.12
 - @now/next@2.4.1-canary.2
2020-03-19 16:55:36 -04:00
Steven
8dd52605be [now-cli] Print link to more details on error (#3944)
This PR updates API Errors to support the `error.link` property.

Unlike `error.slug` which is only a path to an error message, `error.link` contains the full URL.


### Example Output

```
$ now
Error! Serverless Functions.........etc
> More details: https://zeit.ink/...etc
```
2020-03-19 20:49:57 +00:00
JJ Kasper
4b9c6a2a2a [now-next] Make sure to set 404 status for /404 route itself (#3924)
As discussed this makes sure to set the `404` status on the `/404` path itself
2020-03-19 18:24:20 +00:00
Steven
17f92a5ad3 Publish Canary
- now@17.0.5-canary.11
2020-03-19 13:59:08 -04:00
Ana Trajkovska
0aab7cc509 Fix error on now ls (#3942)
Co-authored-by: Leo Lamprecht <leo@zeit.co>
2020-03-19 18:45:24 +01:00
Steven
b39622b271 [tests] Fix publish job environment variables (#3943) 2020-03-19 18:44:59 +01:00
Ana Trajkovska
1e9aeee8e9 Publish Canary
- now@17.0.5-canary.10
2020-03-19 17:37:56 +01:00
Ana Trajkovska
49fac0dfad Paginate listing deployments for a project (#3933) 2020-03-19 17:21:13 +01:00
Steven
a668df829f Publish Canary
- @now/build-utils@2.1.2-canary.1
 - now@17.0.5-canary.9
 - now-client@7.0.1-canary.4
 - @now/static-build@0.14.13-canary.4
2020-03-19 10:30:33 -04:00
Steven
3d4ef1f825 [now-cli][now-client] Revert major version per #3939 2020-03-19 10:30:07 -04:00
Steven
f986daa1cc [now-client] Fix lint error: forbidden non-null assertion (#3941)
Fixes the following lint error: `Forbidden non-null assertion`.

<img src="https://user-images.githubusercontent.com/229881/77072131-98b8ab80-69c3-11ea-84f5-e45be43951f9.png" height=200 />

I realized this logic was somewhat brittle because it relied on `/` path separators so I switched it to use the native file name function `basename()` to determine if a file begins with a dot.
2020-03-19 13:44:51 +00:00
Steven
549c8777ba [tests] Fix test retry and allow local token (#3940)
This PR updates the way we run integration tests (the ones that create test deployments) so that it will be less likely to fail. 

A couple side effects to this PR:

- To run the tests locally, you must set `NOW_TOKEN` env var (can be found in `~/.now/auth.json`).
- PRs from forked repos won't run tests because they now rely on a secret in GH Actions.
- A couple alias tests that require certs need to be disabled because they will fail.

[PRODUCT-2093]

[PRODUCT-2093]: https://zeit.atlassian.net/browse/PRODUCT-2093
2020-03-19 13:08:46 +00:00
Andy
51d7242fda Revert "[now-cli][now-client] (Major) Remove legacy code (#3840)" (#3939)
* Revert "[now-cli][now-client] (Major) Remove legacy code (#3840)"

* Remove get

* Add projectId to .now/project.json
2020-03-19 00:16:51 +01:00
Nathan Rajlich
36db0e5bab [now-cli] Catch process.kill() for dev process and mute "ESRCH" (#3927)
"ESRCH" error means that the process is no longer running, and thus
already shut down. No need to throw in that case so just ignore the
error.

Fixes: https://sentry.io/organizations/zeithq/issues/1568104652

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-03-18 13:49:22 -07:00
Steven
99368b4248 [all] Fix test fixture pkg names and increase retry (#3935)
Fixes test warnings from `jest-hast-map`:

```
[now-static-build] Running yarn test-integration-once
$ jest --env node --verbose --runInBand test/integration.test.js
jest-haste-map: Haste module naming collision: 12-create-react-app
  The following files share their name; please adjust your hasteImpl:
    * <rootDir>/test/fixtures/12-create-react-app/package.json
    * <rootDir>/test/fixtures/26-ejected-cra/package.json

jest-haste-map: Haste module naming collision: gatsby-starter-default
  The following files share their name; please adjust your hasteImpl:
    * <rootDir>/test/fixtures/10-gatsby/package.json
    * <rootDir>/test/fixtures/10-gatsby-without-build-script/package.json

jest-haste-map: Haste module naming collision: gohugo-default-theme
  The following files share their name; please adjust your hasteImpl:
    * <rootDir>/test/fixtures/31-hugo/themes/ananke/package.json
    * <rootDir>/test/fixtures/46-hugo-with-framework/themes/ananke/package.json

jest-haste-map: Haste module naming collision: gohugo-default-styles
  The following files share their name; please adjust your hasteImpl:
    * <rootDir>/test/fixtures/31-hugo/themes/ananke/src/package.json
    * <rootDir>/test/fixtures/46-hugo-with-framework/themes/ananke/src/package.json

jest-haste-map: Haste module naming collision: 47-nuxt-with-custom-output
  The following files share their name; please adjust your hasteImpl:
    * <rootDir>/test/fixtures/47-nuxt-with-custom-output/package.json
    * <rootDir>/test/fixtures/48-nuxt-without-framework/package.json
```

Also increased test retry to 5.
2020-03-18 16:37:19 -04:00
Andy Bitz
95daf0e292 Publish Canary
- @now/frameworks@0.0.11-canary.2
 - now@18.0.0-canary.12
 - @now/static-build@0.14.13-canary.3
2020-03-18 12:53:21 +01:00
Andy
8bfa9c1a42 [now-cli] Fix ID check for orgs (#3934)
* [now-cli] Fix ID check for orgs

* Validate project settings

* Fix check
2020-03-18 12:51:52 +01:00
Shu Uesugi
4208dc0466 Add missing websites to frameworks (#3814) 2020-03-18 00:08:07 +01:00
Leo Lamprecht
00ae011b95 Use correct frameworks endpoint (#3932) 2020-03-17 23:02:30 +01:00
Andy
a770991a81 [now-cli] Restore now alias <domain> (#3910)
* [now-cli] Restore `now alias <domain>`

* Fix test

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-03-17 22:57:02 +01:00
Leo Lamprecht
f80a6d6392 Add support for /api/v1/frameworks to the framework API (#3931)
* Add support for `/api/v1/frameworks` to the framework API

* Update now.json

* Add another rewrite

Co-authored-by: Andy <AndyBitz@users.noreply.github.com>
Co-authored-by: Andy Bitz <artzbitz@gmail.com>
2020-03-17 22:37:38 +01:00
Andy
30777384ec Revert "Rewrite all paths to frameworks API (#3929)" (#3930)
This reverts commit ff18788b20.
2020-03-17 22:13:06 +01:00
Yamagishi Kazutoshi
0c719b7f6a [now-static-build] Add defaultRoutes for docusaurus v2 (#3909)
Add a default router for Docusaurus v2 to add strong asset caching and 404 page fallback.
2020-03-17 17:12:06 -04:00
Leo Lamprecht
ff18788b20 Rewrite all paths to frameworks API (#3929) 2020-03-17 22:11:22 +01:00
Steven
752ab39787 Publish Canary
- @now/frameworks@0.0.11-canary.1
 - now@18.0.0-canary.11
 - @now/static-build@0.14.13-canary.2
2020-03-17 16:33:13 -04:00
Steven
c1df8c8bd1 [now-static-build] Update error message to mention project settings (#3926)
This PR improves the error message to make it actionable.

The docs will be updated in https://github.com/zeit/docs/pull/1661 with even more detail.
2020-03-17 16:29:43 -04:00
Andy
25fd1df35d [frameworks] Adjust placeholder for build command (#3928) 2020-03-17 20:31:19 +01:00
Steven
d32ab1e0d9 [examples] Update jekyll and middleman to use bundler 2.1.4 (#3923)
These examples were using an old version of Bundler which didn't match our tests and would fail with:

```
/ruby27/lib/ruby/2.7.0/rubygems.rb:275:in `find_spec_for_exe': Could not find 'bundler' (1.17.2) required by your /zeit/6f4b9e46/Gemfile.lock. (Gem::GemNotFoundException)
To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:1.17.2`
	from /ruby27/lib/ruby/2.7.0/rubygems.rb:294:in `activate_bin_path'
	from /ruby27/bin/bundle:23:in `<main>'
```

I ran `bundle update --bundler` in each of these directories and it only updated the version in `Gemfile.lock` because 2.x is mostly backwards compatible.
2020-03-17 11:54:20 -04:00
Steven
a69c460760 [now-cli] Fix test for username (#3916)
We renamed the CI Bot so this will use the name from the context rather than hardcoding the name.
2020-03-17 11:23:21 -04:00
Andy
b985853f15 [frameworks] Remove quotes from placeholder (#3921) 2020-03-17 15:37:24 +01:00
Andy
94e607a93a [frameworks] Fix more placeholders (#3920)
* [frameworks] Fix order in build placeholder

* [frameworks] Adjust order for more frameworks
2020-03-17 15:31:01 +01:00
Andy
f97a81fa14 [frameworks] Fix order in build placeholder (#3918) 2020-03-17 15:20:47 +01:00
Andy
6e28438eb4 [frameworks] Change build placeholder (#3917) 2020-03-17 15:08:28 +01:00
Shu Ding
8fcdf3f458 improve brunch example (#3906) 2020-03-17 00:33:08 +08:00
dependabot[bot]
dbf0cc3562 Bump acorn from 5.7.3 to 5.7.4 (#3913)
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-03-15 11:55:13 +01:00
Steven
27ccfa7e7a Publish Canary
- now@18.0.0-canary.10
2020-03-13 14:22:52 -04:00
Steven
f37edbc670 [now-cli] Fix invalid token error message during project link (#3907)
This PR improves the error message and prevents "An unexpected error occurred" when the token is invalid during a project link step.

I also added the `--token` option to `now dev --help` .

Lastly, I updated `now logout` to work correctly when the token is invalid.

- Fixes #3772 
- Fixes #3786
2020-03-13 00:10:11 +00:00
JJ Kasper
b7943e83d2 Publish Canary
- @now/next@2.4.1-canary.1
 - @now/routing-utils@1.7.1-canary.1
2020-03-12 11:45:18 -05:00
JJ Kasper
300ed5b952 [now-next] Implement new handles for custom routes (#3892)
This implements the new handles from https://github.com/zeit/now/pull/3876 to allow us to ensure the proper order for `rewrites`, `redirects`, and `headers` in Next.js. I also added in the tests from the Next.js [custom-routes test suite](https://github.com/zeit/next.js/tree/canary/test/integration/custom-routes) to ensure we're matching behavior. 

To help keep track of what each probe is testing I added support for parsing the `now.json` files in `testDeployment` as [JSON5](https://www.npmjs.com/package/json5) to allow adding comments before each probe. If this is undesired I can remove this specific change even though it makes managing the fixture tests much easier
2020-03-12 03:18:33 +00:00
Andy Bitz
9e6ebfb3ec Publish Canary
- now@18.0.0-canary.9
2020-03-11 23:02:30 +01:00
Andy
f49620790c [now-cli] Proper error message when now alias misses arguments (#3904)
We need to show a proper error message when `now alias` misses arguments.
2020-03-11 21:57:02 +00:00
Steven
84065688b5 Publish Canary
- now@18.0.0-canary.8
2020-03-11 16:15:11 -04:00
Steven
5a1012fb0f [now-cli] Prevent framework from clearing console in now dev (#3903)
When running a framework like Create React App or Gridsome, the console gets cleared. This prevented the user from seeing the message printed from `now dev` which is typically `http://localhost:3000`. Instead the user would see the framework's URL such as `http://localhost:54684`.

See #3497 for an example.

The solution is to change the child process to pipe stdout/stderr. Since most frameworks detect [`process.stdout.isTTY`](7e6d6cd05f/packages/react-scripts/scripts/start.js (L141-L143)) before clearing the console, this will solve the problem. I was also able to intercept stdout to replace the framework's port with the `now dev` port and I think this will also help prevent confusion.

I also had to set `FORCE_COLOR=1` to avoid losing ANSI colors.

- Related to https://github.com/facebook/create-react-app/issues/2495
- Fixes #3497
2020-03-11 16:11:51 -04:00
Nathan Rajlich
4b6143c293 [now-cli] Remove "Serving all files as static" message (#3901) 2020-03-11 13:03:16 +00:00
Mark Glagola
b6601b0d9a Publish Canary
- now@18.0.0-canary.7
2020-03-10 13:59:46 -05:00
Mark Glagola
2870a1dd49 Fix setup-domain to use aliasDomain for gets (#3871) 2020-03-10 13:35:50 -05:00
Steven
6249f7e293 [now-cli] Change suggestion for rootDirectory to ./ (#3899)
When we ask the question "In which directory is your code located?" we were displaying a prefix  of `cwd/` which is confusing because it seems like you are supposed to type in the current directory. It also doesn't match what is displayed in the Project Settings after it is deployed.

This changes the prefix to `./` so that `rootDirectory` is set to the current directory and the user can type in a subdirectory if they wish such as `./packages/web` for example.

### Before

```
? Set up and deploy “~/Code/app”? [Y/n] y
? Which scope do you want to deploy to? Testing
? Link to existing project? [y/N] n
? What’s your project’s name? app
? In which directory is your code located? app/
```

### After

```
? Set up and deploy “~/Code/app”? [Y/n] y
? Which scope do you want to deploy to? Testing
? Link to existing project? [y/N] n
? What’s your project’s name? app
? In which directory is your code located? ./
```
2020-03-10 10:50:50 -04:00
274 changed files with 18675 additions and 2765 deletions

View File

@@ -45,6 +45,9 @@ jobs:
- 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
@@ -68,6 +71,9 @@ jobs:
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"

View File

@@ -27,3 +27,5 @@ jobs:
run: yarn publish-from-github
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GA_TRACKING_ID: ${{ secrets.GA_TRACKING_ID }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="256px" height="256px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve">
<rect x="65" y="68" fill="#7C7C7C" width="127" height="124"/>
<rect x="83.392" y="22.163" transform="matrix(-0.7071 0.7071 -0.7071 -0.7071 266.9294 23.9747)" fill="#86AD8A" width="90.215" height="90.215"/>
<path fill="#FFFFFF" d="M117.601,167.637c3.788-11.191,5.795-45.647,4.728-62.795c-0.128-2.057-0.333-4.445-0.692-6.351
c-0.112-0.607-0.244-1.168-0.391-1.651c0-6.672,0-62.892,0-67.02c0-5.717,5.242-8.419,10.166,4.128
c4.925,12.547,3.495,57.175,3.495,60.348c0,0.673-0.611,1.469-1.57,2.25c-0.383,0.247-0.73,0.499-1.051,0.754
c-2.949,2.366-3.379,5.095-3.533,7.542c-1.067,17.148-1.554,51.604,2.234,62.795c4.438,13.112-4.004,14.759-6.691,14.759
C121.605,182.395,113.163,180.748,117.601,167.637z"/>
<path fill="#FFFFFF" d="M103.019,47.413c-0.472,0.049-1.062,9.387-1.257,16.131c-0.238,8.194-0.607,15.482-2.908,15.482
c-1.728,0-2.8-2.853-2.517-10.094c0.407-10.427,0.395-21.519-0.219-21.519c-0.774,0-4.358,22.999-4.358,31.09
c0,5.803,2.487,11.269,5.603,14.115c0.01,0.008,0.019,0.019,0.033,0.027c0.093,0.081,0.191,0.166,0.278,0.247
c0.005,0.004,0.01,0.004,0.01,0.008c2.252,2.053,2.819,4.283,2.971,6.32c1.286,17.143-1.128,59.614-5.678,70.804
c-5.33,13.108,4.811,14.759,8.042,14.759c3.227,0,13.368-1.651,8.037-14.759c-4.55-11.191-6.96-53.661-5.678-70.804
c0.153-2.037,0.719-4.267,2.971-6.32c0-0.004,0.005-0.004,0.01-0.008c0.088-0.081,0.185-0.166,0.278-0.247
c0.014-0.008,0.023-0.019,0.033-0.027c3.116-2.845,5.764-8.314,5.604-14.115c-0.635-23.017-3.263-31.09-4.037-31.09
c-0.614,0-0.613,12.286-0.438,21.024c0.143,7.158-0.471,10.383-2.49,10.383c-1.923,0-2.504-6.972-2.748-15.368
c-0.196-6.762-0.804-16.039-1.54-16.039"/>
<path fill="#FFFFFF" d="M154.075,179.591c-2.546,0-10.532-1.562-6.332-13.967c3.585-10.59,5.483-50.779,4.474-67.007
c-0.051-0.826-0.256-1.664-0.413-2.55c-0.443-2.504-3.543-4.555-5.338-6.356c-3.486-3.498-5.725-9.38-5.725-16.013
c0-10.77,5.97-24.4,13.334-24.4c7.364,0,13.334,13.63,13.334,24.4c0,6.633-2.24,12.515-5.725,16.013
c-1.795,1.801-4.895,3.852-5.338,6.356c-0.157,0.886-0.361,1.724-0.413,2.55c-1.01,16.227,0.889,56.416,4.474,67.007
C164.607,178.03,156.621,179.591,154.075,179.591z"/>
<polyline fill="#CECCAE" points="65,68 65,192 192,192 "/>
<polygon fill="#3F894A" points="65,191.835 65,253 192,253 192,191.835 192,66.938 "/>
<g>
<g>
<line fill="none" stroke="#FFFFFF" stroke-width="0.5" stroke-miterlimit="10" x1="191.658" y1="82.55" x2="190.946" y2="83.253"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.5" stroke-miterlimit="10" stroke-dasharray="2.02,2.02" x1="189.509" y1="84.672" x2="66.629" y2="206.035"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.5" stroke-miterlimit="10" x1="65.91" y1="206.745" x2="65.199" y2="207.448"/>
</g>
</g>
<g>
<path fill="#FFFFFF" d="M85.353,211.117h6.328c0.827,0,1.665,0.067,2.513,0.201c0.848,0.135,1.608,0.414,2.28,0.838
c0.672,0.424,1.22,1.014,1.644,1.768c0.424,0.756,0.636,1.753,0.636,2.994c0,1.116-0.331,2.089-0.993,2.916
c-0.662,0.827-1.644,1.376-2.947,1.645v0.062c0.724,0.062,1.385,0.232,1.985,0.512c0.6,0.279,1.117,0.651,1.551,1.117
c0.434,0.465,0.77,1.019,1.008,1.659c0.238,0.642,0.357,1.345,0.357,2.109c0,1.262-0.243,2.291-0.729,3.087
c-0.486,0.797-1.097,1.422-1.83,1.877c-0.734,0.455-1.531,0.766-2.389,0.931c-0.858,0.166-1.66,0.248-2.404,0.248h-7.011V211.117z
M87.4,220.609h4.25c1.055,0,1.913-0.129,2.575-0.388c0.662-0.259,1.179-0.574,1.551-0.946c0.373-0.372,0.621-0.771,0.745-1.194
s0.186-0.812,0.186-1.163c0-0.765-0.129-1.401-0.388-1.908c-0.259-0.506-0.605-0.909-1.039-1.21
c-0.435-0.299-0.941-0.512-1.52-0.636c-0.579-0.124-1.189-0.186-1.831-0.186H87.4V220.609z M87.4,231.219h4.684
c1.199,0,2.166-0.145,2.9-0.435c0.734-0.289,1.298-0.651,1.691-1.085c0.393-0.435,0.656-0.895,0.791-1.381
c0.134-0.485,0.202-0.926,0.202-1.318c0-0.807-0.155-1.499-0.465-2.078s-0.713-1.05-1.21-1.412c-0.496-0.361-1.06-0.625-1.69-0.791
c-0.631-0.165-1.267-0.248-1.908-0.248H87.4V231.219z"/>
<path fill="#FFFFFF" d="M103.73,222.781c0-0.269-0.011-0.595-0.031-0.978c-0.021-0.383-0.036-0.771-0.046-1.163
c-0.011-0.393-0.026-0.76-0.047-1.102c-0.021-0.341-0.031-0.604-0.031-0.791h1.861c0.021,0.538,0.036,1.055,0.046,1.551
c0.01,0.497,0.036,0.817,0.078,0.962c0.476-0.848,1.086-1.54,1.83-2.078c0.745-0.538,1.654-0.807,2.73-0.807
c0.186,0,0.367,0.016,0.543,0.047c0.175,0.03,0.357,0.067,0.543,0.108l-0.217,1.83c-0.248-0.082-0.486-0.124-0.713-0.124
c-0.807,0-1.504,0.129-2.094,0.388c-0.589,0.259-1.075,0.615-1.458,1.07s-0.667,0.987-0.853,1.598s-0.279,1.267-0.279,1.97v7.817
h-1.861V222.781z"/>
<path fill="#FFFFFF" d="M126,229.047c0,0.27,0.01,0.595,0.031,0.978c0.021,0.383,0.036,0.771,0.046,1.163
c0.01,0.394,0.026,0.761,0.047,1.102c0.02,0.341,0.031,0.604,0.031,0.791h-1.861c-0.021-0.537-0.036-1.055-0.046-1.551
c-0.011-0.497-0.036-0.817-0.078-0.962h-0.093c-0.373,0.786-0.993,1.463-1.861,2.032c-0.869,0.568-1.882,0.853-3.04,0.853
c-1.117,0-2.032-0.176-2.746-0.527s-1.272-0.812-1.675-1.38c-0.403-0.569-0.678-1.226-0.822-1.971
c-0.145-0.744-0.217-1.509-0.217-2.295v-8.531h1.861v8.438c0,0.58,0.051,1.144,0.155,1.691s0.284,1.039,0.543,1.474
c0.258,0.434,0.621,0.78,1.086,1.039s1.07,0.388,1.815,0.388c0.682,0,1.329-0.119,1.938-0.356c0.61-0.238,1.137-0.6,1.582-1.086
s0.796-1.097,1.055-1.83c0.258-0.734,0.388-1.598,0.388-2.591v-7.166H126V229.047z"/>
<path fill="#FFFFFF" d="M130.584,222.781c0-0.269-0.01-0.595-0.029-0.978c-0.021-0.383-0.037-0.771-0.047-1.163
c-0.012-0.393-0.027-0.76-0.047-1.102c-0.021-0.341-0.031-0.604-0.031-0.791h1.861c0.02,0.538,0.035,1.055,0.047,1.551
c0.01,0.497,0.035,0.817,0.076,0.962h0.094c0.373-0.785,0.992-1.463,1.861-2.032c0.869-0.568,1.883-0.853,3.041-0.853
c1.115,0,2.025,0.176,2.729,0.527s1.258,0.812,1.66,1.381c0.404,0.568,0.684,1.225,0.838,1.97c0.154,0.744,0.232,1.51,0.232,2.295
v8.531h-1.861v-8.438c0-0.579-0.051-1.143-0.154-1.691c-0.104-0.547-0.285-1.039-0.543-1.473c-0.26-0.435-0.621-0.781-1.086-1.04
c-0.465-0.258-1.07-0.388-1.814-0.388c-0.684,0-1.33,0.119-1.939,0.357c-0.609,0.237-1.137,0.6-1.582,1.085
c-0.445,0.486-0.797,1.097-1.055,1.831c-0.26,0.734-0.389,1.598-0.389,2.59v7.166h-1.861V222.781z"/>
<path fill="#FFFFFF" d="M159.28,230.971c-0.703,0.849-1.5,1.474-2.389,1.877c-0.89,0.403-1.852,0.604-2.885,0.604
c-1.097,0-2.11-0.186-3.041-0.559c-0.931-0.372-1.727-0.894-2.389-1.566c-0.662-0.672-1.179-1.468-1.551-2.389
c-0.372-0.92-0.559-1.928-0.559-3.024c0-1.096,0.187-2.104,0.559-3.024s0.889-1.717,1.551-2.389s1.458-1.194,2.389-1.566
c0.931-0.373,1.944-0.559,3.041-0.559c1.055,0,2.031,0.217,2.931,0.651c0.9,0.435,1.701,1.045,2.404,1.83l-1.489,1.117
c-0.537-0.6-1.127-1.07-1.768-1.412c-0.642-0.341-1.334-0.512-2.078-0.512c-0.869,0-1.655,0.155-2.358,0.466
c-0.703,0.31-1.303,0.729-1.799,1.256s-0.874,1.148-1.133,1.861c-0.259,0.714-0.388,1.474-0.388,2.28s0.129,1.566,0.388,2.28
c0.259,0.713,0.637,1.334,1.133,1.861s1.096,0.946,1.799,1.256c0.703,0.311,1.489,0.466,2.358,0.466
c0.785,0,1.504-0.181,2.155-0.543c0.651-0.361,1.215-0.843,1.69-1.442L159.28,230.971z"/>
<path fill="#FFFFFF" d="M161.925,209.628h1.861v11.633h0.062c0.372-0.785,0.992-1.463,1.861-2.032
c0.868-0.568,1.882-0.853,3.04-0.853c1.116,0,2.026,0.176,2.729,0.527s1.257,0.812,1.66,1.381c0.403,0.568,0.683,1.225,0.837,1.97
c0.155,0.744,0.233,1.51,0.233,2.295v8.531h-1.861v-8.438c0-0.579-0.052-1.143-0.155-1.691c-0.104-0.547-0.285-1.039-0.543-1.473
c-0.259-0.435-0.62-0.781-1.086-1.04c-0.465-0.258-1.07-0.388-1.814-0.388c-0.683,0-1.329,0.119-1.939,0.357
c-0.609,0.237-1.137,0.6-1.582,1.085c-0.444,0.486-0.796,1.097-1.055,1.831s-0.388,1.598-0.388,2.59v7.166h-1.861V209.628z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -10,7 +10,8 @@
</head>
<body>
<div class="brunch">
<a href="http://brunch.io"><img src="http://brunch.io/images/logo.png" alt="Brunch"></a>
<p>Bon Appétit.</p>
<img src="/brunch-napkin.svg" alt="Brunch">
<h4>Bon Appétit.</h4>
<h6>A <a href="http://brunch.io" target="_blank">Brunch</a> website deployed on <a href="https://zeit.co" target="_blank">ZEIT Now</a></h6>
</div>
</body>

View File

@@ -1,6 +1,21 @@
html, body {
height: 100%;
}
body {
display: flex;
margin: 0;
align-items: center;
justify-content: center;
}
.brunch {
font-family: -apple-system, Sans-Serif;
text-align: center;
font-size: 24pt;
color: #3f894a;
}
a {
color: inherit;
}

View File

@@ -1,6 +1,6 @@
{
"private": true,
"name": "gatsby-starter-default",
"name": "gatsby",
"version": "1.0.0",
"dependencies": {
"gatsby": "^2.18.14",

View File

@@ -1,5 +1,5 @@
{
"name": "gridsomee",
"name": "gridsome",
"private": true,
"scripts": {
"build": "gridsome build",

View File

@@ -79,4 +79,4 @@ DEPENDENCIES
wdm (~> 0.1.0)
BUNDLED WITH
1.17.2
2.1.4

View File

@@ -105,4 +105,4 @@ DEPENDENCIES
wdm (~> 0.1)
BUNDLED WITH
1.17.2
2.1.4

View File

@@ -24,3 +24,7 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Environment Variables
.env
.env.build

View File

@@ -3,6 +3,10 @@
{
"source": "/",
"destination": "/api/frameworks"
},
{
"source": "/api/v1/frameworks",
"destination": "/api/frameworks"
}
],
"env": {

View File

@@ -23,6 +23,7 @@
"eslint": "6.2.2",
"eslint-config-prettier": "6.1.0",
"husky": "3.0.4",
"json5": "2.1.1",
"lint-staged": "9.2.5",
"node-fetch": "2.6.0",
"prettier": "1.18.2"

View File

@@ -17,7 +17,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`build` from `package.json` or `next build`"
"placeholder": "`npm run build` or `next build`"
},
"devCommand": {
"value": "next dev --port $PORT"
@@ -45,7 +45,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`gatsby build` or `build` from `package.json`"
"placeholder": "`npm run build` or `gatsby build`"
},
"devCommand": {
"value": "gatsby develop --port $PORT"
@@ -62,6 +62,7 @@
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/hexo.svg",
"tagline": "Hexo is a fast, simple & powerful blog framework powered by Node.js.",
"description": "A Hexo site, created with the Hexo CLI.",
"website": "https://hexo.io/",
"detectors": {
"every": [
{
@@ -72,7 +73,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`hexo generate` or `build` from `package.json`"
"placeholder": "`npm run build` or `hexo generate`"
},
"devCommand": {
"value": "hexo server --port $PORT"
@@ -89,6 +90,7 @@
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/eleventy.svg",
"tagline": "11ty is a simpler static site generator written in JavaScript, created to be an alternative to Jekyll.",
"description": "An Eleventy site, created with npm init.",
"website": "https://www.11ty.dev/",
"detectors": {
"every": [
{
@@ -99,7 +101,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`npx @11ty/eleventy` or `build` from `package.json`"
"placeholder": "`npm run build` or `npx @11ty/eleventy`"
},
"devCommand": {
"value": "npx @11ty/eleventy --serve --watch --port $PORT"
@@ -116,6 +118,7 @@
"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://docusaurus.io/",
"detectors": {
"some": [
{
@@ -130,7 +133,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`docusaurus-build` or `build` from `package.json`"
"placeholder": "`npm run build` or `docusaurus-build`"
},
"devCommand": {
"value": "docusaurus-start --port $PORT"
@@ -158,7 +161,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`preact build` or `build` from `package.json`"
"placeholder": "`npm run build` or `preact build`"
},
"devCommand": {
"value": "preact watch --port $PORT"
@@ -175,6 +178,7 @@
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ember.svg",
"tagline": "Ember.js helps webapp developers be more productive out of the box.",
"description": "An Ember app, created with the Ember CLI.",
"website": "https://emberjs.com/",
"detectors": {
"every": [
{
@@ -185,7 +189,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`ember build` or `build` from `package.json`"
"placeholder": "`npm run build` or `ember build`"
},
"devCommand": {
"value": "ember serve --port $PORT"
@@ -213,7 +217,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`vue-cli-service build` or `build` from `package.json`"
"placeholder": "`npm run build` or `vue-cli-service build`"
},
"devCommand": {
"value": "vue-cli-service serve --port $PORT"
@@ -229,6 +233,7 @@
"demo": "https://scully.now-examples.now.sh",
"tagline": "Scully is a static site generator for Angular.",
"description": "The Static Site Generator for Angular apps.",
"website": "https://github.com/scullyio/scully",
"detectors": {
"every": [
{
@@ -239,7 +244,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`ng build && scully` or `build` from `package.json`"
"placeholder": "`npm run build` or `ng build && scully`"
},
"devCommand": {
"value": "ng serve --port $PORT"
@@ -267,7 +272,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`ng build` or `build` from `package.json`"
"placeholder": "`npm run build` or `ng build`"
},
"devCommand": {
"value": "ng serve --port $PORT"
@@ -284,6 +289,7 @@
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/polymer.svg",
"tagline": "Polymer is an open-source webapps library from Google, for building using Web Components.",
"description": "A Polymer app, created with the Polymer CLI.",
"website": "https://www.polymer-project.org/",
"detectors": {
"every": [
{
@@ -294,7 +300,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`polymer build` or `build` from `package.json`"
"placeholder": "`npm run build` or `polymer build`"
},
"devCommand": {
"value": "polymer serve --port $PORT"
@@ -322,7 +328,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`rollup -c` or `build` from `package.json`"
"placeholder": "`npm run build` or `rollup -c`"
},
"devCommand": {
"value": "sirv public --single --dev --port $PORT"
@@ -350,7 +356,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`build` from `package.json`"
"placeholder": "npm run build"
},
"devCommand": {
"value": "stencil build --dev --watch --serve --port $PORT"
@@ -382,7 +388,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`react-scripts build` or `build` from `package.json`"
"placeholder": "`npm run build` or `react-scripts build`"
},
"devCommand": {
"value": "react-scripts start"
@@ -399,6 +405,7 @@
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/gridsome.svg",
"tagline": "Gridsome is a Vue.js-powered framework for building websites & apps that are fast by default.",
"description": "A Gridsome app, created with the Gridsome CLI.",
"website": "https://gridsome.org/",
"detectors": {
"every": [
{
@@ -409,7 +416,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`gridsome build` or `build` from `package.json`"
"placeholder": "`npm run build` or `gridsome build`"
},
"devCommand": {
"value": "gridsome develop -p $PORT"
@@ -437,7 +444,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`umi build` or `build` from `package.json`"
"placeholder": "`npm run build` or `umi build`"
},
"devCommand": {
"value": "umi dev --port $PORT"
@@ -465,7 +472,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`sapper export` or `build` from `package.json`"
"placeholder": "`npm run build` or `sapper export`"
},
"devCommand": {
"value": "sapper dev --port $PORT"
@@ -482,6 +489,7 @@
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/saber.svg",
"tagline": "Saber is a framework for building static sites in Vue.js that supports data from any source.",
"description": "A Saber site, created with npm init.",
"website": "https://saber.land/",
"detectors": {
"every": [
{
@@ -492,7 +500,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`saber build` or `build` from `package.json`"
"placeholder": "`npm run build` or `saber build`"
},
"devCommand": {
"value": "saber --port $PORT"
@@ -509,6 +517,7 @@
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/stencil.svg",
"tagline": "Stencil is a powerful toolchain for building Progressive Web Apps and Design Systems.",
"description": "A Stencil site, created with the Stencil CLI.",
"website": "https://stenciljs.com/",
"detectors": {
"every": [
{
@@ -519,7 +528,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`stencil build` or `build` from `package.json`"
"placeholder": "`npm run build` or `stencil build`"
},
"devCommand": {
"value": "stencil build --dev --watch --serve --port $PORT"
@@ -547,7 +556,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`nuxt build` or `build` from `package.json`"
"placeholder": "`npm run build` or `nuxt build`"
},
"devCommand": {
"value": "nuxt"
@@ -580,7 +589,7 @@
},
"settings": {
"buildCommand": {
"value": "hugo -D --gc"
"placeholder": "`npm run build` or `hugo -D --gc`"
},
"devCommand": {
"value": "hugo server -D -w -p $PORT"
@@ -597,6 +606,7 @@
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/jekyll.svg",
"tagline": "Jekyll makes it super easy to transform your plain text into static websites and blogs.",
"description": "A Jekyll site, created with the Jekyll CLI.",
"website": "https://jekyllrb.com/",
"detectors": {
"every": [
{
@@ -606,7 +616,7 @@
},
"settings": {
"buildCommand": {
"value": "jekyll build"
"placeholder": "`npm run build` or `jekyll build`"
},
"devCommand": {
"value": "bundle exec jekyll serve --watch --port $PORT"
@@ -623,6 +633,7 @@
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/brunch.svg",
"tagline": "Brunch is a fast and simple webapp build tool with seamless incremental compilation for rapid development.",
"description": "A Brunch app, created with the Brunch CLI.",
"website": "https://brunch.io/",
"detectors": {
"every": [
{
@@ -632,7 +643,7 @@
},
"settings": {
"buildCommand": {
"placeholder": "`brunch build --production` or `build` from `package.json`"
"placeholder": "`npm run build` or `brunch build --production`"
},
"devCommand": {
"value": "brunch watch --server --port $PORT"
@@ -649,6 +660,7 @@
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/middleman.svg",
"tagline": "Middleman is a static site generator that uses all the shortcuts and tools in modern web development.",
"description": "A Middleman app, created with the Middleman CLI.",
"website": "https://middlemanapp.com/",
"detectors": {
"every": [
{
@@ -658,7 +670,7 @@
},
"settings": {
"buildCommand": {
"value": "bundle exec middleman build"
"value": "`npm run build` or `bundle exec middleman build`"
},
"devCommand": {
"value": "bundle exec middleman server -p $PORT"
@@ -675,7 +687,7 @@
"description": "No framework or a unoptimized framework.",
"settings": {
"buildCommand": {
"placeholder": "`build` or `now-build` from `package.json` if it exists"
"placeholder": "`npm run now-build` or `npm run build`"
},
"devCommand": {
"placeholder": "None"

View File

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

View File

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

View File

@@ -99,6 +99,11 @@ export async function execCommand(command: string, options: SpawnOptions = {}) {
return true;
}
export async function getNodeBinPath({ cwd }: { cwd: string }) {
const { stdout } = await execAsync('npm', ['bin'], { cwd });
return stdout.trim();
}
async function chmodPlusX(fsPath: string) {
const s = await fs.stat(fsPath);
const newMode = s.mode | 64 | 8 | 1; // eslint-disable-line no-bitwise
@@ -189,6 +194,46 @@ async function scanParentDirs(destPath: string, readPackageJson = false) {
return { hasPackageLockJson, packageJson };
}
interface WalkParentDirsProps {
/**
* The highest directory, typically the workPath root of the project.
* If this directory is reached and it doesn't contain the file, null is returned.
*/
base: string;
/**
* The directory to start searching, typically the same directory of the entrypoint.
* If this directory doesn't contain the file, the parent is checked, etc.
*/
start: string;
/**
* The name of the file to search for, typically `package.json` or `Gemfile`.
*/
filename: string;
}
export async function walkParentDirs({
base,
start,
filename,
}: WalkParentDirsProps): Promise<string | null> {
assert(path.isAbsolute(base), 'Expected "base" to be absolute path');
assert(path.isAbsolute(start), 'Expected "start" to be absolute path');
let parent = '';
for (let current = start; base.length <= current.length; current = parent) {
const fullPath = path.join(current, filename);
// eslint-disable-next-line no-await-in-loop
if (await fs.pathExists(fullPath)) {
return fullPath;
}
parent = path.dirname(current);
}
return null;
}
export async function runNpmInstall(
destPath: string,
args: string[] = [],

View File

@@ -12,6 +12,7 @@ import {
spawnAsync,
execCommand,
spawnCommand,
walkParentDirs,
installDependencies,
runPackageJsonScript,
runNpmInstall,
@@ -20,6 +21,7 @@ import {
runShellScript,
getNodeVersion,
getSpawnOptions,
getNodeBinPath,
} from './fs/run-user-scripts';
import {
getLatestNodeVersion,
@@ -48,6 +50,8 @@ export {
runPackageJsonScript,
execCommand,
spawnCommand,
walkParentDirs,
getNodeBinPath,
runNpmInstall,
runBundleInstall,
runPipInstall,

View File

@@ -18,7 +18,7 @@ module.exports = {
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
name: `05-zero-config-gatsby`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,

View File

@@ -1,5 +1,5 @@
{
"name": "gatsby-starter-default",
"name": "05-zero-config-gatsby",
"private": true,
"description": "A simple starter to get up and developing quickly with Gatsby",
"version": "0.1.0",

View File

@@ -0,0 +1,109 @@
import { walkParentDirs } from '../';
import { strict } from 'assert';
import { join } from 'path';
import { promises } from 'fs';
const { deepEqual, notDeepEqual, fail } = strict;
const { readFile } = promises;
const fixture = (name: string) => join(__dirname, 'walk', name);
const filename = 'file.txt';
async function assertContent(target: string | null, contents: string) {
notDeepEqual(target, null);
const actual = await readFile(target!, 'utf8');
deepEqual(actual.trim(), contents.trim());
}
describe('Test `walkParentDirs`', () => {
it('should throw when `base` is relative', async () => {
const base = './relative';
const start = __dirname;
try {
await walkParentDirs({ base, start, filename });
fail('Expected error');
} catch (error) {
console.log(error);
deepEqual(
(error as Error).message,
'Expected "base" to be absolute path'
);
}
});
it('should throw when `start` is relative', async () => {
const base = __dirname;
const start = './relative';
try {
await walkParentDirs({ base, start, filename });
fail('Expected error');
} catch (error) {
console.log(error);
deepEqual(
(error as Error).message,
'Expected "start" to be absolute path'
);
}
});
it('should find nested one', async () => {
const base = fixture('every-directory');
const start = base;
const target = await walkParentDirs({ base, start, filename });
await assertContent(target, 'First');
});
it('should find nested two', async () => {
const base = fixture('every-directory');
const start = join(base, 'two');
const target = await walkParentDirs({ base, start, filename });
await assertContent(target, 'Second');
});
it('should find nested three', async () => {
const base = fixture('every-directory');
const start = join(base, 'two', 'three');
const target = await walkParentDirs({ base, start, filename });
await assertContent(target, 'Third');
});
it('should not find nested one', async () => {
const base = fixture('not-found');
const start = base;
const target = await walkParentDirs({ base, start, filename });
deepEqual(target, null);
});
it('should not find nested two', async () => {
const base = fixture('not-found');
const start = join(base, 'two');
const target = await walkParentDirs({ base, start, filename });
deepEqual(target, null);
});
it('should not find nested three', async () => {
const base = fixture('not-found');
const start = join(base, 'two', 'three');
const target = await walkParentDirs({ base, start, filename });
deepEqual(target, null);
});
it('should find only one', async () => {
const base = fixture('only-one');
const start = join(base, 'two', 'three');
const target = await walkParentDirs({ base, start, filename });
await assertContent(target, 'First');
});
it('should find only two', async () => {
const base = fixture('only-two');
const start = join(base, 'two', 'three');
const target = await walkParentDirs({ base, start, filename });
await assertContent(target, 'Second');
});
it('should find only three', async () => {
const base = fixture('only-three');
const start = join(base, 'two', 'three');
const target = await walkParentDirs({ base, start, filename });
await assertContent(target, 'Third');
});
});

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
First

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
Second

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
Third

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
First

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
Third

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
Another

View File

@@ -0,0 +1 @@
Second

View File

@@ -0,0 +1 @@
Another

View File

@@ -1,6 +1,6 @@
{
"name": "now",
"version": "18.0.0-canary.6",
"version": "17.0.5-canary.13",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Now",
@@ -14,6 +14,7 @@
"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-v1": "ava test/integration-v1.js --serial --fail-fast",
"test-integration-now-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
"prepublishOnly": "yarn build",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
@@ -91,10 +92,12 @@
"@types/universal-analytics": "0.4.2",
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@zeit/dockerignore": "0.0.5",
"@zeit/fun": "0.11.2",
"@zeit/ncc": "0.18.5",
"@zeit/source-map-support": "0.6.2",
"ajv": "6.10.2",
"alpha-sort": "2.0.1",
"ansi-escapes": "3.0.0",
"ansi-regex": "3.0.0",
"arg": "2.0.0",
@@ -127,6 +130,8 @@
"get-port": "5.1.1",
"glob": "7.1.2",
"http-proxy": "1.17.0",
"ignore": "4.0.6",
"ini": "1.3.4",
"inquirer": "7.0.4",
"is-port-reachable": "3.0.0",
"is-url": "1.2.2",

View File

@@ -1,3 +1,4 @@
//
import chalk from 'chalk';
import { handleError } from '../../util/error';
@@ -30,11 +31,15 @@ const help = () => {
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
'DIR'
)} Path to the global ${'`.now`'} directory
-r ${chalk.bold.underline('RULES_FILE')}, --rules=${chalk.bold.underline(
'RULES_FILE'
)} Rules file
-d, --debug Debug mode [off]
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN'
)} Login token
-S, --scope Set a custom scope
-n, --no-verify Don't wait until instance count meets the previous alias constraints
${chalk.dim('Examples:')}
@@ -60,6 +65,22 @@ const help = () => {
${chalk.dim('')} ${chalk.dim(
'Protocols'
)} in the URLs are unneeded and ignored.
${chalk.gray('')} Add and modify path based aliases for ${chalk.underline(
'zeit.ninja'
)}
${chalk.cyan(
`$ now alias ${chalk.underline('zeit.ninja')} -r ${chalk.underline(
'rules.json'
)}`
)}
Export effective routing rules
${chalk.cyan(
`$ now alias ls aliasId --json > ${chalk.underline('rules.json')}`
)}
`);
};
@@ -76,7 +97,11 @@ export default async function main(ctx) {
try {
argv = getArgs(ctx.argv.slice(2), {
'--json': Boolean,
'--no-verify': Boolean,
'--rules': String,
'--yes': Boolean,
'-n': '--no-verify',
'-r': '--rules',
'-y': '--yes',
});
} catch (err) {

View File

@@ -2,7 +2,7 @@ import chalk from 'chalk';
import ms from 'ms';
import plural from 'pluralize';
import table from 'text-table';
import Now from '../../util/now';
import Now from '../../util';
import Client from '../../util/client.ts';
import getAliases from '../../util/alias/get-aliases';
import getScope from '../../util/get-scope.ts';
@@ -36,6 +36,7 @@ export default async function ls(ctx, opts, args, output) {
throw err;
}
// $FlowFixMe
const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam });
const lsStamp = stamp();
let cancelWait;

View File

@@ -1,7 +1,7 @@
import chalk from 'chalk';
import ms from 'ms';
import table from 'text-table';
import Now from '../../util/now';
import Now from '../../util';
import cmd from '../../util/output/cmd.ts';
import Client from '../../util/client.ts';
import getScope from '../../util/get-scope.ts';
@@ -39,6 +39,7 @@ export default async function rm(ctx, opts, args, output) {
throw err;
}
// $FlowFixMe
const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam });
const [aliasOrId] = args;

View File

@@ -1,24 +1,34 @@
import ms from 'ms';
import chalk from 'chalk';
import { SetDifference } from 'utility-types';
import { AliasRecord } from '../../util/alias/create-alias';
import { NowContext, Domain } from '../../types';
import { Output } from '../../util/output';
import * as ERRORS from '../../util/errors';
import * as ERRORS from '../../util/errors-ts';
import assignAlias from '../../util/alias/assign-alias';
import Client from '../../util/client';
import cmd from '../../util/output/cmd';
import formatDnsTable from '../../util/format-dns-table';
import formatNSTable from '../../util/format-ns-table';
import getDeploymentByIdOrHost from '../../util/deploy/get-deployment-by-id-or-host';
import getDeploymentForAlias from '../../util/alias/get-deployment-for-alias';
import getRulesFromFile from '../../util/alias/get-rules-from-file';
import getScope from '../../util/get-scope';
import { getTargetsForAlias } from '../../util/alias/get-targets-for-alias';
import humanizePath from '../../util/humanize-path';
import setupDomain from '../../util/domains/setup-domain';
import stamp from '../../util/output/stamp';
import { isValidName } from '../../util/is-valid-name';
import upsertPathAlias from '../../util/alias/upsert-path-alias';
import handleCertError from '../../util/certs/handle-cert-error';
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
import link from '../../util/output/link';
import { User } from '../../types';
type Options = {
'--debug': boolean;
'--local-config': string;
'--no-verify': boolean;
'--rules': string;
};
export default async function set(
@@ -30,13 +40,18 @@ export default async function set(
const {
authConfig: { token },
config,
localConfig,
} = ctx;
const { currentTeam } = config;
const { apiUrl } = ctx;
const setStamp = stamp();
const { '--debug': debugEnabled } = opts;
const {
'--debug': debugEnabled,
'--no-verify': noVerify,
'--rules': rulesPath,
} = opts;
const client = new Client({
apiUrl,
@@ -44,10 +59,12 @@ export default async function set(
currentTeam,
debug: debugEnabled,
});
let contextName = null;
let user: User;
let contextName: string | null = null;
try {
({ contextName } = await getScope(client));
({ contextName, user } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
@@ -77,7 +94,35 @@ export default async function set(
return 1;
}
if (args.length === 0) {
// Read the path alias rules in case there is is given
const rules = await getRulesFromFile(rulesPath);
if (rules instanceof ERRORS.FileNotFound) {
output.error(`Can't find the provided rules file at location:`);
output.print(` ${chalk.gray('-')} ${rules.meta.file}\n`);
return 1;
}
if (rules instanceof ERRORS.CantParseJSONFile) {
output.error(`Error parsing provided rules.json file at location:`);
output.print(` ${chalk.gray('-')} ${rules.meta.file}\n`);
return 1;
}
if (rules instanceof ERRORS.RulesFileValidationError) {
output.error(`Path Alias validation error: ${rules.meta.message}`);
output.print(` ${chalk.gray('-')} ${rules.meta.location}\n`);
return 1;
}
// If the user provided rules and also a deployment target, we should fail
if (args.length === 2 && rules) {
output.error(
`You can't supply a deployment target and target rules simultaneously.`
);
return 1;
}
if (args.length === 0 && !rules) {
output.error(
`To ship to production, optionally configure your domains (${link(
'https://zeit.co/docs/v2/custom-domains/'
@@ -86,11 +131,62 @@ export default async function set(
return 1;
}
const [deploymentIdOrHost, aliasTarget] = args;
// Find the targets to perform the alias
const targets = getTargetsForAlias(args, localConfig);
if (targets instanceof ERRORS.NoAliasInConfig) {
output.error(`Couldn't find an alias in config`);
return 1;
}
if (targets instanceof ERRORS.InvalidAliasInConfig) {
output.error(
`Wrong value for alias found in config. It must be a string or array of string.`
);
return 1;
}
if (rules) {
// If we have rules for path alias we assign them to the domain
for (const target of targets) {
output.log(
`Assigning path alias rules from ${humanizePath(
rulesPath
)} to ${target}`
);
const pathAlias = await upsertPathAlias(
output,
client,
rules,
target,
contextName
);
const remaining = handleCreateAliasError(output, pathAlias);
if (handleSetupDomainError(output, remaining) !== 1) {
console.log(
`${chalk.cyan('> Success!')} ${
rules.length
} rules configured for ${chalk.underline(target)} ${setStamp()}`
);
}
}
return 0;
}
// If there are no rules for path alias we should find out a deployment and perform the alias
const deployment = handleCertError(
output,
await getDeploymentByIdOrHost(client, contextName, deploymentIdOrHost)
await getDeploymentForAlias(
client,
output,
args,
opts['--local-config'],
user,
contextName,
localConfig
)
);
if (deployment === 1) {
@@ -127,34 +223,37 @@ export default async function set(
return 1;
}
output.log(`Assigning alias ${aliasTarget} to deployment ${deployment.url}`);
// Assign the alias for each of the targets in the array
for (const target of targets) {
output.log(`Assigning alias ${target} to deployment ${deployment.url}`);
const isWildcard = isWildcardAlias(aliasTarget);
const record = await assignAlias(
output,
client,
deployment,
aliasTarget,
contextName
);
const isWildcard = isWildcardAlias(target);
const record = await assignAlias(
output,
client,
deployment,
target,
contextName,
noVerify
);
const handleResult = handleSetupDomainError(
output,
handleCreateAliasError(output, record),
isWildcard
);
if (handleResult === 1) {
return 1;
}
const handleResult = handleSetupDomainError(
output,
handleCreateAliasError(output, record)
);
const prefix = isWildcard ? '' : 'https://';
if (handleResult === 1) {
return 1;
console.log(
`${chalk.cyan('> Success!')} ${chalk.bold(
`${prefix}${handleResult.alias}`
)} now points to https://${deployment.url} ${setStamp()}`
);
}
const prefix = isWildcard ? '' : 'https://';
console.log(
`${chalk.cyan('> Success!')} ${chalk.bold(
`${prefix}${handleResult.alias}`
)} now points to https://${deployment.url} ${setStamp()}`
);
return 0;
}
@@ -164,7 +263,8 @@ type SetupDomainError = Exclude<SetupDomainResolve, Domain>;
function handleSetupDomainError<T>(
output: Output,
error: SetupDomainError | T
error: SetupDomainError | T,
isWildcard: boolean = false
): T | 1 {
if (
error instanceof ERRORS.DomainVerificationFailed ||
@@ -176,7 +276,9 @@ function handleSetupDomainError<T>(
`We could not alias since the domain ${domain} could not be verified due to the following reasons:\n`
);
output.print(
`Nameservers verification failed since we see a different set than the intended set:`
` ${chalk.gray(
'a)'
)} Nameservers verification failed since we see a different set than the intended set:`
);
output.print(
`\n${formatNSTable(
@@ -185,6 +287,34 @@ function handleSetupDomainError<T>(
{ extraSpace: ' ' }
)}\n\n`
);
if (error instanceof ERRORS.DomainVerificationFailed && !isWildcard) {
const { txtVerification } = error.meta;
output.print(
` ${chalk.gray(
'b)'
)} DNS TXT verification failed since found no matching records.`
);
output.print(
`\n${formatDnsTable(
[['_now', 'TXT', txtVerification.verificationRecord]],
{ extraSpace: ' ' }
)}\n\n`
);
output.print(
` Once your domain uses either the nameservers or the TXT DNS record from above, run again ${cmd(
'now domains verify <domain>'
)}.\n`
);
output.print(
` We will also periodically run a verification check for you and you will receive an email once your domain is verified.\n`
);
} else {
output.print(
` Once your domain uses the nameservers from above, run again ${cmd(
'now domains verify <domain>'
)}.\n`
);
}
output.print(' Read more: https://err.sh/now/domain-verification\n');
return 1;
}
@@ -325,6 +455,66 @@ function handleCreateAliasError<T>(
return 1;
}
if (error instanceof ERRORS.RuleValidationFailed) {
output.error(`Rule validation error: ${error.meta.message}.`);
output.print(` Make sure your rules file is written correctly.\n`);
return 1;
}
if (error instanceof ERRORS.VerifyScaleTimeout) {
output.error(`Instance verification timed out (${ms(error.meta.timeout)})`);
output.log('Read more: https://err.sh/now/verification-timeout');
return 1;
}
if (error instanceof ERRORS.NotSupportedMinScaleSlots) {
output.error(
`Scale rules from previous aliased deployment ${chalk.dim(
error.meta.url
)} could not be copied since Cloud v2 deployments cannot have a non-zero min`
);
output.log(
`Update the scale settings on ${chalk.dim(
error.meta.url
)} with \`now scale\` and try again`
);
output.log('Read more: https://err.sh/now/v2-no-min');
return 1;
}
if (error instanceof ERRORS.ForbiddenScaleMaxInstances) {
output.error(
`Scale rules from previous aliased deployment ${chalk.dim(
error.meta.url
)} could not be copied since the given number of max instances (${
error.meta.max
}) is not allowed.`
);
output.log(
`Update the scale settings on ${chalk.dim(
error.meta.url
)} with \`now scale\` and try again`
);
return 1;
}
if (error instanceof ERRORS.ForbiddenScaleMinInstances) {
output.error(
`You can't scale to more than ${error.meta.max} min instances with your current plan.`
);
return 1;
}
if (error instanceof ERRORS.InvalidScaleMinMaxRelation) {
output.error(
`Scale rules from previous aliased deployment ${chalk.dim(
error.meta.url
)} could not be copied becuase the relation between min and max instances is wrong.`
);
output.log(
`Update the scale settings on ${chalk.dim(
error.meta.url
)} with \`now scale\` and try again`
);
return 1;
}
if (error instanceof ERRORS.CertMissing) {
output.error(
`There is no certificate for the domain ${error.meta.domain} and it could not be created.`

View File

@@ -300,13 +300,10 @@ async function run({ token, config: { currentTeam } }) {
const deletedCard = cards.sources.find(card => card.id === cardId);
const remainingCards = cards.sources.filter(card => card.id !== cardId);
let text = `The provided card does not exist`;
if (deletedCard) {
text = `${deletedCard.brand ||
deletedCard.card.brand} ending in ${deletedCard.last4 ||
deletedCard.card.last4} was deleted`;
}
let text = `${deletedCard.brand ||
deletedCard.card.brand} ending in ${deletedCard.last4 ||
deletedCard.card.last4} was deleted`;
// ${chalk.gray(`[${elapsed}]`)}
if (cardId === cards.defaultSource) {
if (remainingCards.length === 0) {

View File

@@ -1,5 +1,7 @@
import chalk from 'chalk';
import Now from '../../util/now';
// @ts-ignore
import Now from '../../util';
import Client from '../../util/client';
import getScope from '../../util/get-scope';
import stamp from '../../util/output/stamp';
@@ -59,6 +61,7 @@ async function add(
throw err;
}
// $FlowFixMe
const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam });
if (overwite) {

View File

@@ -3,7 +3,7 @@ import chalk from 'chalk';
import { NowContext } from '../../types';
import { Output } from '../../util/output';
import * as ERRORS from '../../util/errors';
import * as ERRORS from '../../util/errors-ts';
import Client from '../../util/client';
import createCertForCns from '../../util/certs/create-cert-for-cns';
import createCertFromFile from '../../util/certs/create-cert-from-file';

View File

@@ -3,13 +3,14 @@ import ms from 'ms';
import plural from 'pluralize';
import psl from 'psl';
import table from 'text-table';
import Now from '../../util/now';
// @ts-ignore
import Now from '../../util';
import cmd from '../../util/output/cmd';
import Client from '../../util/client';
import getScope from '../../util/get-scope';
import stamp from '../../util/output/stamp';
import getCerts from '../../util/certs/get-certs';
import { CertNotFound } from '../../util/errors';
import { CertNotFound } from '../../util/errors-ts';
import strlen from '../../util/strlen';
import { Output } from '../../util/output';
import { NowContext, Cert } from '../../types';

View File

@@ -3,7 +3,7 @@ import ms from 'ms';
import plural from 'pluralize';
import table from 'text-table';
import { NowContext, Cert } from '../../types';
import * as ERRORS from '../../util/errors';
import * as ERRORS from '../../util/errors-ts';
import { Output } from '../../util/output';
import deleteCertById from '../../util/certs/delete-cert-by-id';
import getCertById from '../../util/certs/get-cert-by-id';

View File

@@ -1,7 +1,9 @@
import chalk from 'chalk';
import logo from '../../util/output/logo';
import code from '../../util/output/code';
import note from '../../util/output/note';
export const help = () => `
export const latestHelp = () => `
${chalk.bold(`${logo} now`)} [options] <command | path>
${chalk.dim('Commands:')}
@@ -35,6 +37,7 @@ export const help = () => `
-h, --help Output usage information
-v, --version Output the version number
-V, --platform-version Set the platform version to deploy to
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'
)} Path to the local ${'`now.json`'} file
@@ -64,6 +67,12 @@ export const help = () => `
--prod Create a production deployment
-c, --confirm Confirm default options and skip questions
${note(
`To view the usage information for Now 1.0, run ${code(
'now help deploy-v1'
)}`
)}
${chalk.dim('Examples:')}
${chalk.gray('')} Deploy the current directory
@@ -86,13 +95,14 @@ export const help = () => `
`;
export const args = {
export const latestArgs = {
'--force': Boolean,
'--public': Boolean,
'--no-clipboard': Boolean,
'--env': [String],
'--build-env': [String],
'--meta': [String],
'--no-scale': Boolean,
// This is not an array in favor of matching
// the config property name.
'--regions': String,
@@ -111,3 +121,180 @@ export const args = {
'-n': '--name',
'--target': String,
};
export const legacyArgsMri = {
string: [
'name',
'build-env',
'alias',
'meta',
'session-affinity',
'regions',
'dotenv',
'target',
],
boolean: [
'help',
'version',
'debug',
'force',
'links',
'C',
'clipboard',
'forward-npm',
'docker',
'npm',
'static',
'public',
'no-scale',
'no-verify',
'dotenv',
'prod',
],
default: {
C: false,
clipboard: true,
},
alias: {
env: 'e',
meta: 'm',
'build-env': 'b',
dotenv: 'E',
help: 'h',
debug: 'd',
version: 'v',
force: 'f',
links: 'l',
public: 'p',
'forward-npm': 'N',
'session-affinity': 'S',
name: 'n',
project: 'P',
alias: 'a',
},
};
// The following arg parsing is simply to make it compatible
// with the index. Let's not migrate it to the new args parsing, as
// we are gonna delete this file soon anyways.
const argList = {};
for (const item of legacyArgsMri.string) {
argList[`--${item}`] = String;
}
for (const item of legacyArgsMri.boolean) {
argList[`--${item}`] = Boolean;
}
for (const item of Object.keys(legacyArgsMri.alias)) {
argList[`-${legacyArgsMri.alias[item]}`] = `--${item}`;
}
export const legacyArgs = argList;
export const legacyHelp = () => `
${chalk.bold(`${logo} now`)} [options] <command | path>
${chalk.dim('Commands:')}
${chalk.dim('Cloud')}
deploy [path] Performs a deployment ${chalk.bold(
'(default)'
)}
ls | list [app] Lists deployments
rm | remove [id] Removes a deployment
ln | alias [id] [url] Configures aliases for deployments
inspect [id] Displays information related to a deployment
domains [name] Manages your domain names
certs [cmd] Manages your SSL certificates
secrets [name] Manages your secret environment variables
dns [name] Manages your DNS records
logs [url] Displays the logs for a deployment
scale [args] Scales the instance count of a deployment
init [example] Initialize an example project
help [cmd] Displays complete help for [cmd]
${chalk.dim('Administrative')}
billing | cc [cmd] Manages your credit cards and billing methods
upgrade | downgrade [plan] Upgrades or downgrades your plan
teams Manages your teams
switch [scope] Switches between teams and your account
login [email] Logs into your account or creates a new one
logout Logs out of your account
whoami Shows the username of the currently logged in user
${chalk.dim('Options:')}
-h, --help Output usage information
-v, --version Output the version number
-V, --platform-version Set the platform version to deploy to
-n, --name Set the project name of the deployment
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'
)} Path to the local ${'`now.json`'} file
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
'DIR'
)} Path to the global ${'`.now`'} directory
-d, --debug Debug mode [off]
-f, --force Force a new deployment even if nothing has changed
-t ${chalk.underline('TOKEN')}, --token=${chalk.underline(
'TOKEN'
)} Login token
-l, --links Copy symlinks without resolving their target
-p, --public Deployment is public (${chalk.dim(
'`/_src`'
)} is exposed) [on for oss, off for premium]
-e, --env Include an env var during run time (e.g.: ${chalk.dim(
'`-e KEY=value`'
)}). Can appear many times.
-b, --build-env Similar to ${chalk.dim(
'`--env`'
)} but for build time only.
-m, --meta Add metadata for the deployment (e.g.: ${chalk.dim(
'`-m KEY=value`'
)}). Can appear many times.
-E ${chalk.underline('FILE')}, --dotenv=${chalk.underline(
'FILE'
)} Include env vars from .env file. Defaults to '.env'
-C, --no-clipboard Do not attempt to copy URL to clipboard
-N, --forward-npm Forward login information to install private npm modules
--session-affinity Session affinity, \`ip\` or \`random\` (default) to control session affinity
-S, --scope Set a custom scope
--regions Set default regions or DCs to enable the deployment on
--no-scale Skip scaling rules deploying with the default presets
--no-verify Skip step of waiting until instance count meets given constraints
${chalk.dim(`Enforceable Types (by default, it's detected automatically):`)}
--npm Node.js application
--docker Docker container
--static Static file hosting
${chalk.dim('Examples:')}
${chalk.gray('')} Deploy the current directory
${chalk.cyan('$ now')}
${chalk.gray('')} Deploy a custom path
${chalk.cyan('$ now /usr/src/project')}
${chalk.gray('')} Deploy a GitHub repository
${chalk.cyan('$ now user/repo#ref')}
${chalk.gray('')} Deploy with environment variables
${chalk.cyan('$ now -e NODE_ENV=production -e SECRET=@mysql-secret')}
${chalk.gray('')} Show the usage information for the sub command ${chalk.dim(
'`list`'
)}
${chalk.cyan('$ now help list')}
`;

View File

@@ -1,13 +1,21 @@
import fs from 'fs-extra';
import { resolve, basename } from 'path';
import { resolve, basename, parse, join } from 'path';
import Client from '../../util/client.ts';
import getScope from '../../util/get-scope.ts';
import createOutput from '../../util/output';
import code from '../../util/output/code';
import highlight from '../../util/output/highlight';
import param from '../../util/output/param.ts';
import { readLocalConfig } from '../../util/config/files';
import getArgs from '../../util/get-args';
import { help, args } from './args';
import * as parts from './args';
import { handleError } from '../../util/error';
import deploy from './deploy';
import readPackage from '../../util/read-package';
import preferV2Deployment, {
hasDockerfile,
hasServerfile,
} from '../../util/prefer-v2-deployment';
import getProjectName from '../../util/get-project-name';
export default async ctx => {
const {
@@ -15,11 +23,14 @@ export default async ctx => {
config: { currentTeam },
apiUrl,
} = ctx;
const combinedArgs = Object.assign({}, parts.legacyArgs, parts.latestArgs);
let platformVersion = null;
let contextName = currentTeam || 'current user';
let argv = null;
try {
argv = getArgs(ctx.argv.slice(2), args);
argv = getArgs(ctx.argv.slice(2), combinedArgs);
} catch (error) {
handleError(error);
return 1;
@@ -46,8 +57,12 @@ export default async ctx => {
const debugEnabled = argv['--debug'];
const output = createOutput({ debug: debugEnabled });
const stats = {};
const versionFlag = argv['--platform-version'];
if (argv['--help']) {
const lastArg = argv._[argv._.length - 1];
const help = lastArg === 'deploy-v1' ? parts.legacyHelp : parts.latestHelp;
output.print(help());
return 2;
}
@@ -56,15 +71,28 @@ export default async ctx => {
try {
stats[path] = await fs.lstat(path);
} catch (err) {
output.error(
`The specified file or directory "${basename(path)}" does not exist.`
);
return 1;
const { ext } = parse(path);
if (versionFlag === 1 && !ext) {
// This will ensure `-V 1 zeit/serve` (GitHub deployments) work. Since
// GitHub repositories are never just one file, we need to set
// the `isFile` property accordingly.
stats[path] = {
isFile: () => false,
};
} else {
output.error(
`The specified file or directory "${basename(path)}" does not exist.`
);
return 1;
}
}
}
let client = null;
const isFile = Object.keys(stats).length === 1 && stats[paths[0]].isFile();
if (authConfig && authConfig.token) {
client = new Client({
apiUrl,
@@ -73,7 +101,7 @@ export default async ctx => {
debug: debugEnabled,
});
try {
({ contextName } = await getScope(client));
({ contextName, platformVersion } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
@@ -84,5 +112,89 @@ export default async ctx => {
}
}
return deploy(ctx, contextName, output, stats, localConfig, args);
const file = highlight('now.json');
const prop = code('version');
if (localConfig) {
const { version } = localConfig;
if (version) {
if (typeof version === 'number') {
if (version !== 1 && version !== 2) {
const first = code(1);
const second = code(2);
output.error(
`The value of the ${prop} property within ${file} can only be ${first} or ${second}.`
);
return 1;
}
platformVersion = version;
} else {
output.error(
`The ${prop} property inside your ${file} file must be a number.`
);
return 1;
}
}
}
if (versionFlag) {
if (versionFlag !== 1 && versionFlag !== 2) {
output.error(
`The ${param('--platform-version')} option must be either ${code(
'1'
)} or ${code('2')}.`
);
return 1;
}
platformVersion = versionFlag;
}
if (
platformVersion === 1 &&
versionFlag !== 1 &&
!argv['--docker'] &&
!argv['--npm']
) {
// Only check when it was not set via CLI flag
const reason = await preferV2Deployment({
client,
localConfig,
projectName: getProjectName({
argv,
nowConfig: localConfig || {},
isFile,
paths,
}),
hasServerfile: await hasServerfile(paths[0]),
hasDockerfile: await hasDockerfile(paths[0]),
pkg: await readPackage(join(paths[0], 'package.json')),
});
if (reason) {
output.note(reason);
platformVersion = 2;
}
}
if (platformVersion === null || platformVersion > 1) {
return require('./latest').default(
ctx,
contextName,
output,
stats,
localConfig,
parts.latestArgs
);
}
return require('./legacy').default(
ctx,
contextName,
output,
parts.legacyArgsMri
);
};

View File

@@ -8,7 +8,7 @@ import Client from '../../util/client';
import { handleError } from '../../util/error';
import getArgs from '../../util/get-args';
import toHumanPath from '../../util/humanize-path';
import Now from '../../util/now';
import Now from '../../util';
import stamp from '../../util/output/stamp.ts';
import createDeploy from '../../util/deploy/create-deploy';
import getDeploymentByIdOrHost from '../../util/deploy/get-deployment-by-id-or-host';
@@ -35,7 +35,7 @@ import {
ConflictingPathSegment,
BuildError,
NotDomainOwner,
} from '../../util/errors';
} from '../../util/errors-ts';
import { SchemaValidationFailed } from '../../util/errors';
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
@@ -45,7 +45,7 @@ import {
getLinkedProject,
linkFolderToProject,
} from '../../util/projects/link';
import { getProjectName } from '../../util/get-project-name';
import getProjectName from '../../util/get-project-name';
import selectOrg from '../../util/input/select-org';
import inputProject from '../../util/input/input-project';
import { prependEmoji, emoji } from '../../util/emoji';
@@ -225,6 +225,7 @@ export default async function main(
const paths = Object.keys(stats);
const debugEnabled = argv['--debug'];
// $FlowFixMe
const isTTY = process.stdout.isTTY;
const quiet = !isTTY;

File diff suppressed because it is too large Load Diff

View File

@@ -29,6 +29,7 @@ const help = () => {
-h, --help Output usage information
-d, --debug Debug mode [off]
-l, --listen [uri] Specify a URI endpoint on which to listen [0.0.0.0:3000]
-t, --token [token] Specify an Authorization Token
${chalk.dim('Examples:')}

View File

@@ -4,7 +4,7 @@ import {
DNSPermissionDenied,
DNSInvalidPort,
DNSInvalidType,
} from '../../util/errors';
} from '../../util/errors-ts';
import { NowContext } from '../../types';
import { Output } from '../../util/output';
import addDNSRecord from '../../util/dns/add-dns-record';

View File

@@ -3,7 +3,7 @@ import { NowContext } from '../../types';
import { Output } from '../../util/output';
import Client from '../../util/client';
import getScope from '../../util/get-scope';
import { DomainNotFound, InvalidDomain } from '../../util/errors';
import { DomainNotFound, InvalidDomain } from '../../util/errors-ts';
import stamp from '../../util/output/stamp';
import importZonefile from '../../util/dns/import-zonefile';

View File

@@ -2,7 +2,7 @@ import chalk from 'chalk';
import ms from 'ms';
import plural from 'pluralize';
import { Output } from '../../util/output';
import { DomainNotFound } from '../../util/errors';
import { DomainNotFound } from '../../util/errors-ts';
import { ThenArg, DNSRecord, NowContext } from '../../types';
import Client from '../../util/client';
import formatTable from '../../util/format-table';

View File

@@ -3,7 +3,7 @@ import psl from 'psl';
import { NowContext } from '../../types';
import { Output } from '../../util/output';
import * as ERRORS from '../../util/errors';
import * as ERRORS from '../../util/errors-ts';
import addDomain from '../../util/domains/add-domain';
import Client from '../../util/client';
import cmd from '../../util/output/cmd';

View File

@@ -3,7 +3,7 @@ import psl from 'psl';
import { NowContext } from '../../types';
import { Output } from '../../util/output';
import * as ERRORS from '../../util/errors';
import * as ERRORS from '../../util/errors-ts';
import Client from '../../util/client';
import cmd from '../../util/output/cmd';
import getDomainPrice from '../../util/domains/get-domain-price';

View File

@@ -1,5 +1,5 @@
import chalk from 'chalk';
import { DomainNotFound, DomainPermissionDenied } from '../../util/errors';
import { DomainNotFound, DomainPermissionDenied } from '../../util/errors-ts';
import { NowContext } from '../../types';
import { Output } from '../../util/output';
import Client from '../../util/client';

View File

@@ -3,7 +3,7 @@ import plural from 'pluralize';
import { NowContext, User, Team } from '../../types';
import { Output } from '../../util/output';
import * as ERRORS from '../../util/errors';
import * as ERRORS from '../../util/errors-ts';
import Client from '../../util/client';
import cmd from '../../util/output/cmd';
import getScope from '../../util/get-scope';

View File

@@ -1,7 +1,7 @@
import chalk from 'chalk';
import plural from 'pluralize';
import { DomainNotFound, DomainPermissionDenied } from '../../util/errors';
import { DomainNotFound, DomainPermissionDenied } from '../../util/errors-ts';
import { NowContext, Domain } from '../../types';
import { Output } from '../../util/output';
import Client from '../../util/client';
@@ -12,7 +12,7 @@ import getScope from '../../util/get-scope';
import removeAliasById from '../../util/alias/remove-alias-by-id';
import removeDomainByName from '../../util/domains/remove-domain-by-name';
import stamp from '../../util/output/stamp';
import * as ERRORS from '../../util/errors';
import * as ERRORS from '../../util/errors-ts';
import param from '../../util/output/param';
import promptBool from '../../util/input/prompt-bool';
import setCustomSuffix from '../../util/domains/set-custom-suffix';

View File

@@ -2,7 +2,7 @@ import chalk from 'chalk';
import { NowContext } from '../../types';
import { Output } from '../../util/output';
import * as ERRORS from '../../util/errors';
import * as ERRORS from '../../util/errors-ts';
import Client from '../../util/client';
import cmd from '../../util/output/cmd';
import getScope from '../../util/get-scope';

View File

@@ -1,7 +1,7 @@
import chalk from 'chalk';
import { NowContext } from '../../types';
import { Output } from '../../util/output';
import * as ERRORS from '../../util/errors';
import * as ERRORS from '../../util/errors-ts';
import Client from '../../util/client';
import cmd from '../../util/output/cmd';
import formatDnsTable from '../../util/format-dns-table';

View File

@@ -6,11 +6,14 @@ export default new Map([
['cert', 'certs'],
['certs', 'certs'],
['deploy', 'deploy'],
['deploy-v1', 'deploy'],
['deploy-v2', 'deploy'],
['dev', 'dev'],
['dns', 'dns'],
['domain', 'domains'],
['domains', 'domains'],
['downgrade', 'upgrade'],
['help', 'help'],
['init', 'init'],
['inspect', 'inspect'],
['list', 'list'],
@@ -24,6 +27,7 @@ export default new Map([
['projects', 'projects'],
['remove', 'remove'],
['rm', 'remove'],
['scale', 'scale'],
['secret', 'secrets'],
['secrets', 'secrets'],
['switch', 'teams'],

View File

@@ -1,17 +1,21 @@
import chalk from 'chalk';
import table from 'text-table';
import getArgs from '../util/get-args';
import buildsList from '../util/output/builds';
import routesList from '../util/output/routes';
import indent from '../util/output/indent';
import cmd from '../util/output/cmd.ts';
import createOutput from '../util/output';
import Now from '../util/now';
import Now from '../util';
import logo from '../util/output/logo';
import elapsed from '../util/output/elapsed.ts';
import { handleError } from '../util/error';
import strlen from '../util/strlen.ts';
import Client from '../util/client.ts';
import getScope from '../util/get-scope.ts';
const STATIC = 'STATIC';
const help = () => {
console.log(`
${chalk.bold(`${logo} now inspect`)} <url>
@@ -44,6 +48,7 @@ const help = () => {
};
export default async function main(ctx) {
let id;
let deployment;
let argv;
@@ -65,7 +70,7 @@ export default async function main(ctx) {
const { print, log, error } = output;
// extract the first parameter
const [, deploymentIdOrHost] = argv._;
id = argv._[1];
if (argv._.length !== 2) {
error(`${cmd('now inspect <url>')} expects exactly one argument`);
@@ -102,24 +107,20 @@ export default async function main(ctx) {
// resolve the deployment, since we might have been given an alias
const depFetchStart = Date.now();
const cancelWait = output.spinner(
`Fetching deployment "${deploymentIdOrHost}" in ${chalk.bold(contextName)}`
`Fetching deployment "${id}" in ${chalk.bold(contextName)}`
);
try {
deployment = await now.findDeployment(deploymentIdOrHost);
deployment = await now.findDeployment(id);
} catch (err) {
cancelWait();
if (err.status === 404) {
error(
`Failed to find deployment "${deploymentIdOrHost}" in ${chalk.bold(
contextName
)}`
);
error(`Failed to find deployment "${id}" in ${chalk.bold(contextName)}`);
return 1;
}
if (err.status === 403) {
error(
`No permission to access deployment "${deploymentIdOrHost}" in ${chalk.bold(
`No permission to access deployment "${id}" in ${chalk.bold(
contextName
)}`
);
@@ -129,12 +130,39 @@ export default async function main(ctx) {
throw err;
}
const { id, name, url, created, routes, readyState } = deployment;
const {
id: finalId,
name,
state,
type,
slot,
sessionAffinity,
url,
created,
limits,
version,
routes,
readyState,
} = deployment;
const { builds } =
deployment.version === 2
? await now.fetch(`/v1/now/deployments/${id}/builds`)
: { builds: [] };
const isBuilds = version === 2;
const buildsUrl = `/v1/now/deployments/${finalId}/builds`;
const [scale, events, { builds }] = await Promise.all([
caught(
now.fetch(`/v3/now/deployments/${encodeURIComponent(finalId)}/instances`)
),
type === STATIC
? null
: caught(
now.fetch(
`/v1/now/deployments/${encodeURIComponent(
finalId
)}/events?types=event`
)
),
isBuilds ? now.fetch(buildsUrl) : { builds: [] },
]);
cancelWait();
log(
@@ -145,9 +173,21 @@ export default async function main(ctx) {
print('\n');
print(chalk.bold(' General\n\n'));
print(` ${chalk.cyan('id')}\t\t${id}\n`);
print(` ${chalk.cyan('version')}\t${version}\n`);
print(` ${chalk.cyan('id')}\t\t${finalId}\n`);
print(` ${chalk.cyan('name')}\t${name}\n`);
print(` ${chalk.cyan('readyState')}\t${stateString(readyState)}\n`);
print(
` ${chalk.cyan('readyState')}\t${stateString(state || readyState)}\n`
);
if (!isBuilds) {
print(` ${chalk.cyan('type')}\t${type}\n`);
}
if (slot) {
print(` ${chalk.cyan('slot')}\t${slot}\n`);
}
if (sessionAffinity) {
print(` ${chalk.cyan('affinity')}\t${sessionAffinity}\n`);
}
print(` ${chalk.cyan('url')}\t\t${url}\n`);
if (created) {
print(
@@ -178,7 +218,95 @@ export default async function main(ctx) {
print(`\n\n`);
}
return 0;
if (limits) {
print(chalk.bold(' Limits\n\n'));
print(
` ${chalk.dim('duration')}\t\t${limits.duration} ${elapsed(
limits.duration
)}\n`
);
print(
` ${chalk.dim('maxConcurrentReqs')}\t${limits.maxConcurrentReqs}\n`
);
print(
` ${chalk.dim('timeout')}\t\t${limits.timeout} ${elapsed(
limits.timeout
)}\n`
);
print('\n');
}
if (type === STATIC || isBuilds) {
return 0;
}
print(chalk.bold(' Scale\n\n'));
let exitCode = 0;
if (scale instanceof Error) {
error(`Scale information unavailable: ${scale}`);
exitCode = 1;
} else {
const dcs = Object.keys(scale);
const t = [['dc', 'min', 'max', 'current'].map(v => chalk.gray(v))];
for (const dc of dcs) {
const { instances } = scale[dc];
const cfg = deployment.scale[dc] || {};
t.push([dc, cfg.min || 0, cfg.max || 0, instances.length]);
}
print(
`${table(t, {
align: ['l', 'c', 'c', 'c'],
hsep: ' '.repeat(8),
stringLength: strlen,
}).replace(/^(.*)/gm, ' $1')}\n`
);
print('\n');
}
print(chalk.bold(' Events\n\n'));
if (events instanceof Error) {
error(`Events unavailable: ${scale}`);
exitCode = 1;
} else if (events) {
events.forEach(data => {
if (!data.event) return; // keepalive
print(
` ${chalk.gray(new Date(data.created).toISOString())} ${
data.event
} ${getEventMetadata(data)}\n`
);
});
print('\n');
}
return exitCode;
}
// gets the metadata that should be printed next to
// each event
function getEventMetadata({ event, payload }) {
if (event === 'state') {
return chalk.bold(payload.value);
}
if (event === 'instance-start' || event === 'instance-stop') {
if (payload.dc != null) {
return chalk.green(`(${payload.dc})`);
}
}
return '';
}
// makes sure the promise never rejects, exposing the error
// as the resolved value instead
function caught(p) {
return new Promise(r => {
p.then(r).catch(r);
});
}
// renders the state string
@@ -190,9 +318,6 @@ function stateString(s) {
case 'ERROR':
return chalk.red(s);
case 'BUILDING':
return chalk.grey(s);
case 'READY':
return s;

View File

@@ -1,9 +1,10 @@
import chalk from 'chalk';
import ms from 'ms';
import plural from 'pluralize';
import table from 'text-table';
import Now from '../util/now';
import Now from '../util';
import getAliases from '../util/alias/get-aliases';
import getArgs from '../util/get-args';
import getDeploymentInstances from '../util/deploy/get-deployment-instances';
import createOutput from '../util/output';
import { handleError } from '../util/error';
import cmd from '../util/output/cmd.ts';
@@ -13,7 +14,7 @@ import wait from '../util/output/wait';
import strlen from '../util/strlen.ts';
import Client from '../util/client.ts';
import getScope from '../util/get-scope.ts';
import { toHost } from '../util/to-host';
import toHost from '../util/to-host';
import parseMeta from '../util/parse-meta';
import { isValidName } from '../util/is-valid-name';
@@ -62,6 +63,8 @@ const help = () => {
`);
};
// Options
// $FlowFixMe
export default async function main(ctx) {
let argv;
@@ -71,6 +74,7 @@ export default async function main(ctx) {
'--meta': [String],
'-a': '--all',
'-m': '--meta',
'--next': Number,
});
} catch (err) {
handleError(err);
@@ -123,6 +127,13 @@ export default async function main(ctx) {
throw err;
}
const nextTimestamp = argv['--next'];
if (typeof nextTimestamp !== undefined && Number.isNaN(nextTimestamp)) {
error('Please provide a number for flag `--next`');
return 1;
}
const stopSpinner = wait(
`Fetching deployments in ${chalk.bold(contextName)}`
);
@@ -160,16 +171,22 @@ export default async function main(ctx) {
host = asHost;
}
let deployments;
let response;
try {
debug('Fetching deployments');
deployments = await now.list(app, { version: 5, meta });
response = await now.list(app, {
version: 6,
meta,
nextTimestamp,
});
} catch (err) {
stopSpinner();
throw err;
}
let { deployments, pagination } = response;
if (app && !deployments.length) {
debug(
'No deployments: attempting to find deployment that matches supplied app name'
@@ -193,19 +210,60 @@ export default async function main(ctx) {
}
}
if (app && !deployments.length) {
debug(
'No deployments: attempting to find aliases that matches supplied app name'
);
const aliases = await getAliases(now);
const item = aliases.find(e => e.uid === app || e.alias === app);
if (item) {
debug(`Found alias that matches app name: ${item.alias}`);
if (Array.isArray(item.rules)) {
now.close();
stopSpinner();
log(`Found matching path alias: ${chalk.cyan(item.alias)}`);
log(`Please run ${cmd(`now alias ls ${item.alias}`)} instead`);
return 0;
}
const match = await now.findDeployment(item.deploymentId);
const instances = await getDeploymentInstances(
now,
item.deploymentId,
'now_cli_alias_instances'
);
match.instanceCount = Object.keys(instances).reduce(
(count, dc) => count + instances[dc].instances.length,
0
);
if (match !== null && typeof match !== 'undefined') {
deployments = Array.of(match);
}
}
}
now.close();
if (argv['--all']) {
await Promise.all(
deployments.map(async ({ uid, instanceCount }, i) => {
deployments[i].instances =
instanceCount > 0 ? await now.listInstances(uid) : [];
})
);
}
if (host) {
deployments = deployments.filter(deployment => deployment.url === host);
}
stopSpinner();
log(
`Fetched ${plural(
'deployment',
deployments.length,
true
)} under ${chalk.bold(contextName)} ${elapsed(Date.now() - start)}`
`Deployments under ${chalk.bold(contextName)} ${elapsed(
Date.now() - start
)}`
);
// we don't output the table headers if we have no deployments
@@ -218,6 +276,8 @@ export default async function main(ctx) {
log(
`To list more deployments for a project run ${cmd('now ls [project]')}`
);
} else if (!argv['--all']) {
log(`To list deployment instances run ${cmd('now ls --all [project]')}`);
}
print('\n');
@@ -235,9 +295,18 @@ export default async function main(ctx) {
getProjectName(dep),
chalk.bold((includeScheme ? 'https://' : '') + dep.url),
stateString(dep.state),
chalk.gray(ms(Date.now() - new Date(dep.created))),
chalk.gray(ms(Date.now() - new Date(dep.createdAt))),
dep.creator.username,
],
...(argv['--all']
? dep.instances.map(i => [
'',
` ${chalk.gray('-')} ${i.url} `,
'',
'',
'',
])
: []),
])
// flatten since the previous step returns a nested
// array of the deployment and (optionally) its instances
@@ -257,6 +326,10 @@ export default async function main(ctx) {
}
).replace(/^/gm, ' ')}\n\n`
);
if (pagination && deployments.length === 20) {
log(`To display the next page use the flag --next ${pagination.next}`);
}
}
function getProjectName(d) {
@@ -288,7 +361,7 @@ function stateString(s) {
// sorts by most recent deployment
function sortRecent() {
return function recencySort(a, b) {
return b.created - a.created;
return b.createdAt - a.createdAt;
};
}

View File

@@ -1,17 +1,17 @@
import mri from 'mri';
import chalk from 'chalk';
import fetch from 'node-fetch';
import ora from 'ora';
import logo from '../util/output/logo';
// @ts-ignore
import { handleError } from '../util/error';
import {
readConfigFile,
writeToConfigFile,
readAuthConfigFile,
writeToAuthConfigFile
writeToAuthConfigFile,
} from '../util/config/files';
import error from '../util/output/error';
import exit from '../util/exit';
import getArgs from '../util/get-args';
import { NowContext } from '../types';
import createOutput, { Output } from '../util/output';
const help = () => {
console.log(`
@@ -35,56 +35,31 @@ const help = () => {
`);
};
let argv;
let apiUrl;
export default async function main(ctx: NowContext): Promise<number> {
let argv;
const main = async ctx => {
argv = mri(ctx.argv.slice(2), {
boolean: ['help'],
alias: {
help: 'h'
}
});
apiUrl = ctx.apiUrl;
argv._ = argv._.slice(1);
if (argv.help || argv._[0] === 'help') {
help();
await exit(0);
}
logout();
};
export default async ctx => {
try {
await main(ctx);
argv = getArgs(ctx.argv.slice(2), {
'--help': Boolean,
'-h': '--help',
});
} catch (err) {
handleError(err);
process.exit(1);
return 1;
}
};
const revokeToken = async (token) => {
const options = {
method: 'DELETE',
headers: {
Authorization: `Bearer ${token}`
}
};
const result = await fetch(`${apiUrl}/v3/user/tokens/current`, options);
if (!result.ok) {
console.error(error('Not able to log out'));
if (argv['--help']) {
help();
return 2;
}
};
const logout = async () => {
const spinner = ora({
text: 'Logging out...'
}).start();
const debugEnabled = argv['--debug'];
const output = createOutput({ debug: debugEnabled });
return logout(ctx.apiUrl, output);
}
const logout = async (apiUrl: string, output: Output) => {
const spinner = output.spinner('Logging out...', 200);
const configContent = readConfigFile();
const authContent = readAuthConfigFile();
@@ -103,19 +78,33 @@ const logout = async () => {
delete authContent.token;
try {
await writeToConfigFile(configContent);
await writeToAuthConfigFile(authContent);
writeToConfigFile(configContent);
writeToAuthConfigFile(authContent);
output.debug('Configuration has been deleted');
} catch (err) {
spinner.fail(`Couldn't remove config while logging out`);
process.exit(1);
spinner();
output.error(`Couldn't remove config while logging out`);
return 1;
}
try {
await revokeToken(token);
} catch (err) {
spinner.fail('Could not revoke token on logout');
process.exit(1);
const res = await fetch(`${apiUrl}/v3/user/tokens/current`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${token}`,
},
});
if (res.status === 403) {
output.debug('Token is invalid so it cannot be revoked');
} else if (res.status !== 200) {
spinner();
const err = await res.json();
output.error('Failed to revoke token');
output.debug(err ? err.message : '');
return 1;
}
spinner.succeed('Logged out!');
spinner();
output.log('Logged out!');
return 0;
};

View File

@@ -1,6 +1,6 @@
import mri from 'mri';
import chalk from 'chalk';
import Now from '../util/now';
import Now from '../util';
import createOutput from '../util/output';
import logo from '../util/output/logo';
import elapsed from '../util/output/elapsed.ts';

View File

@@ -3,7 +3,7 @@ import chalk from 'chalk';
import ms from 'ms';
import plural from 'pluralize';
import table from 'text-table';
import Now from '../util/now';
import Now from '../util';
import getAliases from '../util/alias/get-aliases';
import createOutput from '../util/output';
import logo from '../util/output/logo';

View File

@@ -0,0 +1,340 @@
import ms from 'ms';
import chalk from 'chalk';
import cmd from '../util/output/cmd.ts';
import createOutput from '../util/output';
import logo from '../util/output/logo';
import stamp from '../util/output/stamp.ts';
import Now from '../util';
import getArgs from '../util/get-args';
import Client from '../util/client.ts';
import getScope from '../util/get-scope.ts';
import getDCsFromArgs from '../util/scale/get-dcs-from-args';
import getDeploymentByIdOrHost from '../util/deploy/get-deployment-by-id-or-host';
import getDeploymentByIdOrThrow from '../util/deploy/get-deployment-by-id-or-throw';
import getMaxFromArgs from '../util/scale/get-max-from-args';
import getMinFromArgs from '../util/scale/get-min-from-args';
import patchDeploymentScale from '../util/scale/patch-deployment-scale';
import waitVerifyDeploymentScale from '../util/scale/wait-verify-deployment-scale';
import { handleError } from '../util/error';
import {
VerifyScaleTimeout,
DeploymentTypeUnsupported,
} from '../util/errors-ts';
import {
DeploymentNotFound,
DeploymentPermissionDenied,
ForbiddenScaleMaxInstances,
ForbiddenScaleMinInstances,
InvalidArgsForMinMaxScale,
InvalidMaxForScale,
InvalidMinForScale,
InvalidScaleMinMaxRelation,
NotSupportedMinScaleSlots,
} from '../util/errors-ts';
import { InvalidAllForScale, InvalidRegionOrDCForScale } from '../util/errors';
import handleCertError from '../util/certs/handle-cert-error';
const help = () => {
console.log(`
${chalk.bold(`${logo} now scale`)} <url> <dc> [min] [max]
${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
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN'
)} Login token
-d, --debug Debug mode [off]
-S, --scope Set a custom scope
-n, --no-verify Skip step of waiting until instance count meets given constraints
-t, --verify-timeout How long to wait for verification to complete [5m]
${chalk.dim('Examples:')}
${chalk.gray(
''
)} Enable your deployment in all datacenters (min: 0, max: auto)
${chalk.cyan('$ now scale my-deployment-123.now.sh all')}
${chalk.gray(
'-'
)} Enable your deployment in the SFO datacenter (min: 0, max: auto)
${chalk.cyan('$ now scale my-deployment-123.now.sh sfo')}
${chalk.gray(
''
)} Scale a deployment in all datacenters to 3 instances at all times (no sleep)
${chalk.cyan('$ now scale my-deployment-123.now.sh all 3')}
${chalk.gray(
''
)} Enable your deployment in all datacenters, with auto-scaling
${chalk.cyan('$ now scale my-deployment-123.now.sh all auto')}
`);
};
export default async function main(ctx) {
let argv;
try {
argv = getArgs(ctx.argv.slice(2), {
'--verify-timeout': Number,
'--no-verify': Boolean,
'-n': '--no-verify',
});
} catch (err) {
handleError(err);
return 1;
}
if (argv['--help']) {
help();
return 2;
}
// Prepare the context
const {
authConfig: { token },
config,
} = ctx;
const { currentTeam } = config;
const { apiUrl } = ctx;
const debug = argv['--debug'];
// $FlowFixMe
const now = new Now({ apiUrl, token, debug, currentTeam });
const output = createOutput({ debug });
const client = new Client({ apiUrl, token, currentTeam, debug });
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
// Fail if the user is providing an old command
if (argv._[1] === 'ls') {
output.error(
`${cmd('now scale ls')} has been deprecated. Use ${cmd(
'now ls'
)} and ${cmd('now inspect <url>')}`
);
now.close();
return 1;
}
// Ensure the number of arguments is between the allower range
if (argv._.length < 3 || argv._.length > 5) {
output.error(
`${cmd(
'now scale <url> <dc> [min] [max]'
)} expects at least two arguments`
);
help();
now.close();
return 1;
}
const dcs = getDCsFromArgs(argv._);
if (dcs instanceof InvalidAllForScale) {
output.error(
'The region value "all" was used, but it cannot be used alongside other region or dc identifiers'
);
now.close();
return 1;
}
if (dcs instanceof InvalidRegionOrDCForScale) {
output.error(
`The value "${dcs.meta.regionOrDC}" is not a valid region or DC identifier`
);
now.close();
return 1;
}
const min = getMinFromArgs(argv._);
if (min instanceof InvalidMinForScale) {
output.error(
`Invalid <min> parameter "${min.meta.value}". A number or "auto" were expected`
);
now.close();
return 1;
}
const max = getMaxFromArgs(argv._);
if (max instanceof InvalidMinForScale) {
output.error(
`Invalid <min> parameter "${max.meta.value}". A number or "auto" were expected`
);
now.close();
return 1;
}
if (max instanceof InvalidArgsForMinMaxScale) {
output.error(
`Invalid number of arguments: expected <min> ("${max.meta.min}") and [max]`
);
now.close();
return 1;
}
if (max instanceof InvalidMaxForScale) {
output.error(
`Invalid <max> parameter "${max.meta.value}". A number or "auto" were expected`
);
now.close();
return 1;
}
// Fetch the deployment
const deploymentStamp = stamp();
const deployment = handleCertError(
output,
await getDeploymentByIdOrHost(now, contextName, argv._[1])
);
if (deployment === 1) {
return deployment;
}
if (deployment instanceof DeploymentPermissionDenied) {
output.error(
`No permission to access deployment ${chalk.dim(
deployment.meta.id
)} under ${chalk.bold(deployment.meta.context)}`
);
now.close();
return 1;
}
if (deployment instanceof DeploymentNotFound) {
output.error(
`Failed to find deployment "${argv._[1]}" in ${chalk.bold(contextName)}`
);
now.close();
return 1;
}
output.log(`Fetched deployment "${deployment.url}" ${deploymentStamp()}`);
// Make sure the deployment can be scaled
if (deployment.type === 'STATIC') {
output.error('Scaling rules cannot be set on static deployments');
now.close();
return 1;
}
if (deployment.state === 'ERROR') {
output.error('Cannot scale a deployment in the ERROR state');
now.close();
return 1;
}
if (deployment.version === 2) {
output.error('Cannot scale a Now 2.0 deployment');
now.close();
return 1;
}
const scaleArgs = dcs.reduce(
(result, dc) => ({ ...result, [dc]: { min, max } }),
{}
);
output.debug(
`Setting scale deployment presets to ${JSON.stringify(scaleArgs)}`
);
// Set the deployment scale
const scaleStamp = stamp();
const result = await patchDeploymentScale(
output,
now,
deployment.uid,
scaleArgs,
deployment.url
);
if (result instanceof ForbiddenScaleMinInstances) {
output.error(
`You can't scale to more than ${result.meta.max} min instances with your current plan.`
);
now.close();
return 1;
}
if (result instanceof ForbiddenScaleMaxInstances) {
output.error(
`You can't scale to more than ${result.meta.max} max instances with your current plan.`
);
now.close();
return 1;
}
if (result instanceof InvalidScaleMinMaxRelation) {
output.error(`Min number of instances can't be higher than max.`);
now.close();
return 1;
}
if (result instanceof NotSupportedMinScaleSlots) {
output.error(
`Cloud v2 does not yet support setting a non-zero min number of instances.`
);
output.log('Read more: https://err.sh/now/v2-no-min');
now.close();
return 1;
}
if (result instanceof DeploymentTypeUnsupported) {
output.error(`This region only accepts Serverless Docker Deployments.`);
now.close();
return 1;
}
console.log(
`${chalk.gray('>')} Scale rules for ${dcs
.map(d => chalk.bold(d))
.join(', ')} (min: ${chalk.bold(min)}, max: ${chalk.bold(
max
)}) saved ${scaleStamp()}`
);
if (argv['--no-verify']) {
now.close();
return 0;
}
// Verify that the scale presets are there
const verifyStamp = stamp();
const updatedDeployment = await getDeploymentByIdOrThrow(
now,
contextName,
deployment.uid
);
if (updatedDeployment.type === 'NPM' || updatedDeployment.type === 'DOCKER') {
const result = await waitVerifyDeploymentScale(
output,
now,
deployment.uid,
updatedDeployment.scale
);
if (result instanceof VerifyScaleTimeout) {
output.error(
`Instance verification timed out (${ms(result.meta.timeout)})`,
'verification-timeout'
);
now.close();
return 1;
}
output.success(`Scale state verified ${verifyStamp()}`);
}
now.close();
return 0;
}

View File

@@ -1,7 +1,7 @@
import chalk from 'chalk';
import { email as regexEmail } from '../../util/input/regexes';
import createOutput from '../../util/output/create-output';
import wait from '../../util/output/wait';
import fatalError from '../../util/fatal-error';
import cmd from '../../util/output/cmd.ts';
import info from '../../util/output/info';
import stamp from '../../util/output/stamp.ts';
@@ -65,7 +65,6 @@ export default async function({
apiUrl,
token,
} = {}) {
const output = createOutput();
const { currentTeam: currentTeamId } = config;
const stopSpinner = wait('Fetching teams');
@@ -100,8 +99,7 @@ export default async function({
)}.\nPlease select a team scope using ${cmd('now switch')} or use ${cmd(
'--scope'
)}`;
output.error(err);
return 1;
return fatalError(err);
}
console.log(

View File

@@ -43,7 +43,7 @@ import { handleError } from './util/error';
import highlight from './util/output/highlight';
import reportError from './util/report-error';
import getConfig from './util/get-config';
import * as ERRORS from './util/errors';
import * as ERRORS from './util/errors-ts';
import { NowError } from './util/now-error';
import { SENTRY_DSN } from './util/constants.ts';
import getUpdateCommand from './util/get-update-command';
@@ -519,7 +519,12 @@ const main = async argv_ => {
let scope = argv['--scope'] || argv['--team'] || localConfig.scope;
if (process.env.NOW_ORG_ID || !scope) {
const targetCommand = commands.get(subcommand);
if (
!['login', 'logout'].includes(targetCommand) &&
(process.env.NOW_ORG_ID || !scope)
) {
const client = new Client({ apiUrl, token });
const link = await getLinkedOrg(client, output);
@@ -532,8 +537,6 @@ const main = async argv_ => {
}
}
const targetCommand = commands.get(subcommand);
if (
typeof scope === 'string' &&
targetCommand !== 'login' &&

View File

@@ -4,10 +4,6 @@ export type ThenArg<T> = T extends Promise<infer U> ? U : T;
export type Config = NowConfig;
export interface Dictionary<T> {
[key: string]: T;
}
export interface NowContext {
argv: string[];
apiUrl: string;
@@ -106,7 +102,7 @@ export type DeploymentScale = {
};
export type NpmDeployment = {
id: string;
uid: string;
url: string;
name: string;
type: 'NPM';
@@ -119,7 +115,7 @@ export type NpmDeployment = {
};
export type StaticDeployment = {
id: string;
uid: string;
url: string;
name: string;
type: 'STATIC';
@@ -131,7 +127,7 @@ export type StaticDeployment = {
};
export type DockerDeployment = {
id: string;
uid: string;
url: string;
name: string;
type: 'DOCKER';
@@ -229,6 +225,7 @@ export interface Project {
devCommand?: string | null;
framework?: string | null;
rootDirectory?: string | null;
latestDeployments?: Partial<Deployment>[];
}
export interface Org {

View File

@@ -1,9 +1,18 @@
import { Deployment } from '../../types';
import { Output } from '../output';
import * as ERRORS from '../errors-ts';
import Client from '../client';
import createAlias from './create-alias';
import deploymentShouldCopyScale from './deployment-should-copy-scale';
import deploymentShouldDownscale from './deployment-should-downscale';
import findAliasByAliasOrId from './find-alias-by-alias-or-id';
import getDeploymentDownscalePresets from './get-deployment-downscale-presets';
import getDeploymentFromAlias from './get-deployment-from-alias';
import isDomainExternal from '../domains/is-domain-external';
import setDeploymentScale from '../scale/set-deployment-scale';
import setupDomain from '../domains/setup-domain';
import stamp from '../output/stamp';
import waitForScale from '../scale/wait-verify-deployment-scale';
const NOW_SH_REGEX = /\.now\.sh$/;
@@ -12,11 +21,71 @@ export default async function assignAlias(
client: Client,
deployment: Deployment,
alias: string,
contextName: string
contextName: string,
noVerify: boolean
) {
const prevAlias = await findAliasByAliasOrId(output, client, alias);
let externalDomain = false;
// Check if the alias is a custom domain, because then
// If there was a previous deployment, we should fetch it to scale and downscale later
let prevDeployment = await getDeploymentFromAlias(
client,
contextName,
prevAlias,
deployment
);
// If there is an alias laying around that points to a deleted
// deployment, we need to account for it here.
if (prevDeployment instanceof ERRORS.DeploymentNotFound) {
prevDeployment = null;
}
if (prevDeployment instanceof Error) {
return prevDeployment;
}
// If there was a prev deployment that wasn't static we have to check if we should scale
if (
prevDeployment !== null &&
prevDeployment.type !== 'STATIC' &&
deployment.type !== 'STATIC'
) {
if (deploymentShouldCopyScale(prevDeployment, deployment)) {
const scaleStamp = stamp();
const result = await setDeploymentScale(
output,
client,
deployment.uid,
prevDeployment.scale,
deployment.url
);
if (result instanceof Error) {
return result;
}
output.log(
`Scale rules copied from previous alias ${
prevDeployment.url
} ${scaleStamp()}`
);
if (!noVerify) {
const result = await waitForScale(
output,
client,
deployment.uid,
prevDeployment.scale
);
if (result instanceof ERRORS.VerifyScaleTimeout) {
return result;
}
}
} else {
output.debug(`Both deployments have the same scaling rules.`);
}
}
// Check if the alias is a custom domain and if case we have a positive
// we have to configure the DNS records and certificate
if (alias.indexOf('.') !== -1 && !NOW_SH_REGEX.test(alias)) {
// Now the domain shouldn't be available and it might or might not belong to the user
@@ -38,6 +107,23 @@ export default async function assignAlias(
alias,
externalDomain
);
if (record instanceof Error) {
return record;
}
// Downscale if the previous deployment is not static and doesn't have the minimal presets
if (prevDeployment !== null && prevDeployment.type !== 'STATIC') {
if (await deploymentShouldDownscale(output, client, prevDeployment)) {
await setDeploymentScale(
output,
client,
prevDeployment.uid,
getDeploymentDownscalePresets(prevDeployment),
prevDeployment.url
);
output.log(`Previous deployment ${prevDeployment.url} downscaled`);
}
}
return record;
}

View File

@@ -1,6 +1,6 @@
import { Deployment } from '../../types';
import { Output } from '../output';
import * as ERRORS from '../errors';
import * as ERRORS from '../errors-ts';
import Client from '../client';
import createCertForAlias from '../certs/create-cert-for-alias';
@@ -62,7 +62,7 @@ async function performCreateAlias(
) {
try {
return await client.fetch<AliasRecord>(
`/now/deployments/${deployment.id}/aliases`,
`/now/deployments/${deployment.uid}/aliases`,
{
method: 'POST',
body: { alias },
@@ -78,7 +78,7 @@ async function performCreateAlias(
if (error.code === 'deployment_not_found') {
return new ERRORS.DeploymentNotFound({
context: contextName,
id: deployment.id,
id: deployment.uid,
});
}
if (error.code === 'gone') {

View File

@@ -0,0 +1,11 @@
import { Deployment } from '../../types';
import Client from '../client';
import getAliases from './get-aliases';
export default async function deploymentIsAliased(
client: Client,
deployment: Deployment
) {
const aliases = await getAliases(client);
return aliases.some(alias => alias.deploymentId === deployment.uid);
}

View File

@@ -0,0 +1,23 @@
import { Deployment } from '../../types';
import getScaleForDC from '../scale/get-scale-for-dc';
export default function shouldCopyScalingAttributes(
origin: Deployment,
dest: Deployment
) {
if (origin.version === 2 || dest.version === 2) {
return false;
}
return (
(origin.type !== 'STATIC' &&
getScaleForDC('bru1', origin).min !== getScaleForDC('bru1', dest).min) ||
getScaleForDC('bru1', origin).max !== getScaleForDC('bru1', dest).max ||
getScaleForDC('gru1', origin).min !== getScaleForDC('gru1', dest).min ||
getScaleForDC('gru1', origin).max !== getScaleForDC('gru1', dest).max ||
getScaleForDC('sfo1', origin).min !== getScaleForDC('sfo1', dest).min ||
getScaleForDC('sfo1', origin).max !== getScaleForDC('sfo1', dest).max ||
getScaleForDC('iad1', origin).min !== getScaleForDC('iad1', dest).min ||
getScaleForDC('iad1', origin).max !== getScaleForDC('iad1', dest).max
);
}

View File

@@ -0,0 +1,34 @@
import deploymentIsAliased from './deployment-is-aliased';
import { Output } from '../output';
import Client from '../client';
import getScaleForDC from '../scale/get-scale-for-dc';
import { Deployment } from '../../types';
export default async function deploymentShouldDownscale(
output: Output,
client: Client,
deployment: Deployment
) {
const isAliased = await deploymentIsAliased(client, deployment);
output.debug(`Previous deployment is aliased: ${isAliased.toString()}`);
if (
(deployment.type === 'DOCKER' && !!deployment.slot) ||
deployment.version === 2 ||
deployment.type === 'STATIC'
) {
// Don't downscale a previous slot or builds deployment
return false;
}
return (
!isAliased &&
Object.keys(deployment.scale).reduce(
(result, dc) =>
result ||
getScaleForDC(dc, deployment).min !== 0 ||
getScaleForDC(dc, deployment).max !== 1,
false
)
);
}

View File

@@ -0,0 +1,72 @@
import path from 'path';
import chalk from 'chalk';
import Client from '../client';
import { Output } from '../output';
import { User, Config } from '../../types';
import getDeploymentsByAppName from '../deploy/get-deployments-by-appname';
import getDeploymentByIdOrHost from '../deploy/get-deployment-by-id-or-host';
async function getAppLastDeployment(
output: Output,
client: Client,
appName: string,
user: User,
contextName: string
) {
output.debug(`Looking for deployments matching app ${appName}`);
const deployments = await getDeploymentsByAppName(client, appName);
const deploymentItem = deployments
.sort((a, b) => b.created - a.created)
.filter(dep => dep.state === 'READY' && dep.creator.uid === user.uid)[0];
// Try to fetch deployment details
if (deploymentItem) {
return getDeploymentByIdOrHost(client, contextName, deploymentItem.uid);
}
return null;
}
export async function getDeploymentForAlias(
client: Client,
output: Output,
args: string[],
localConfigPath: string | undefined,
user: User,
contextName: string,
localConfig: Config
) {
const cancelWait = output.spinner(
`Fetching deployment to alias in ${chalk.bold(contextName)}`
);
// When there are no args at all we try to get the targets from the config
if (args.length === 2) {
const [deploymentId] = args;
const deployment = await getDeploymentByIdOrHost(
client,
contextName,
deploymentId
);
cancelWait();
return deployment;
}
const appName =
(localConfig && localConfig.name) ||
path.basename(path.resolve(process.cwd(), localConfigPath || ''));
if (!appName) {
return null;
}
const deployment = await getAppLastDeployment(
output,
client,
appName,
user,
contextName
);
cancelWait();
return deployment;
}

View File

@@ -0,0 +1,13 @@
import { DockerDeployment, NpmDeployment } from '../../types';
export default function getDeploymentDownscalePresets(
deployment: DockerDeployment | NpmDeployment
) {
return Object.keys(deployment.scale).reduce(
(result, dc) =>
Object.assign(result, {
[dc]: { min: 0, max: 1 },
}),
{}
);
}

View File

@@ -0,0 +1,50 @@
import chalk from 'chalk';
import getAppLastDeployment from '../deploy/get-app-last-deployment';
import getAppName from '../deploy/get-app-name';
import fetchDeploymentByIdOrHost from '../deploy/get-deployment-by-id-or-host';
import Client from '../client';
import { Output } from '../output';
import { User, Config } from '../../types';
export default async function getDeploymentForAlias(
client: Client,
output: Output,
args: string[],
localConfigPath: string | undefined,
user: User,
contextName: string,
localConfig: Config
) {
const cancelWait = output.spinner(
`Fetching deployment to alias in ${chalk.bold(contextName)}`
);
// When there are no args at all we try to get the targets from the config
if (args.length === 2) {
const [deploymentId] = args;
const deployment = await fetchDeploymentByIdOrHost(
client,
contextName,
deploymentId
);
cancelWait();
return deployment;
}
const appName = await getAppName(output, localConfig, localConfigPath);
if (!appName) {
return null;
}
const deployment = await getAppLastDeployment(
output,
client,
appName,
user,
contextName
);
cancelWait();
return deployment;
}

View File

@@ -0,0 +1,16 @@
import Client from '../client';
import { Deployment, Alias } from '../../types';
import fetchDeploymentByIdOrHost from '../deploy/get-deployment-by-id-or-host';
export default async function fetchDeploymentFromAlias(
client: Client,
contextName: string,
prevAlias: Alias | null,
currentDeployment: Deployment
) {
return prevAlias &&
prevAlias.deploymentId &&
prevAlias.deploymentId !== currentDeployment.uid
? fetchDeploymentByIdOrHost(client, contextName, prevAlias.deploymentId)
: null;
}

View File

@@ -0,0 +1,37 @@
import path from 'path';
import * as ERRORS from '../errors-ts';
import humanizePath from '../humanize-path';
import readJSONFile from '../read-json-file';
import validatePathAliasRules from './validate-path-alias-rules';
import { PathRule } from '../../types';
export default async function getRulesFromFile(filePath: string) {
return typeof filePath === 'string' ? readRulesFile(filePath) : null;
}
async function readRulesFile(rulesPath: string) {
const fullPath = path.resolve(process.cwd(), rulesPath);
const result = (await readJSONFile(fullPath)) as { [key: string]: any };
if (result instanceof ERRORS.CantParseJSONFile) {
return result;
}
if (result === null) {
return new ERRORS.FileNotFound(fullPath);
}
if (!result.rules) {
return new ERRORS.RulesFileValidationError(
humanizePath(fullPath),
'Your rules file must include a rules field'
);
}
const rules = result.rules as PathRule[];
const error = validatePathAliasRules(humanizePath(fullPath), rules);
if (error instanceof ERRORS.RulesFileValidationError) {
return error;
}
return rules;
}

View File

@@ -0,0 +1,28 @@
import toHost from '../to-host';
import { Config } from '../../types';
import * as ERRORS from '../errors-ts';
export function getTargetsForAlias(args: string[], { alias }: Config) {
if (args.length) {
return targetsToHosts([args[args.length - 1]]);
}
if (!alias) {
return new ERRORS.NoAliasInConfig();
}
// Check the type for the option aliases
if (typeof alias !== 'string' && !Array.isArray(alias)) {
return new ERRORS.InvalidAliasInConfig(alias);
}
return typeof alias === 'string' ? [alias] : alias;
}
function targetsToHosts(targets: string[]) {
return targets.map(targetToHost).filter(item => item) as string[];
}
function targetToHost(target: string) {
return target.indexOf('.') !== -1 ? toHost(target) : target;
}

View File

@@ -0,0 +1,102 @@
import { Output } from '../output';
import { PathRule } from '../../types';
import * as ERRORS from '../errors-ts';
import Client from '../client';
import createCertForAlias from '../certs/create-cert-for-alias';
import setupDomain from '../domains/setup-domain';
const NOW_SH_REGEX = /\.now\.sh$/;
type AliasRecord = {
uid: string;
alias: string;
created?: string;
oldDeploymentId?: string;
};
export default async function upsertPathAlias(
output: Output,
client: Client,
rules: PathRule[],
alias: string,
contextName: string
) {
let externalDomain = false;
if (!NOW_SH_REGEX.test(alias)) {
const domainInfo = await setupDomain(output, client, alias, contextName);
if (domainInfo instanceof Error) {
return domainInfo;
}
externalDomain = domainInfo.serviceType === 'external';
}
const result = await performUpsertPathAlias(
output,
client,
alias,
rules,
contextName
);
if (result instanceof ERRORS.CertMissing) {
const cert = await createCertForAlias(
output,
client,
contextName,
alias,
!externalDomain
);
if (cert instanceof Error) {
return cert;
}
return performUpsertPathAlias(output, client, alias, rules, contextName);
}
return result;
}
async function performUpsertPathAlias(
output: Output,
client: Client,
alias: string,
rules: PathRule[],
contextName: string
) {
const cancelMessage = output.spinner(
`Updating path alias rules for ${alias}`
);
try {
const record = await client.fetch<AliasRecord>(`/now/aliases`, {
body: { alias, rules },
method: 'POST',
});
cancelMessage();
return record;
} catch (error) {
cancelMessage();
if (error.code === 'cert_missing' || error.code === 'cert_expired') {
return new ERRORS.CertMissing(alias);
}
if (error.status === 409) {
return { uid: error.uid, alias: error.alias } as AliasRecord;
}
if (error.code === 'rule_validation_failed') {
return new ERRORS.RuleValidationFailed(error.serverMessage);
}
if (error.code === 'invalid_alias') {
return new ERRORS.InvalidAlias(alias);
}
if (error.status === 403) {
if (error.code === 'alias_in_use') {
console.log(error);
return new ERRORS.AliasInUse(alias);
}
if (error.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(alias, contextName);
}
}
throw error;
}
}

View File

@@ -0,0 +1,29 @@
import { RulesFileValidationError } from '../errors-ts';
import { PathRule } from '../../types';
export default function validatePathAliasRules(
location: string,
rules: PathRule[]
) {
if (!Array.isArray(rules)) {
return new RulesFileValidationError(location, 'rules must be an array');
}
if (rules.length === 0) {
return new RulesFileValidationError(location, 'empty rules');
}
for (const rule of rules) {
if (!(rule instanceof Object)) {
return new RulesFileValidationError(
location,
'all rules must be objects'
);
}
if (!rule.dest) {
return new RulesFileValidationError(
location,
'all rules must have a dest field'
);
}
}
}

View File

@@ -0,0 +1,8 @@
//
export default async function getCreditCards(now) {
const payload = await now.fetch('/stripe/sources/');
const cards = payload.sources;
return cards;
}

View File

@@ -1,6 +1,6 @@
import chalk from 'chalk';
import * as ERRORS from '../errors';
import * as ERRORS from '../errors-ts';
import Client from '../client';
import issueCert from './issue-cert';
import wait from '../output/wait';

View File

@@ -1,7 +1,7 @@
import chalk from 'chalk';
import { Cert } from '../../types';
import * as ERRORS from '../errors';
import * as ERRORS from '../errors-ts';
import Client from '../client';
import wait from '../output/wait';
import mapCertError from './map-cert-error';

View File

@@ -1,7 +1,7 @@
import { Cert } from '../../types';
import { Output } from '../output/create-output';
import Client from '../client';
import * as ERRORS from '../errors';
import * as ERRORS from '../errors-ts';
export default async function getCertById(
output: Output,

View File

@@ -1,7 +1,7 @@
import { stringify } from 'querystring';
import { Cert } from '../../types';
import { Output } from '../output';
import * as ERRORS from '../errors';
import * as ERRORS from '../errors-ts';
import Client from '../client';
type Response = {

View File

@@ -3,7 +3,7 @@ import Client from '../client';
import { Output } from '../output';
import { Cert } from '../../types';
import getCertById from './get-cert-by-id';
import { CertNotFound } from '../errors';
import { CertNotFound } from '../errors-ts';
type Response = {
certs: Cert[];

View File

@@ -1,5 +1,5 @@
import psl from 'psl';
import { InvalidDomain } from '../errors';
import { InvalidDomain } from '../errors-ts';
import isWildcardAlias from '../alias/is-wildcard-alias';
import extractDomain from '../alias/extract-domain';

View File

@@ -1,7 +1,7 @@
import ms from 'ms';
import { parse } from 'psl';
import chalk from 'chalk';
import * as ERRORS from '../errors';
import * as ERRORS from '../errors-ts';
import { Output } from '../output';
import dnsTable from '../format-dns-table';

View File

@@ -1,4 +1,4 @@
import * as ERRORS from '../errors';
import * as ERRORS from '../errors-ts';
export default function mapCertError(error: any, cns?: string[]) {
const errorCode: string = error.code;

View File

@@ -0,0 +1,50 @@
// Native
import os from 'os';
import path from 'path';
const checkPath = async dir => {
if (!dir) {
return;
}
const home = os.homedir();
let location;
const paths = {
home,
desktop: path.join(home, 'Desktop'),
downloads: path.join(home, 'Downloads'),
};
for (const locationPath in paths) {
if (!{}.hasOwnProperty.call(paths, locationPath)) {
continue;
}
if (dir === paths[locationPath]) {
location = locationPath;
}
}
if (!location) {
return;
}
let locationName;
switch (location) {
case 'home':
locationName = 'user directory';
break;
case 'downloads':
locationName = 'downloads directory';
break;
default:
locationName = location;
}
throw new Error(`You're trying to deploy your ${locationName}.`);
};
export default checkPath;

View File

@@ -1,13 +1,13 @@
import qs from 'querystring';
import { EventEmitter } from 'events';
import { parse as parseUrl } from 'url';
import fetch, { RequestInit } from 'node-fetch';
import retry, { RetryFunction, Options as RetryOptions } from 'async-retry';
import createOutput, { Output } from './output/create-output';
import responseError from './response-error';
import { URLSearchParams } from 'url';
import ua from './ua';
export interface FetchOptions {
export type FetchOptions = {
body?: NodeJS.ReadableStream | object | string;
headers?: { [key: string]: string };
json?: boolean;
@@ -15,7 +15,7 @@ export interface FetchOptions {
retry?: RetryOptions;
useCurrentTeam?: boolean;
accountId?: string;
}
};
export default class Client extends EventEmitter {
_apiUrl: string;
@@ -63,19 +63,19 @@ export default class Client extends EventEmitter {
: '';
if (opts.accountId || opts.useCurrentTeam !== false) {
const query = new URLSearchParams(parsedUrl.query);
const query = parsedUrl.query;
if (opts.accountId) {
if (opts.accountId.startsWith('team_')) {
query.set('teamId', opts.accountId);
query.teamId = opts.accountId;
} else {
query.delete('teamId');
delete query.teamId;
}
} else if (opts.useCurrentTeam !== false && this.currentTeam) {
query.set('teamId', this.currentTeam);
query.teamId = this.currentTeam;
}
_url = `${apiUrl}${parsedUrl.pathname}?${query}`;
_url = `${apiUrl}${parsedUrl.pathname}?${qs.stringify(query)}`;
delete opts.useCurrentTeam;
delete opts.accountId;

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