mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 21:07:47 +00:00
Compare commits
121 Commits
@now/pytho
...
@now/pytho
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
500c36f5d4 | ||
|
|
69dbbeac44 | ||
|
|
69486c3adb | ||
|
|
e6692bb79b | ||
|
|
94fba1d7af | ||
|
|
223d8f4774 | ||
|
|
42e7a7e4e3 | ||
|
|
6716fdd49b | ||
|
|
3b69092fd8 | ||
|
|
aa8eaedbc8 | ||
|
|
f519ed373f | ||
|
|
851dff4b03 | ||
|
|
c5ebaa11ea | ||
|
|
934fbc8992 | ||
|
|
72cb5515fd | ||
|
|
c7f0770d53 | ||
|
|
7ea49e8ada | ||
|
|
cae6ce96b3 | ||
|
|
3699dfd756 | ||
|
|
6dca96d877 | ||
|
|
88c14b27a2 | ||
|
|
0d2a9539f6 | ||
|
|
bae160bd7c | ||
|
|
92852ecff2 | ||
|
|
ac0c841cb8 | ||
|
|
53e4b71f89 | ||
|
|
017a2692ca | ||
|
|
311f89eecb | ||
|
|
40d2bc4743 | ||
|
|
37160cbc8b | ||
|
|
3807a2b018 | ||
|
|
b6697dd432 | ||
|
|
6c33496e8a | ||
|
|
89f32625ed | ||
|
|
8253e76ec0 | ||
|
|
e0b3e9606a | ||
|
|
dc75a303f7 | ||
|
|
c1eb8ec78c | ||
|
|
12435f25fd | ||
|
|
d4dc5222cf | ||
|
|
bf1e59b2d3 | ||
|
|
3657e4a36e | ||
|
|
09efc1d865 | ||
|
|
22bded50b6 | ||
|
|
b5b02be3c2 | ||
|
|
776f372eb3 | ||
|
|
81279fd40b | ||
|
|
3342485d29 | ||
|
|
028ee848f5 | ||
|
|
7e64c3b8a9 | ||
|
|
704031f7b2 | ||
|
|
5e3c184735 | ||
|
|
88a8022787 | ||
|
|
96844dc4a5 | ||
|
|
a09acd6969 | ||
|
|
4e232f78de | ||
|
|
b146a04772 | ||
|
|
eaaa50e616 | ||
|
|
c893eaeb7a | ||
|
|
5bf7d7fd07 | ||
|
|
ca8fc92b94 | ||
|
|
9956e85f12 | ||
|
|
7fa4739c78 | ||
|
|
0ef2e2a7ec | ||
|
|
8fd1752acf | ||
|
|
14a1446faf | ||
|
|
0c2c8c5ae5 | ||
|
|
511b27ad39 | ||
|
|
e22ce7da0a | ||
|
|
d9a4ce06bc | ||
|
|
77fb14cc60 | ||
|
|
17c397211e | ||
|
|
6ca83644bc | ||
|
|
d1946ea9b6 | ||
|
|
cc9eae3b71 | ||
|
|
7bbc17df4b | ||
|
|
df6b2be482 | ||
|
|
5ff6263fb7 | ||
|
|
04dc8aaf73 | ||
|
|
5435805e58 | ||
|
|
903f819c5d | ||
|
|
5d927b2d25 | ||
|
|
b7a260cc6d | ||
|
|
e8ba8fb97b | ||
|
|
dd1d9d856b | ||
|
|
eef4c65e5f | ||
|
|
3f64594a22 | ||
|
|
3f5f71f8ab | ||
|
|
2a44179898 | ||
|
|
4a6ddf8b1d | ||
|
|
eeb1b2442c | ||
|
|
129f234aaa | ||
|
|
331c352e2b | ||
|
|
2ccbaea9dd | ||
|
|
069eca3c62 | ||
|
|
305e364f8b | ||
|
|
4068805ae0 | ||
|
|
9a1e7a4a7a | ||
|
|
bbad3d1b96 | ||
|
|
c62116d9a4 | ||
|
|
c94086ff21 | ||
|
|
e99caa7b97 | ||
|
|
658b9e9007 | ||
|
|
3ef27ae45c | ||
|
|
e669cd1152 | ||
|
|
b22aa7c0cf | ||
|
|
582cbb61fb | ||
|
|
7f4197cf43 | ||
|
|
30889db487 | ||
|
|
2d08d5d23e | ||
|
|
59e7367e03 | ||
|
|
84af278e86 | ||
|
|
67d9ee39e2 | ||
|
|
6c67bb81f7 | ||
|
|
aba19701c0 | ||
|
|
bb79402999 | ||
|
|
420bc4b244 | ||
|
|
323c3d74cb | ||
|
|
4ecec8a8f6 | ||
|
|
98883f9978 | ||
|
|
37cf3be437 |
@@ -1,6 +1,5 @@
|
||||
version: 2
|
||||
jobs:
|
||||
|
||||
install:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
@@ -11,9 +10,9 @@ jobs:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "yarn.lock" }}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- v1-dependencies-
|
||||
- v1-dependencies-{{ checksum "yarn.lock" }}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- v1-dependencies-
|
||||
- run:
|
||||
name: Updating apt packages
|
||||
command: sudo apt-get update
|
||||
@@ -22,10 +21,11 @@ jobs:
|
||||
command: sudo apt-get install golang-go
|
||||
- run:
|
||||
name: Installing Dependencies
|
||||
command: yarn install
|
||||
command: yarn install --check-files --frozen-lockfile
|
||||
- save_cache:
|
||||
paths:
|
||||
- node_modules
|
||||
- packages/gatsby-plugin-now/node_modules
|
||||
- packages/now-build-utils/node_modules
|
||||
- packages/now-cgi/node_modules
|
||||
- packages/now-cli/node_modules
|
||||
@@ -43,6 +43,7 @@ jobs:
|
||||
root: .
|
||||
paths:
|
||||
- node_modules
|
||||
- packages/gatsby-plugin-now/node_modules
|
||||
- packages/now-build-utils/node_modules
|
||||
- packages/now-cgi/node_modules
|
||||
- packages/now-cli/node_modules
|
||||
@@ -67,6 +68,9 @@ jobs:
|
||||
command: sudo apt install -y rsync
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Linking dependencies
|
||||
command: yarn bootstrap
|
||||
- run:
|
||||
name: Building
|
||||
command: yarn build
|
||||
@@ -75,6 +79,7 @@ jobs:
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- packages/gatsby-plugin-now/test/fixtures
|
||||
- packages/now-build-utils/dist
|
||||
- packages/now-cgi/dist
|
||||
- packages/now-cli/dist
|
||||
@@ -91,6 +96,7 @@ jobs:
|
||||
- packages/now-routing-utils/dist
|
||||
- packages/now-ruby/dist
|
||||
- packages/now-static-build/dist
|
||||
- packages/now-static-build/test/fixtures/10a-gatsby-redirects/plugins
|
||||
|
||||
test-lint:
|
||||
docker:
|
||||
@@ -107,25 +113,6 @@ jobs:
|
||||
name: Linting Code
|
||||
command: yarn test-lint
|
||||
|
||||
# test-unit:
|
||||
# docker:
|
||||
# - image: circleci/node:10
|
||||
# working_directory: ~/repo
|
||||
# steps:
|
||||
# - checkout
|
||||
# - attach_workspace:
|
||||
# at: .
|
||||
# - run:
|
||||
# name: Compiling `now dev` HTML error templates
|
||||
# command: node packages/now-cli/scripts/compile-templates.js
|
||||
# - run:
|
||||
# name: Running Unit Tests
|
||||
# command: yarn test-unit --clean false
|
||||
# - persist_to_workspace:
|
||||
# root: .
|
||||
# paths:
|
||||
# - packages/now-cli/.nyc_output
|
||||
|
||||
test-integration-macos-node-8:
|
||||
macos:
|
||||
xcode: '9.2.0'
|
||||
@@ -384,36 +371,6 @@ jobs:
|
||||
name: Finalize Sentry Release
|
||||
command: sentry-cli releases finalize now-cli@`git describe --tags`
|
||||
|
||||
publish-stable:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Saving Authentication Information
|
||||
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
||||
- run:
|
||||
name: Publishing to Stable Channel
|
||||
command: npm publish --tag latest
|
||||
|
||||
publish-canary:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Saving Authentication Information
|
||||
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
||||
- run:
|
||||
name: Publishing to Canary Channel
|
||||
command: npm publish --tag canary
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
unscheduled:
|
||||
@@ -434,12 +391,6 @@ workflows:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
# - test-unit:
|
||||
# requires:
|
||||
# - build
|
||||
# filters:
|
||||
# tags:
|
||||
# only: /.*/
|
||||
- test-integration-macos-node-8:
|
||||
requires:
|
||||
- build
|
||||
@@ -520,7 +471,6 @@ workflows:
|
||||
only: /.*/
|
||||
- coverage:
|
||||
requires:
|
||||
#- test-unit
|
||||
- test-integration-macos-node-8
|
||||
- test-integration-macos-node-10
|
||||
- test-integration-macos-node-12
|
||||
@@ -538,19 +488,3 @@ workflows:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- publish-canary:
|
||||
requires:
|
||||
- coverage
|
||||
filters:
|
||||
tags:
|
||||
only: /^.*canary.*($|\b)/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
- publish-stable:
|
||||
requires:
|
||||
- coverage
|
||||
filters:
|
||||
tags:
|
||||
only: /^(\d+\.)?(\d+\.)?(\*|\d+)$/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
|
||||
32
.eslintignore
Normal file
32
.eslintignore
Normal file
@@ -0,0 +1,32 @@
|
||||
node_modules
|
||||
dist
|
||||
|
||||
# gatsby-plugin-now
|
||||
packages/gatsby-plugin-now/test/fixtures
|
||||
|
||||
# now-cli
|
||||
packages/now-cli/@types
|
||||
packages/now-cli/download
|
||||
packages/now-cli/dist
|
||||
packages/now-cli/test/fixtures
|
||||
packages/now-cli/test/dev/fixtures
|
||||
packages/now-cli/bin
|
||||
packages/now-cli/link
|
||||
packages/now-cli/src/util/dev/templates/*.ts
|
||||
|
||||
# now-client
|
||||
packages/now-client/tests/fixtures
|
||||
packages/now-client/lib
|
||||
|
||||
# now-next
|
||||
packages/now-next/test/fixtures
|
||||
|
||||
# now-node
|
||||
packages/now-node/src/bridge.ts
|
||||
packages/now-node/test/fixtures
|
||||
|
||||
# now-node-bridge
|
||||
packages/now-node-bridge/bridge.*
|
||||
|
||||
# now-static-build
|
||||
packages/now-static-build/test/fixtures
|
||||
64
.eslintrc.json
Normal file
64
.eslintrc.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"modules": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier",
|
||||
"prettier/@typescript-eslint"
|
||||
],
|
||||
"env": {
|
||||
"node": true,
|
||||
"jest": true,
|
||||
"es6": true
|
||||
},
|
||||
"rules": {
|
||||
"require-atomic-updates": 0,
|
||||
"@typescript-eslint/ban-ts-ignore": 0,
|
||||
"@typescript-eslint/camelcase": 0,
|
||||
"@typescript-eslint/explicit-function-return-type": 0,
|
||||
"@typescript-eslint/no-empty-function": 0,
|
||||
"@typescript-eslint/no-use-before-define": 0
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.js"],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-var-requires": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["packages/now-cli/**/*"],
|
||||
"rules": {
|
||||
"lines-between-class-members": 0,
|
||||
"no-async-promise-executor": 0,
|
||||
"no-control-regex": 0,
|
||||
"no-empty": 0,
|
||||
"prefer-const": 0,
|
||||
"prefer-destructuring": 0,
|
||||
"@typescript-eslint/ban-types": 0,
|
||||
"@typescript-eslint/consistent-type-assertions": 0,
|
||||
"@typescript-eslint/member-delimiter-style": 0,
|
||||
"@typescript-eslint/no-empty-function": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/no-inferrable-types": 0,
|
||||
"@typescript-eslint/no-var-requires": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["packages/now-client/**/*"],
|
||||
"rules": {
|
||||
"prefer-const": 0,
|
||||
"require-atomic-updates": 0,
|
||||
"@typescript-eslint/ban-ts-ignore": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Ignore test fixtures in GitHub Languages
|
||||
# See https://github.com/github/linguist#vendored-code
|
||||
packages/*/test/* linguist-vendored
|
||||
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
- '!*'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
Publish:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install
|
||||
run: yarn install --pure-lockfile
|
||||
run: yarn install --check-files --frozen-lockfile
|
||||
- name: Build
|
||||
run: yarn build
|
||||
- name: Publish
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,7 +12,9 @@ packages/now-cli/.builders
|
||||
packages/now-cli/assets
|
||||
packages/now-cli/src/util/dev/templates/*.ts
|
||||
packages/now-cli/test/**/yarn.lock
|
||||
!packages/now-cli/test/dev/**/yarn.lock
|
||||
packages/now-cli/test/**/node_modules
|
||||
packages/now-cli/test/dev/fixtures/08-hugo/hugo
|
||||
packages/now-cli/test/dev/fixtures/**/dist
|
||||
packages/now-cli/test/dev/fixtures/**/public
|
||||
packages/now-cli/test/fixtures/integration
|
||||
|
||||
@@ -12,5 +12,6 @@ optimistic_updates = true
|
||||
|
||||
[merge.message]
|
||||
title = "pull_request_title"
|
||||
body = "pull_request_body"
|
||||
include_pr_number = true
|
||||
body_type = "markdown"
|
||||
|
||||
4
.prettierrc
Normal file
4
.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
# Publishing to npm
|
||||
|
||||
Always publish to the Canary Channel as soon as a PR is merged into the `canary` branch.
|
||||
|
||||
```
|
||||
yarn publish-canary
|
||||
```
|
||||
|
||||
Publish the Stable Channel weekly.
|
||||
|
||||
- Cherry pick each commit from `canary` to `master` branch
|
||||
- Verify that you are _in-sync_ with canary (with the exception of the `version` line in `package.json`)
|
||||
- Deploy the modified Builders
|
||||
|
||||
```
|
||||
# View differences excluding "Publish" commits
|
||||
git checkout canary && git pull
|
||||
git log --pretty=format:"$ad- %s [%an]" | grep -v Publish > ~/Desktop/canary.txt
|
||||
git checkout master && git pull
|
||||
git log --pretty=format:"$ad- %s [%an]" | grep -v Publish > ~/Desktop/master.txt
|
||||
diff ~/Desktop/canary.txt ~/Desktop/master.txt
|
||||
|
||||
# Cherry pick all PRs from canary into master ...
|
||||
git cherry-pick <PR501_COMMIT_SHA>
|
||||
git cherry-pick <PR502_COMMIT_SHA>
|
||||
git cherry-pick <PR503_COMMIT_SHA>
|
||||
git cherry-pick <PR504_COMMIT_SHA>
|
||||
|
||||
# Verify the only difference is "version" in package.json
|
||||
git diff origin/canary
|
||||
|
||||
# Ship it
|
||||
yarn publish-stable
|
||||
```
|
||||
|
||||
After running this publish step, GitHub Actions will take care of publishing the modified Builder packages to npm.
|
||||
|
||||
If for some reason GitHub Actions fails to publish the npm package, you may do so
|
||||
manually by running `npm publish` from the package directory. Make sure to
|
||||
use `npm publish --tag canary` if you are publishing a canary release!
|
||||
@@ -1,6 +1,6 @@
|
||||

|
||||
|
||||
[](https://circleci.com/gh/zeit/workflows/now)
|
||||
[](https://circleci.com/gh/zeit/workflows/now/tree/master)
|
||||
[](https://spectrum.chat/zeit)
|
||||
|
||||
**Note**: The [canary](https://github.com/zeit/now/tree/canary) branch is under heavy development – the stable release branch is [master](https://github.com/zeit/now/tree/master).
|
||||
|
||||
18
changelog.js
Normal file
18
changelog.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const commit = execSync('git log --pretty=format:"%s %H"')
|
||||
.toString()
|
||||
.trim()
|
||||
.split('\n')
|
||||
.find(line => line.startsWith('Publish '))
|
||||
.split(' ')
|
||||
.pop();
|
||||
|
||||
if (!commit) {
|
||||
throw new Error('Unable to find last publish commit');
|
||||
}
|
||||
|
||||
const log = execSync(`git log --pretty=format:"- %s [%an]" ${commit}...HEAD`).toString().trim();
|
||||
|
||||
console.log(`Changes since the last publish commit ${commit}:`);
|
||||
console.log(`\n${log}\n`);
|
||||
36
diff.js
Normal file
36
diff.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const { execSync } = require('child_process');
|
||||
const { join } = require('path');
|
||||
const { tmpdir } = require('os');
|
||||
const { mkdirSync, writeFileSync } = require('fs');
|
||||
|
||||
function getCommits(count) {
|
||||
return execSync('git log --pretty=format:"%s [%an]"')
|
||||
.toString()
|
||||
.trim()
|
||||
.split('\n')
|
||||
.slice(0, count)
|
||||
.filter(line => !line.startsWith('Publish '))
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
function main(count = '100') {
|
||||
console.log(`Generating diff using last ${count} commits...`);
|
||||
const randomTmpId = Math.random().toString().slice(2);
|
||||
const dir = join(tmpdir(), 'now-diff' + randomTmpId);
|
||||
mkdirSync(dir);
|
||||
|
||||
execSync('git checkout canary && git pull');
|
||||
const canary = getCommits(count);
|
||||
execSync('git checkout master && git pull');
|
||||
const master = getCommits(count);
|
||||
|
||||
writeFileSync(join(dir, 'log.txt'), '# canary\n' + canary);
|
||||
execSync('git init && git add -A && git commit -m "init"', { cwd: dir });
|
||||
writeFileSync(join(dir, 'log.txt'), '# master\n' + master);
|
||||
|
||||
console.log(`Done generating diff. Run the following:`);
|
||||
console.log(`cd ${dir}`);
|
||||
console.log('Then use `git diff` or `git difftool` to view the differences.');
|
||||
}
|
||||
|
||||
main(process.argv[2]);
|
||||
88
package.json
88
package.json
@@ -1,32 +1,62 @@
|
||||
{
|
||||
"name": "now-builders",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"lerna": "3.16.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@zeit/ncc": "0.20.4",
|
||||
"async-retry": "1.2.3",
|
||||
"buffer-replace": "1.0.0",
|
||||
"node-fetch": "2.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lerna": "lerna",
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"publish-stable": "git checkout master && git pull && lerna version --exact",
|
||||
"publish-canary": "git checkout canary && git pull && lerna version prerelease --preid canary --exact",
|
||||
"publish-from-github": "./.circleci/publish.sh",
|
||||
"build": "node run.js build all",
|
||||
"test-lint": "node run.js test-lint",
|
||||
"test-unit": "node run.js test-unit",
|
||||
"test-integration": "node run.js test-integration",
|
||||
"test-integration-once": "node run.js test-integration-once",
|
||||
"test-integration-now-dev": "node run.js test-integration-now-dev"
|
||||
"name": "now-builders",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"nohoist": [
|
||||
"**/@types/**"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"lerna": "3.16.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "2.0.0",
|
||||
"@typescript-eslint/parser": "2.0.0",
|
||||
"@zeit/ncc": "0.20.4",
|
||||
"async-retry": "1.2.3",
|
||||
"buffer-replace": "1.0.0",
|
||||
"eslint": "6.2.2",
|
||||
"eslint-config-prettier": "6.1.0",
|
||||
"husky": "3.0.4",
|
||||
"lint-staged": "9.2.5",
|
||||
"node-fetch": "2.6.0",
|
||||
"prettier": "1.18.2"
|
||||
},
|
||||
"scripts": {
|
||||
"lerna": "lerna",
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"publish-stable": "git checkout master && git pull && lerna version --exact",
|
||||
"publish-canary": "git checkout canary && git pull && lerna version prerelease --preid canary --exact",
|
||||
"publish-from-github": "./.circleci/publish.sh",
|
||||
"diff": "node diff.js",
|
||||
"changelog": "node changelog.js",
|
||||
"build": "node run.js build all",
|
||||
"test-lint": "node run.js test-lint",
|
||||
"test-unit": "node run.js test-unit",
|
||||
"test-integration": "node run.js test-integration",
|
||||
"test-integration-once": "node run.js test-integration-once",
|
||||
"test-integration-now-dev": "node run.js test-integration-now-dev",
|
||||
"lint": "eslint . --ext .ts,.js"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts}": [
|
||||
"prettier --write",
|
||||
"eslint",
|
||||
"git add"
|
||||
],
|
||||
"*.{json,md}": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
6
packages/gatsby-plugin-now/build.sh
Executable file
6
packages/gatsby-plugin-now/build.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# build fixtures for tests
|
||||
yarn --cwd test/fixtures install
|
||||
yarn --cwd test/fixtures run build
|
||||
33
packages/gatsby-plugin-now/gatsby-node.js
Normal file
33
packages/gatsby-plugin-now/gatsby-node.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const path = require('path');
|
||||
const writeFile = require('util').promisify(require('fs').writeFile);
|
||||
|
||||
const REDIRECT_FILE_NAME = '__now_routes_g4t5bY.json';
|
||||
|
||||
exports.onPostBuild = async ({ store }) => {
|
||||
const { redirects, program } = store.getState();
|
||||
|
||||
if (!redirects.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const routes = [{ handle: 'filesystem' }];
|
||||
|
||||
for (const redirect of redirects) {
|
||||
const route = {
|
||||
src: redirect.fromPath,
|
||||
status: redirect.statusCode || (redirect.isPermanent ? 301 : 302),
|
||||
headers: { Location: redirect.toPath }
|
||||
};
|
||||
|
||||
if (redirect.force) {
|
||||
routes.unshift(route);
|
||||
} else {
|
||||
routes.push(route);
|
||||
}
|
||||
}
|
||||
|
||||
await writeFile(
|
||||
path.join(program.directory, 'public', REDIRECT_FILE_NAME),
|
||||
JSON.stringify(routes)
|
||||
);
|
||||
};
|
||||
1
packages/gatsby-plugin-now/index.js
Normal file
1
packages/gatsby-plugin-now/index.js
Normal file
@@ -0,0 +1 @@
|
||||
// noop
|
||||
36
packages/gatsby-plugin-now/package.json
Normal file
36
packages/gatsby-plugin-now/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "gatsby-plugin-now",
|
||||
"version": "1.2.2",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/zeit/now/tree/canary/packages/gatsby-plugin-now#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/zeit/now.git",
|
||||
"directory": "packages/gatsby-plugin-now"
|
||||
},
|
||||
"keywords": [
|
||||
"gatsby",
|
||||
"gatsby-plugin"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "./build.sh",
|
||||
"test-integration-once": "jest --verbose"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"gatsby-node.js"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"gatsby": ">=2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "24.9.0"
|
||||
},
|
||||
"jest": {
|
||||
"testPathIgnorePatterns": [
|
||||
"/node_modules/",
|
||||
"<rootDir>/test/fixtures/"
|
||||
]
|
||||
}
|
||||
}
|
||||
24
packages/gatsby-plugin-now/readme.md
Normal file
24
packages/gatsby-plugin-now/readme.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# gatsby-plugin-now
|
||||
|
||||
This plugin generates [Now Routes](https://zeit.co/docs/v2/advanced/routes) for [redirects](https://www.gatsbyjs.org/docs/actions/#createRedirect) you configured for to your Gatsby project.
|
||||
|
||||
### Usage
|
||||
|
||||
1. Install the plugin:
|
||||
|
||||
```
|
||||
npm install gatsby-plugin-now --save-dev
|
||||
```
|
||||
|
||||
2. Add it to the configuration file:
|
||||
|
||||
```
|
||||
// gatsby-config.js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
'gatsby-plugin-now'
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
3. [Deploy your project to ZEIT Now](https://www.gatsbyjs.org/docs/deploying-to-zeit-now/)
|
||||
@@ -0,0 +1,86 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`test generated now routes 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"headers": Object {
|
||||
"Location": "/",
|
||||
},
|
||||
"src": "/my-special-redirect",
|
||||
"status": 302,
|
||||
},
|
||||
Object {
|
||||
"handle": "filesystem",
|
||||
},
|
||||
Object {
|
||||
"headers": Object {
|
||||
"Location": "/page-2",
|
||||
},
|
||||
"src": "/page2",
|
||||
"status": 301,
|
||||
},
|
||||
Object {
|
||||
"headers": Object {
|
||||
"Location": "/page-2/",
|
||||
},
|
||||
"src": "/page2/",
|
||||
"status": 301,
|
||||
},
|
||||
Object {
|
||||
"headers": Object {
|
||||
"Location": "/",
|
||||
},
|
||||
"src": "/orange",
|
||||
"status": 302,
|
||||
},
|
||||
Object {
|
||||
"headers": Object {
|
||||
"Location": "/",
|
||||
},
|
||||
"src": "/grape",
|
||||
"status": 302,
|
||||
},
|
||||
Object {
|
||||
"headers": Object {
|
||||
"Location": "/page-2/",
|
||||
},
|
||||
"src": "/blue",
|
||||
"status": 302,
|
||||
},
|
||||
Object {
|
||||
"headers": Object {
|
||||
"Location": "/page-2/",
|
||||
},
|
||||
"src": "/randirect",
|
||||
"status": 302,
|
||||
},
|
||||
Object {
|
||||
"headers": Object {
|
||||
"Location": "/",
|
||||
},
|
||||
"src": "/juice",
|
||||
"status": 302,
|
||||
},
|
||||
Object {
|
||||
"headers": Object {
|
||||
"Location": "/",
|
||||
},
|
||||
"src": "/soda",
|
||||
"status": 302,
|
||||
},
|
||||
Object {
|
||||
"headers": Object {
|
||||
"Location": "/page-2/",
|
||||
},
|
||||
"src": "/donut",
|
||||
"status": 302,
|
||||
},
|
||||
Object {
|
||||
"headers": Object {
|
||||
"Location": "/page-2/",
|
||||
},
|
||||
"src": "/randorect",
|
||||
"status": 302,
|
||||
},
|
||||
]
|
||||
`;
|
||||
4
packages/gatsby-plugin-now/test/fixtures/.gitignore
vendored
Normal file
4
packages/gatsby-plugin-now/test/fixtures/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
public
|
||||
node_modules
|
||||
.cache
|
||||
yarn.lock
|
||||
3
packages/gatsby-plugin-now/test/fixtures/gatsby-config.js
vendored
Normal file
3
packages/gatsby-plugin-now/test/fixtures/gatsby-config.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
plugins: [{ resolve: require.resolve('../../') }]
|
||||
};
|
||||
105
packages/gatsby-plugin-now/test/fixtures/gatsby-node.js
vendored
Normal file
105
packages/gatsby-plugin-now/test/fixtures/gatsby-node.js
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
'use strict';
|
||||
|
||||
// Implement the Gatsby API “createPages”. This is called once the
|
||||
// data layer is bootstrapped to let plugins create pages from data.
|
||||
exports.createPages = ({ actions }) => {
|
||||
// need createRedirect action in actions collection
|
||||
// to make the redirection magic happen.
|
||||
// https://www.gatsbyjs.org/docs/bound-action-creators/
|
||||
const { createRedirect } = actions;
|
||||
|
||||
// Let’s set up some string consts to use thoroughout the following.
|
||||
// MDN > JavaScript reference > Statements and declarations
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
|
||||
// Maybe we usually redirect to page 2, with trailing slash.
|
||||
const page2Path = `/page-2/`;
|
||||
// But sometimes to homepage.
|
||||
const homePath = `/`;
|
||||
|
||||
// One-off redirect, note trailing slash missing on fromPath and
|
||||
// toPath here.
|
||||
createRedirect({
|
||||
fromPath: `/page2`,
|
||||
isPermanent: true,
|
||||
redirectInBrowser: true,
|
||||
toPath: `/page-2`
|
||||
});
|
||||
|
||||
// Another one-off redirect, note trailing slash on toPath here.
|
||||
// This time we want trailing slash on toPath so we use
|
||||
// page2Path. Need to handle trailing-slashed and non-trailing-
|
||||
// slashed fromPaths separately, Gatsby serves page components
|
||||
// at either version by default, but we need to explicitly redirect
|
||||
// both versions independently, more on page components:
|
||||
// Docs > Building with Components
|
||||
// https://www.gatsbyjs.org/docs/building-with-components/
|
||||
// and handling trailing slashes:
|
||||
// Docs > Creating and modifying pages > Removing trailing slashes
|
||||
// https://www.gatsbyjs.org/docs/creating-and-modifying-pages/#removing-trailing-slashes
|
||||
createRedirect({
|
||||
fromPath: `/page2/`,
|
||||
isPermanent: true,
|
||||
redirectInBrowser: true,
|
||||
toPath: page2Path
|
||||
});
|
||||
|
||||
// One approach to handle several redirects at once is to create an
|
||||
// array of from/to path pairs.
|
||||
let redirectBatch1 = [
|
||||
{ f: `/orange`, t: `/` },
|
||||
// We could use homePath and page2Path directly here.
|
||||
{ f: `/grape`, t: homePath },
|
||||
{ f: `/blue`, t: page2Path },
|
||||
// or leave to empty and swap for page2Path later on.
|
||||
{ f: `/randirect`, t: `` }
|
||||
];
|
||||
|
||||
// Then we can loop through the array of object literals to create
|
||||
// each redirect. A for loop would do the trick
|
||||
for (var { f: f, t: t } of redirectBatch1) {
|
||||
// Here we swap any empty toPath values for trusty page 2 via
|
||||
// page2Path.
|
||||
if (t === ``) {
|
||||
t = page2Path;
|
||||
}
|
||||
createRedirect({
|
||||
fromPath: f,
|
||||
redirectInBrowser: true,
|
||||
toPath: t
|
||||
});
|
||||
// Uncomment next line to see loop in action during build
|
||||
// console.log('\nRedirecting:\n' + f + '\nTo:\n' + t + '\n');
|
||||
// or check .cache/redirects.json post-compile.
|
||||
}
|
||||
|
||||
// A more modern approach might use forEach rather than for...of
|
||||
// Compare
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration#for...of_statement
|
||||
// and
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
|
||||
let redirectBatch2 = [
|
||||
{ f: `/juice`, t: `/` },
|
||||
{ f: `/soda`, t: `/` },
|
||||
{ f: `/donut`, t: page2Path },
|
||||
{ f: `/randorect`, t: `` }
|
||||
];
|
||||
|
||||
redirectBatch2.forEach(({ f, t }) => {
|
||||
if (t === ``) {
|
||||
t = page2Path;
|
||||
}
|
||||
createRedirect({
|
||||
fromPath: f,
|
||||
redirectInBrowser: true,
|
||||
toPath: t
|
||||
});
|
||||
// Uncomment next line to see forEach in action during build
|
||||
// console.log('\nRedirecting:\n' + f + '\nTo:\n' + t + '\n');
|
||||
});
|
||||
|
||||
createRedirect({
|
||||
fromPath: '/my-special-redirect',
|
||||
toPath: homePath,
|
||||
force: true
|
||||
});
|
||||
};
|
||||
11
packages/gatsby-plugin-now/test/fixtures/package.json
vendored
Normal file
11
packages/gatsby-plugin-now/test/fixtures/package.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "fixtures",
|
||||
"dependencies": {
|
||||
"gatsby": "2.14.0",
|
||||
"react": "16.9.0",
|
||||
"react-dom": "16.9.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gatsby build"
|
||||
}
|
||||
}
|
||||
9
packages/gatsby-plugin-now/test/fixtures/src/pages/index.js
vendored
Normal file
9
packages/gatsby-plugin-now/test/fixtures/src/pages/index.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
const IndexPage = () => (
|
||||
<div>
|
||||
<h1>Hi people</h1>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default IndexPage;
|
||||
5
packages/gatsby-plugin-now/test/index.test.js
vendored
Normal file
5
packages/gatsby-plugin-now/test/index.test.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
test('test generated now routes', async () => {
|
||||
const nowRoutes = require('./fixtures/public/__now_routes_g4t5bY.json');
|
||||
|
||||
expect(nowRoutes).toMatchSnapshot();
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/build-utils",
|
||||
"version": "0.9.14",
|
||||
"version": "0.10.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
export default function debug(message: string, ...additional: any[]) {
|
||||
if (process.env.NOW_BUILDER_DEBUG) {
|
||||
console.log(message, ...additional);
|
||||
} else if (process.env.NOW_BUILDER_ANNOTATE) {
|
||||
console.log(`[now-builder-debug] ${message}`, ...additional);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ interface Options {
|
||||
tag?: 'canary' | 'latest' | string;
|
||||
}
|
||||
|
||||
const src: string = 'package.json';
|
||||
const src = 'package.json';
|
||||
const config: Config = { zeroConfig: true };
|
||||
|
||||
const MISSING_BUILD_SCRIPT_ERROR: ErrorResponse = {
|
||||
@@ -96,8 +96,8 @@ async function detectApiBuilders(files: string[]): Promise<Builder[]> {
|
||||
.sort(sortFiles)
|
||||
.filter(ignoreApiFilter)
|
||||
.map(file => {
|
||||
const result = getApiBuilders().find(
|
||||
({ src }): boolean => minimatch(file, src)
|
||||
const result = getApiBuilders().find(({ src }): boolean =>
|
||||
minimatch(file, src)
|
||||
);
|
||||
|
||||
return result ? { ...result, src: file } : null;
|
||||
@@ -107,6 +107,31 @@ async function detectApiBuilders(files: string[]): Promise<Builder[]> {
|
||||
return finishedBuilds as Builder[];
|
||||
}
|
||||
|
||||
// When a package has files that conflict with `/api` routes
|
||||
// e.g. Next.js pages/api we'll check it here and return an error.
|
||||
async function checkConflictingFiles(
|
||||
files: string[],
|
||||
builders: Builder[]
|
||||
): Promise<ErrorResponse | null> {
|
||||
// For Next.js
|
||||
if (builders.some(builder => builder.use.startsWith('@now/next'))) {
|
||||
const hasApiPages = files.some(file => file.startsWith('pages/api/'));
|
||||
const hasApiBuilders = builders.some(builder =>
|
||||
builder.src.startsWith('api/')
|
||||
);
|
||||
|
||||
if (hasApiPages && hasApiBuilders) {
|
||||
return {
|
||||
code: 'conflicting_files',
|
||||
message:
|
||||
'It is not possible to use `api` and `pages/api` at the same time, please only use one option',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// When zero config is used we can call this function
|
||||
// to determine what builders to use
|
||||
export async function detectBuilders(
|
||||
@@ -116,20 +141,28 @@ export async function detectBuilders(
|
||||
): Promise<{
|
||||
builders: Builder[] | null;
|
||||
errors: ErrorResponse[] | null;
|
||||
warnings: ErrorResponse[];
|
||||
}> {
|
||||
const errors: ErrorResponse[] = [];
|
||||
const warnings: ErrorResponse[] = [];
|
||||
|
||||
// Detect all builders for the `api` directory before anything else
|
||||
let builders = await detectApiBuilders(files);
|
||||
|
||||
if (pkg && hasBuildScript(pkg)) {
|
||||
builders.push(await detectBuilder(pkg));
|
||||
|
||||
const conflictError = await checkConflictingFiles(files, builders);
|
||||
|
||||
if (conflictError) {
|
||||
warnings.push(conflictError);
|
||||
}
|
||||
} else {
|
||||
if (pkg && builders.length === 0) {
|
||||
// We only show this error when there are no api builders
|
||||
// since the dependencies of the pkg could be used for those
|
||||
errors.push(MISSING_BUILD_SCRIPT_ERROR);
|
||||
return { errors, builders: null };
|
||||
return { errors, warnings, builders: null };
|
||||
}
|
||||
|
||||
// We allow a `public` directory
|
||||
@@ -178,5 +211,6 @@ export async function detectBuilders(
|
||||
return {
|
||||
builders: builders.length ? builders : null,
|
||||
errors: errors.length ? errors : null,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,37 +43,35 @@ function getSegmentName(segment: string): string | null {
|
||||
function createRouteFromPath(filePath: string): Route {
|
||||
const parts = filePath.split('/');
|
||||
|
||||
let counter: number = 1;
|
||||
let counter = 1;
|
||||
const query: string[] = [];
|
||||
|
||||
const srcParts = parts.map(
|
||||
(segment, index): string => {
|
||||
const name = getSegmentName(segment);
|
||||
const isLast = index === parts.length - 1;
|
||||
const srcParts = parts.map((segment, index): string => {
|
||||
const name = getSegmentName(segment);
|
||||
const isLast = index === parts.length - 1;
|
||||
|
||||
if (name !== null) {
|
||||
// We can't use `URLSearchParams` because `$` would get escaped
|
||||
query.push(`${name}=$${counter++}`);
|
||||
return `([^\\/]+)`;
|
||||
} else if (isLast) {
|
||||
const { name: fileName, ext } = parsePath(segment);
|
||||
const isIndex = fileName === 'index';
|
||||
const prefix = isIndex ? '\\/' : '';
|
||||
if (name !== null) {
|
||||
// We can't use `URLSearchParams` because `$` would get escaped
|
||||
query.push(`${name}=$${counter++}`);
|
||||
return `([^\\/]+)`;
|
||||
} else if (isLast) {
|
||||
const { name: fileName, ext } = parsePath(segment);
|
||||
const isIndex = fileName === 'index';
|
||||
const prefix = isIndex ? '\\/' : '';
|
||||
|
||||
const names = [
|
||||
prefix,
|
||||
prefix + escapeName(fileName),
|
||||
prefix + escapeName(fileName) + escapeName(ext),
|
||||
].filter(Boolean);
|
||||
const names = [
|
||||
prefix,
|
||||
prefix + escapeName(fileName),
|
||||
prefix + escapeName(fileName) + escapeName(ext)
|
||||
].filter(Boolean);
|
||||
|
||||
// Either filename with extension, filename without extension
|
||||
// or nothing when the filename is `index`
|
||||
return `(${names.join('|')})${isIndex ? '?' : ''}`;
|
||||
}
|
||||
|
||||
return segment;
|
||||
// Either filename with extension, filename without extension
|
||||
// or nothing when the filename is `index`
|
||||
return `(${names.join('|')})${isIndex ? '?' : ''}`;
|
||||
}
|
||||
);
|
||||
|
||||
return segment;
|
||||
});
|
||||
|
||||
const { name: fileName } = parsePath(filePath);
|
||||
const isIndex = fileName === 'index';
|
||||
@@ -230,8 +228,8 @@ async function detectApiRoutes(files: string[]): Promise<RoutesResult> {
|
||||
message:
|
||||
`The segment "${conflictingSegment}" occurres more than ` +
|
||||
`one time in your path "${file}". Please make sure that ` +
|
||||
`every segment in a path is unique`,
|
||||
},
|
||||
`every segment in a path is unique`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -251,8 +249,8 @@ async function detectApiRoutes(files: string[]): Promise<RoutesResult> {
|
||||
message:
|
||||
`Two or more files have conflicting paths or names. ` +
|
||||
`Please make sure path segments and filenames, without their extension, are unique. ` +
|
||||
`The path "${file}" has conflicts with ${messagePaths}`,
|
||||
},
|
||||
`The path "${file}" has conflicts with ${messagePaths}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -263,7 +261,7 @@ async function detectApiRoutes(files: string[]): Promise<RoutesResult> {
|
||||
if (defaultRoutes.length) {
|
||||
defaultRoutes.push({
|
||||
status: 404,
|
||||
src: '/api(\\/.*)?$',
|
||||
src: '/api(\\/.*)?$'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -289,7 +287,7 @@ export async function detectRoutes(
|
||||
if (routesResult.defaultRoutes && hasPublicBuilder(builders)) {
|
||||
routesResult.defaultRoutes.push({
|
||||
src: '/(.*)',
|
||||
dest: '/public/$1',
|
||||
dest: '/public/$1'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { intersects } from 'semver';
|
||||
import { NodeVersion } from '../types';
|
||||
import debug from '../debug';
|
||||
|
||||
const supportedOptions: NodeVersion[] = [
|
||||
{ major: 10, range: '10.x', runtime: 'nodejs10.x' },
|
||||
@@ -20,7 +21,7 @@ export async function getSupportedNodeVersion(
|
||||
|
||||
if (!engineRange) {
|
||||
if (!silent) {
|
||||
console.log(
|
||||
debug(
|
||||
'missing `engines` in `package.json`, using default range: ' +
|
||||
selection.range
|
||||
);
|
||||
@@ -34,7 +35,7 @@ export async function getSupportedNodeVersion(
|
||||
});
|
||||
if (found) {
|
||||
if (!silent) {
|
||||
console.log(
|
||||
debug(
|
||||
'Found `engines` in `package.json`, selecting range: ' +
|
||||
selection.range
|
||||
);
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import assert from 'assert';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import debug from '../debug';
|
||||
import spawn from 'cross-spawn';
|
||||
import { SpawnOptions } from 'child_process';
|
||||
import { deprecate } from 'util';
|
||||
import { cpus } from 'os';
|
||||
import { Meta, PackageJson, NodeVersion, Config } from '../types';
|
||||
import { getSupportedNodeVersion } from './node-version';
|
||||
|
||||
function spawnAsync(
|
||||
export function spawnAsync(
|
||||
command: string,
|
||||
args: string[],
|
||||
cwd: string,
|
||||
opts: SpawnOptions = {}
|
||||
) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const stderrLogs: Buffer[] = [];
|
||||
opts = { stdio: 'inherit', cwd, ...opts };
|
||||
opts = { stdio: 'inherit', ...opts };
|
||||
const child = spawn(command, args, opts);
|
||||
|
||||
if (opts.stdio === 'pipe' && child.stderr) {
|
||||
@@ -54,7 +55,10 @@ export async function runShellScript(
|
||||
assert(path.isAbsolute(fsPath));
|
||||
const destPath = path.dirname(fsPath);
|
||||
await chmodPlusX(fsPath);
|
||||
await spawnAsync(`./${path.basename(fsPath)}`, args, destPath, spawnOpts);
|
||||
await spawnAsync(`./${path.basename(fsPath)}`, args, {
|
||||
cwd: destPath,
|
||||
...spawnOpts,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -128,38 +132,100 @@ async function scanParentDirs(destPath: string, readPackageJson = false) {
|
||||
export async function runNpmInstall(
|
||||
destPath: string,
|
||||
args: string[] = [],
|
||||
spawnOpts?: SpawnOptions
|
||||
spawnOpts?: SpawnOptions,
|
||||
meta?: Meta
|
||||
) {
|
||||
if (meta && meta.isDev) {
|
||||
debug('Skipping dependency installation because dev mode is enabled');
|
||||
return;
|
||||
}
|
||||
|
||||
assert(path.isAbsolute(destPath));
|
||||
|
||||
let commandArgs = args;
|
||||
console.log(`installing to ${destPath}`);
|
||||
debug(`Installing to ${destPath}`);
|
||||
const { hasPackageLockJson } = await scanParentDirs(destPath);
|
||||
|
||||
const opts = spawnOpts || { env: process.env };
|
||||
const opts = { cwd: destPath, ...spawnOpts } || {
|
||||
cwd: destPath,
|
||||
env: process.env,
|
||||
};
|
||||
|
||||
if (hasPackageLockJson) {
|
||||
commandArgs = args.filter(a => a !== '--prefer-offline');
|
||||
await spawnAsync(
|
||||
'npm',
|
||||
commandArgs.concat(['install', '--unsafe-perm']),
|
||||
destPath,
|
||||
opts
|
||||
);
|
||||
} else {
|
||||
await spawnAsync(
|
||||
'yarn',
|
||||
commandArgs.concat(['--ignore-engines', '--cwd', destPath]),
|
||||
destPath,
|
||||
opts
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function runBundleInstall(
|
||||
destPath: string,
|
||||
args: string[] = [],
|
||||
spawnOpts?: SpawnOptions,
|
||||
meta?: Meta
|
||||
) {
|
||||
if (meta && meta.isDev) {
|
||||
debug('Skipping dependency installation because dev mode is enabled');
|
||||
return;
|
||||
}
|
||||
|
||||
assert(path.isAbsolute(destPath));
|
||||
const opts = { cwd: destPath, ...spawnOpts } || {
|
||||
cwd: destPath,
|
||||
env: process.env,
|
||||
};
|
||||
|
||||
await spawnAsync(
|
||||
'bundle',
|
||||
args.concat([
|
||||
'install',
|
||||
'--no-prune',
|
||||
'--retry',
|
||||
'3',
|
||||
'--jobs',
|
||||
String(cpus().length || 1),
|
||||
]),
|
||||
opts
|
||||
);
|
||||
}
|
||||
|
||||
export async function runPipInstall(
|
||||
destPath: string,
|
||||
args: string[] = [],
|
||||
spawnOpts?: SpawnOptions,
|
||||
meta?: Meta
|
||||
) {
|
||||
if (meta && meta.isDev) {
|
||||
debug('Skipping dependency installation because dev mode is enabled');
|
||||
return;
|
||||
}
|
||||
|
||||
assert(path.isAbsolute(destPath));
|
||||
const opts = { cwd: destPath, ...spawnOpts } || {
|
||||
cwd: destPath,
|
||||
env: process.env,
|
||||
};
|
||||
|
||||
await spawnAsync(
|
||||
'pip3',
|
||||
['install', '--disable-pip-version-check', ...args],
|
||||
opts
|
||||
);
|
||||
}
|
||||
|
||||
export async function runPackageJsonScript(
|
||||
destPath: string,
|
||||
scriptName: string,
|
||||
opts?: SpawnOptions
|
||||
spawnOpts?: SpawnOptions
|
||||
) {
|
||||
assert(path.isAbsolute(destPath));
|
||||
const { packageJson, hasPackageLockJson } = await scanParentDirs(
|
||||
@@ -174,17 +240,14 @@ export async function runPackageJsonScript(
|
||||
);
|
||||
if (!hasScript) return false;
|
||||
|
||||
const opts = { cwd: destPath, ...spawnOpts };
|
||||
|
||||
if (hasPackageLockJson) {
|
||||
console.log(`running "npm run ${scriptName}"`);
|
||||
await spawnAsync('npm', ['run', scriptName], destPath, opts);
|
||||
console.log(`Running "npm run ${scriptName}"`);
|
||||
await spawnAsync('npm', ['run', scriptName], opts);
|
||||
} else {
|
||||
console.log(`running "yarn run ${scriptName}"`);
|
||||
await spawnAsync(
|
||||
'yarn',
|
||||
['--cwd', destPath, 'run', scriptName],
|
||||
destPath,
|
||||
opts
|
||||
);
|
||||
console.log(`Running "yarn run ${scriptName}"`);
|
||||
await spawnAsync('yarn', ['--cwd', destPath, 'run', scriptName], opts);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -2,14 +2,18 @@ import FileBlob from './file-blob';
|
||||
import FileFsRef from './file-fs-ref';
|
||||
import FileRef from './file-ref';
|
||||
import { Lambda, createLambda } from './lambda';
|
||||
import { Prerender } from './prerender';
|
||||
import download, { DownloadedFiles } from './fs/download';
|
||||
import getWriteableDirectory from './fs/get-writable-directory';
|
||||
import glob from './fs/glob';
|
||||
import rename from './fs/rename';
|
||||
import {
|
||||
spawnAsync,
|
||||
installDependencies,
|
||||
runPackageJsonScript,
|
||||
runNpmInstall,
|
||||
runBundleInstall,
|
||||
runPipInstall,
|
||||
runShellScript,
|
||||
getNodeVersion,
|
||||
getSpawnOptions,
|
||||
@@ -26,14 +30,18 @@ export {
|
||||
FileRef,
|
||||
Lambda,
|
||||
createLambda,
|
||||
Prerender,
|
||||
download,
|
||||
DownloadedFiles,
|
||||
getWriteableDirectory,
|
||||
glob,
|
||||
rename,
|
||||
spawnAsync,
|
||||
installDependencies,
|
||||
runPackageJsonScript,
|
||||
runNpmInstall,
|
||||
runBundleInstall,
|
||||
runPipInstall,
|
||||
runShellScript,
|
||||
getNodeVersion,
|
||||
getSpawnOptions,
|
||||
|
||||
35
packages/now-build-utils/src/prerender.ts
Normal file
35
packages/now-build-utils/src/prerender.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import FileBlob from './file-blob';
|
||||
import FileFsRef from './file-fs-ref';
|
||||
import FileRef from './file-ref';
|
||||
import { Lambda } from './lambda';
|
||||
|
||||
interface PrerenderOptions {
|
||||
expiration: number;
|
||||
lambda: Lambda;
|
||||
fallback: FileBlob | FileFsRef | FileRef;
|
||||
group?: number;
|
||||
}
|
||||
|
||||
export class Prerender {
|
||||
public type: 'Prerender';
|
||||
public expiration: number;
|
||||
public lambda: Lambda;
|
||||
public fallback: FileBlob | FileFsRef | FileRef;
|
||||
public group?: number;
|
||||
|
||||
constructor({ expiration, lambda, fallback, group }: PrerenderOptions) {
|
||||
this.type = 'Prerender';
|
||||
this.expiration = expiration;
|
||||
this.lambda = lambda;
|
||||
this.fallback = fallback;
|
||||
|
||||
if (
|
||||
typeof group !== 'undefined' &&
|
||||
(group <= 0 || !Number.isInteger(group))
|
||||
) {
|
||||
throw new Error('The `group` argument for `Prerender` needs to be a natural number.');
|
||||
}
|
||||
|
||||
this.group = group;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
import FileRef from './file-ref';
|
||||
import FileFsRef from './file-fs-ref';
|
||||
|
||||
export interface Env {
|
||||
[name: string]: string | undefined;
|
||||
}
|
||||
|
||||
export interface File {
|
||||
type: string;
|
||||
mode: number;
|
||||
@@ -52,6 +56,8 @@ export interface Meta {
|
||||
requestPath?: string;
|
||||
filesChanged?: string[];
|
||||
filesRemoved?: string[];
|
||||
env?: Env;
|
||||
buildEnv?: Env;
|
||||
}
|
||||
|
||||
export interface AnalyzeOptions {
|
||||
@@ -185,8 +191,8 @@ export interface ShouldServeOptions {
|
||||
}
|
||||
|
||||
export interface PackageJson {
|
||||
name: string;
|
||||
version: string;
|
||||
name?: string;
|
||||
version?: string;
|
||||
engines?: {
|
||||
[key: string]: string;
|
||||
node: string;
|
||||
|
||||
59
packages/now-build-utils/test/test.js
vendored
59
packages/now-build-utils/test/test.js
vendored
@@ -1,13 +1,9 @@
|
||||
/* global beforeAll, expect, it, jest */
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const execa = require('execa');
|
||||
const assert = require('assert');
|
||||
const { createZip } = require('../dist/lambda');
|
||||
const {
|
||||
glob, download, detectBuilders, detectRoutes,
|
||||
} = require('../');
|
||||
const { glob, download, detectBuilders, detectRoutes } = require('../');
|
||||
const {
|
||||
getSupportedNodeVersion,
|
||||
defaultSelection,
|
||||
@@ -86,33 +82,33 @@ it('should match all semver ranges', () => {
|
||||
// See https://docs.npmjs.com/files/package.json#engines
|
||||
expect(getSupportedNodeVersion('10.0.0')).resolves.toHaveProperty(
|
||||
'major',
|
||||
10,
|
||||
10
|
||||
);
|
||||
expect(getSupportedNodeVersion('10.x')).resolves.toHaveProperty('major', 10);
|
||||
expect(getSupportedNodeVersion('>=10')).resolves.toHaveProperty('major', 10);
|
||||
expect(getSupportedNodeVersion('>=10.3.0')).resolves.toHaveProperty(
|
||||
'major',
|
||||
10,
|
||||
10
|
||||
);
|
||||
expect(getSupportedNodeVersion('8.5.0 - 10.5.0')).resolves.toHaveProperty(
|
||||
'major',
|
||||
10,
|
||||
10
|
||||
);
|
||||
expect(getSupportedNodeVersion('>=9.0.0')).resolves.toHaveProperty(
|
||||
'major',
|
||||
10,
|
||||
10
|
||||
);
|
||||
expect(getSupportedNodeVersion('>=9.5.0 <=10.5.0')).resolves.toHaveProperty(
|
||||
'major',
|
||||
10,
|
||||
10
|
||||
);
|
||||
expect(getSupportedNodeVersion('~10.5.0')).resolves.toHaveProperty(
|
||||
'major',
|
||||
10,
|
||||
10
|
||||
);
|
||||
expect(getSupportedNodeVersion('^10.5.0')).resolves.toHaveProperty(
|
||||
'major',
|
||||
10,
|
||||
10
|
||||
);
|
||||
});
|
||||
|
||||
@@ -162,8 +158,8 @@ for (const fixture of fs.readdirSync(fixturesPath)) {
|
||||
await expect(
|
||||
testDeployment(
|
||||
{ builderUrl, buildUtilsUrl },
|
||||
path.join(fixturesPath, fixture),
|
||||
),
|
||||
path.join(fixturesPath, fixture)
|
||||
)
|
||||
).resolves.toBeDefined();
|
||||
});
|
||||
}
|
||||
@@ -176,7 +172,7 @@ const buildersToTestWith = ['now-next', 'now-node', 'now-static-build'];
|
||||
for (const builder of buildersToTestWith) {
|
||||
const fixturesPath2 = path.resolve(
|
||||
__dirname,
|
||||
`../../${builder}/test/fixtures`,
|
||||
`../../${builder}/test/fixtures`
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
@@ -188,8 +184,8 @@ for (const builder of buildersToTestWith) {
|
||||
await expect(
|
||||
testDeployment(
|
||||
{ builderUrl, buildUtilsUrl },
|
||||
path.join(fixturesPath2, fixture),
|
||||
),
|
||||
path.join(fixturesPath2, fixture)
|
||||
)
|
||||
).resolves.toBeDefined();
|
||||
});
|
||||
}
|
||||
@@ -489,6 +485,23 @@ it('Test `detectBuilders`', async () => {
|
||||
expect(builders[2].use).toBe('@now/next@haha');
|
||||
expect(builders.length).toBe(3);
|
||||
}
|
||||
|
||||
{
|
||||
// next.js pages/api + api
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
};
|
||||
const files = ['api/user.js', 'pages/api/user.js'];
|
||||
|
||||
const { warnings, errors, builders } = await detectBuilders(files, pkg);
|
||||
expect(errors).toBe(null);
|
||||
expect(warnings[0].code).toBe('conflicting_files');
|
||||
expect(builders).toBeDefined();
|
||||
expect(builders.length).toBe(2);
|
||||
expect(builders[0].use).toBe('@now/node');
|
||||
expect(builders[1].use).toBe('@now/next');
|
||||
}
|
||||
});
|
||||
|
||||
it('Test `detectRoutes`', async () => {
|
||||
@@ -592,7 +605,7 @@ it('Test `detectRoutes`', async () => {
|
||||
|
||||
expect(defaultRoutes.length).toBe(3);
|
||||
expect(defaultRoutes[0].src).toBe(
|
||||
'^/api/date(\\/|\\/index|\\/index\\.js)?$',
|
||||
'^/api/date(\\/|\\/index|\\/index\\.js)?$'
|
||||
);
|
||||
expect(defaultRoutes[0].dest).toBe('/api/date/index.js');
|
||||
expect(defaultRoutes[1].src).toBe('^/api/(date|date\\.js)$');
|
||||
@@ -607,7 +620,7 @@ it('Test `detectRoutes`', async () => {
|
||||
|
||||
expect(defaultRoutes.length).toBe(3);
|
||||
expect(defaultRoutes[0].src).toBe(
|
||||
'^/api/([^\\/]+)(\\/|\\/index|\\/index\\.js)?$',
|
||||
'^/api/([^\\/]+)(\\/|\\/index|\\/index\\.js)?$'
|
||||
);
|
||||
expect(defaultRoutes[0].dest).toBe('/api/[date]/index.js?date=$1');
|
||||
expect(defaultRoutes[1].src).toBe('^/api/(date|date\\.js)$');
|
||||
@@ -686,12 +699,12 @@ it('Test `detectBuilders` and `detectRoutes`', async () => {
|
||||
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
||||
await fs.writeFile(
|
||||
path.join(fixture, 'now.json'),
|
||||
JSON.stringify(nowConfig, null, 2),
|
||||
JSON.stringify(nowConfig, null, 2)
|
||||
);
|
||||
|
||||
const deployment = await testDeployment(
|
||||
{ builderUrl, buildUtilsUrl },
|
||||
fixture,
|
||||
fixture
|
||||
);
|
||||
expect(deployment).toBeDefined();
|
||||
});
|
||||
@@ -768,12 +781,12 @@ it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
|
||||
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
||||
await fs.writeFile(
|
||||
path.join(fixture, 'now.json'),
|
||||
JSON.stringify(nowConfig, null, 2),
|
||||
JSON.stringify(nowConfig, null, 2)
|
||||
);
|
||||
|
||||
const deployment = await testDeployment(
|
||||
{ builderUrl, buildUtilsUrl },
|
||||
fixture,
|
||||
fixture
|
||||
);
|
||||
expect(deployment).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{*.json,*.json.example,*.gyp,*.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Ideal settings - some plugins might support these.
|
||||
[*.js]
|
||||
quote_type = single
|
||||
|
||||
[{*.c,*.cc,*.h,*.hh,*.cpp,*.hpp,*.m,*.mm,*.mpp,*.js,*.java,*.go,*.rs,*.php,*.ng,*.jsx,*.ts,*.d,*.cs,*.swift}]
|
||||
curly_bracket_next_line = false
|
||||
spaces_around_operators = true
|
||||
spaces_around_brackets = outside
|
||||
# close enough to 1TB
|
||||
indent_brace_style = K&R
|
||||
1
packages/now-cgi/.gitignore
vendored
1
packages/now-cgi/.gitignore
vendored
@@ -1,2 +1 @@
|
||||
node_modules
|
||||
handler
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
const path = require('path');
|
||||
const { mkdirp, copyFile } = require('fs-extra');
|
||||
|
||||
const glob = require('@now/build-utils/fs/glob'); // eslint-disable-line import/no-extraneous-dependencies
|
||||
const download = require('@now/build-utils/fs/download'); // eslint-disable-line import/no-extraneous-dependencies
|
||||
const { createLambda } = require('@now/build-utils/lambda'); // eslint-disable-line import/no-extraneous-dependencies
|
||||
const getWritableDirectory = require('@now/build-utils/fs/get-writable-directory'); // eslint-disable-line import/no-extraneous-dependencies
|
||||
const { shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
|
||||
const glob = require('@now/build-utils/fs/glob');
|
||||
const download = require('@now/build-utils/fs/download');
|
||||
const { createLambda } = require('@now/build-utils/lambda');
|
||||
const getWritableDirectory = require('@now/build-utils/fs/get-writable-directory');
|
||||
const { shouldServe } = require('@now/build-utils');
|
||||
|
||||
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
|
||||
|
||||
exports.build = async ({
|
||||
workPath, files, entrypoint, meta,
|
||||
}) => {
|
||||
exports.build = async ({ workPath, files, entrypoint, meta }) => {
|
||||
console.log('downloading files...');
|
||||
const outDir = await getWritableDirectory();
|
||||
|
||||
@@ -26,7 +24,7 @@ exports.build = async ({
|
||||
// For now only the entrypoint file is copied into the lambda
|
||||
await copyFile(
|
||||
path.join(workPath, entrypoint),
|
||||
path.join(outDir, entrypoint),
|
||||
path.join(outDir, entrypoint)
|
||||
);
|
||||
|
||||
const lambda = await createLambda({
|
||||
@@ -34,12 +32,12 @@ exports.build = async ({
|
||||
handler: 'handler',
|
||||
runtime: 'go1.x',
|
||||
environment: {
|
||||
SCRIPT_FILENAME: entrypoint,
|
||||
},
|
||||
SCRIPT_FILENAME: entrypoint
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
[entrypoint]: lambda,
|
||||
[entrypoint]: lambda
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/cgi",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
@types
|
||||
download
|
||||
dist
|
||||
test/fixtures
|
||||
test/dev/fixtures
|
||||
bin
|
||||
link
|
||||
src/util/dev/templates/*.ts
|
||||
@@ -1,86 +0,0 @@
|
||||
module.exports = {
|
||||
'extends': [
|
||||
'airbnb',
|
||||
'prettier'
|
||||
],
|
||||
'parser': '@typescript-eslint/parser',
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 2018,
|
||||
'sourceType': 'module',
|
||||
'modules': true
|
||||
},
|
||||
'plugins': [
|
||||
'@typescript-eslint'
|
||||
],
|
||||
'settings': {
|
||||
'import/resolver': {
|
||||
'typescript': {
|
||||
}
|
||||
}
|
||||
},
|
||||
'rules': {
|
||||
'quotes': [
|
||||
2,
|
||||
'single',
|
||||
{
|
||||
'allowTemplateLiterals': true
|
||||
}
|
||||
],
|
||||
'class-methods-use-this': 0,
|
||||
'consistent-return': 0,
|
||||
'func-names': 0,
|
||||
'global-require': 0,
|
||||
'guard-for-in': 0,
|
||||
'import/no-duplicates': 0,
|
||||
'import/no-dynamic-require': 0,
|
||||
'import/no-extraneous-dependencies': 0,
|
||||
'import/prefer-default-export': 0,
|
||||
'lines-between-class-members': 0,
|
||||
'no-await-in-loop': 0,
|
||||
'no-bitwise': 0,
|
||||
'no-console': 0,
|
||||
'no-continue': 0,
|
||||
'no-control-regex': 0,
|
||||
'no-empty': 0,
|
||||
'no-loop-func': 0,
|
||||
'no-nested-ternary': 0,
|
||||
'no-param-reassign': 0,
|
||||
'no-plusplus': 0,
|
||||
'no-restricted-globals': 0,
|
||||
'no-restricted-syntax': 0,
|
||||
'no-shadow': 0,
|
||||
'no-underscore-dangle': 0,
|
||||
'no-use-before-define': 0,
|
||||
'prefer-const': 0,
|
||||
'prefer-destructuring': 0,
|
||||
'camelcase': 0,
|
||||
'no-unused-vars': 0, // in favor of '@typescript-eslint/no-unused-vars'
|
||||
// 'indent': 0 // in favor of '@typescript-eslint/indent'
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
// '@typescript-eslint/indent': ['error', 2] // this might conflict with a lot ongoing changes
|
||||
'@typescript-eslint/no-array-constructor': 'error',
|
||||
'@typescript-eslint/adjacent-overload-signatures': 'error',
|
||||
'@typescript-eslint/class-name-casing': 'error',
|
||||
'@typescript-eslint/interface-name-prefix': 'error',
|
||||
'@typescript-eslint/no-empty-interface': 'error',
|
||||
'@typescript-eslint/no-inferrable-types': 'error',
|
||||
'@typescript-eslint/no-misused-new': 'error',
|
||||
'@typescript-eslint/no-namespace': 'error',
|
||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||
'@typescript-eslint/no-parameter-properties': 'error',
|
||||
'@typescript-eslint/no-triple-slash-reference': 'error',
|
||||
'@typescript-eslint/prefer-namespace-keyword': 'error',
|
||||
'@typescript-eslint/type-annotation-spacing': 'error',
|
||||
// '@typescript-eslint/array-type': 'error',
|
||||
// '@typescript-eslint/ban-types': 'error',
|
||||
// '@typescript-eslint/explicit-function-return-type': 'warn',
|
||||
// '@typescript-eslint/explicit-member-accessibility': 'error',
|
||||
// '@typescript-eslint/member-delimiter-style': 'error',
|
||||
// '@typescript-eslint/no-angle-bracket-type-assertion': 'error',
|
||||
// '@typescript-eslint/no-explicit-any': 'warn',
|
||||
// '@typescript-eslint/no-object-literal-type-assertion': 'error',
|
||||
// '@typescript-eslint/no-use-before-define': 'error',
|
||||
// '@typescript-eslint/no-var-requires': 'error',
|
||||
// '@typescript-eslint/prefer-interface': 'error'
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
declare module 'cache-or-tmp-directory' {
|
||||
export default function (appName: string) : string | null
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
declare module 'pcre-to-regexp' {
|
||||
export default function (pattern: string, keys?: string[]): RegExp
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now",
|
||||
"version": "16.1.3",
|
||||
"version": "16.3.1",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Now",
|
||||
@@ -11,17 +11,15 @@
|
||||
"directory": "packages/now-cli"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "yarn test-lint",
|
||||
"preinstall": "node ./scripts/preinstall.js",
|
||||
"test-unit": "nyc ava test/*unit.js --serial --fail-fast --verbose",
|
||||
"test-integration": "ava test/integration.js --serial --fail-fast",
|
||||
"test-integration-now-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
|
||||
"test-lint": "eslint . --ext .js,.ts",
|
||||
"prepublishOnly": "yarn build",
|
||||
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
|
||||
"build": "ts-node ./scripts/build.ts",
|
||||
"build-dev": "ts-node ./scripts/build.ts --dev",
|
||||
"format-modified": "prettier --parser typescript --write --single-quote `git diff --name-only HEAD * | grep -e \".*\\.ts$\" -e \".*\\.js$\" | xargs echo`"
|
||||
"test-lint": "eslint . --ext .ts,.js --ignore-path ../../.eslintignore"
|
||||
},
|
||||
"nyc": {
|
||||
"include": [
|
||||
@@ -42,12 +40,6 @@
|
||||
"instrument": true,
|
||||
"all": true
|
||||
},
|
||||
"git": {
|
||||
"pre-commit": [
|
||||
"test-lint",
|
||||
"format-modified"
|
||||
]
|
||||
},
|
||||
"bin": {
|
||||
"now": "./dist/index.js"
|
||||
},
|
||||
@@ -69,13 +61,6 @@
|
||||
"node": ">= 8.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@now/build-utils": "0.9.14",
|
||||
"@now/go": "latest",
|
||||
"@now/next": "latest",
|
||||
"@now/node": "latest",
|
||||
"@now/php": "latest",
|
||||
"@now/routing-utils": "1.2.3",
|
||||
"@now/static-build": "latest",
|
||||
"@sentry/node": "5.5.0",
|
||||
"@types/ansi-escapes": "3.0.0",
|
||||
"@types/ansi-regex": "4.0.0",
|
||||
@@ -106,11 +91,8 @@
|
||||
"@types/universal-analytics": "0.4.2",
|
||||
"@types/which": "1.3.1",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "1.6.0",
|
||||
"@typescript-eslint/parser": "1.1.0",
|
||||
"@zeit/dockerignore": "0.0.5",
|
||||
"@zeit/fun": "0.9.1",
|
||||
"@zeit/git-hooks": "0.1.4",
|
||||
"@zeit/fun": "0.10.2",
|
||||
"@zeit/ncc": "0.18.5",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
"ajv": "6.10.2",
|
||||
@@ -123,7 +105,6 @@
|
||||
"async-sema": "2.1.4",
|
||||
"ava": "2.2.0",
|
||||
"bytes": "3.0.0",
|
||||
"cache-or-tmp-directory": "1.0.0",
|
||||
"chalk": "2.4.2",
|
||||
"chokidar": "2.1.6",
|
||||
"clipboardy": "2.1.0",
|
||||
@@ -142,19 +123,12 @@
|
||||
"email-validator": "1.1.1",
|
||||
"epipebomb": "1.0.0",
|
||||
"escape-html": "1.0.3",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-airbnb": "17.1.0",
|
||||
"eslint-config-prettier": "4.1.0",
|
||||
"eslint-import-resolver-typescript": "1.1.1",
|
||||
"eslint-plugin-import": "2.16.0",
|
||||
"eslint-plugin-jsx-a11y": "6.2.1",
|
||||
"eslint-plugin-react": "7.12.4",
|
||||
"esm": "3.1.4",
|
||||
"execa": "1.0.0",
|
||||
"fetch-h2": "2.0.3",
|
||||
"fs-extra": "7.0.1",
|
||||
"glob": "7.1.2",
|
||||
"http-proxy": "1.17.0",
|
||||
"ignore": "4.0.6",
|
||||
"ini": "1.3.4",
|
||||
"inquirer": "3.3.0",
|
||||
"is-url": "1.2.2",
|
||||
@@ -165,15 +139,15 @@
|
||||
"mime-types": "2.1.24",
|
||||
"minimatch": "3.0.4",
|
||||
"mri": "1.1.0",
|
||||
"ms": "2.1.1",
|
||||
"ms": "2.1.2",
|
||||
"node-fetch": "1.7.3",
|
||||
"now-client": "./packages/now-client",
|
||||
"npm-package-arg": "6.1.0",
|
||||
"nyc": "13.2.0",
|
||||
"ora": "3.4.0",
|
||||
"pcre-to-regexp": "0.0.5",
|
||||
"pcre-to-regexp": "1.0.0",
|
||||
"pluralize": "7.0.0",
|
||||
"pre-commit": "1.2.2",
|
||||
"prettier": "1.16.2",
|
||||
"printf": "0.2.5",
|
||||
"progress": "2.0.3",
|
||||
"promisepipe": "3.0.0",
|
||||
|
||||
@@ -8,16 +8,13 @@ import { createWriteStream, mkdirp, remove, writeJSON } from 'fs-extra';
|
||||
|
||||
import { getDistTag } from '../src/util/get-dist-tag';
|
||||
import pkg from '../package.json';
|
||||
import { getBundledBuilders } from '../src/util/dev/get-bundled-builders';
|
||||
|
||||
const dirRoot = join(__dirname, '..');
|
||||
|
||||
const bundledBuilders = Object.keys(pkg.devDependencies).filter(d =>
|
||||
d.startsWith('@now/')
|
||||
);
|
||||
|
||||
async function createBuildersTarball() {
|
||||
const distTag = getDistTag(pkg.version);
|
||||
const builders = Array.from(bundledBuilders).map(b => `${b}@${distTag}`);
|
||||
const builders = Array.from(getBundledBuilders()).map(b => `${b}@${distTag}`);
|
||||
console.log(`Creating builders tarball with: ${builders.join(', ')}`);
|
||||
|
||||
const buildersDir = join(dirRoot, '.builders');
|
||||
@@ -39,7 +36,7 @@ async function createBuildersTarball() {
|
||||
const yarn = join(dirRoot, '../../node_modules/yarn/bin/yarn.js');
|
||||
await execa(process.execPath, [yarn, 'add', '--no-lockfile', ...builders], {
|
||||
cwd: buildersDir,
|
||||
stdio: 'inherit'
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
const packer = tar.pack(buildersDir);
|
||||
@@ -66,7 +63,7 @@ async function main() {
|
||||
// Compile the `doT.js` template files for `now dev`
|
||||
console.log();
|
||||
await execa(process.execPath, [join(__dirname, 'compile-templates.js')], {
|
||||
stdio: 'inherit'
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
// Do the initial `ncc` build
|
||||
@@ -92,20 +89,22 @@ async function main() {
|
||||
// get compiled into the final ncc bundle file, however, we want them to be
|
||||
// present in the npm package because the contents of those files are involved
|
||||
// with `fun`'s cache invalidation mechanism and they need to be shasum'd.
|
||||
const runtimes = join(dirRoot, '../../node_modules/@zeit/fun/dist/src/runtimes');
|
||||
const runtimes = join(
|
||||
dirRoot,
|
||||
'../../node_modules/@zeit/fun/dist/src/runtimes'
|
||||
);
|
||||
const dest = join(dirRoot, 'dist/runtimes');
|
||||
await cpy('**/*', dest, { parents: true, cwd: runtimes });
|
||||
|
||||
console.log('Finished building `now-cli`');
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (err: any) => {
|
||||
console.error('Unhandled Rejection:');
|
||||
console.error(err);
|
||||
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
|
||||
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (err: any) => {
|
||||
process.on('uncaughtException', err => {
|
||||
console.error('Uncaught Exception:');
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
|
||||
@@ -11,7 +11,10 @@ import strlen from '../../util/strlen.ts';
|
||||
import wait from '../../util/output/wait';
|
||||
|
||||
export default async function ls(ctx, opts, args, output) {
|
||||
const { authConfig: { token }, config } = ctx;
|
||||
const {
|
||||
authConfig: { token },
|
||||
config
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
const { '--debug': debugEnabled } = opts;
|
||||
@@ -48,15 +51,13 @@ export default async function ls(ctx, opts, args, output) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!opts['--json']) {
|
||||
cancelWait = wait(
|
||||
args[0]
|
||||
? `Fetching alias details for "${args[0]}" under ${chalk.bold(
|
||||
contextName
|
||||
)}`
|
||||
: `Fetching aliases under ${chalk.bold(contextName)}`
|
||||
);
|
||||
}
|
||||
cancelWait = wait(
|
||||
args[0]
|
||||
? `Fetching alias details for "${args[0]}" under ${chalk.bold(
|
||||
contextName
|
||||
)}`
|
||||
: `Fetching aliases under ${chalk.bold(contextName)}`
|
||||
);
|
||||
|
||||
const aliases = await getAliases(now);
|
||||
if (cancelWait) cancelWait();
|
||||
@@ -72,7 +73,7 @@ export default async function ls(ctx, opts, args, output) {
|
||||
}
|
||||
|
||||
if (opts['--json']) {
|
||||
output.print(JSON.stringify({ rules: alias.rules }, null, 2));
|
||||
console.log(JSON.stringify({ rules: alias.rules }, null, 2));
|
||||
} else {
|
||||
const rules = alias.rules || [];
|
||||
output.log(
|
||||
@@ -105,11 +106,11 @@ function printAliasTable(aliases) {
|
||||
a.rules && a.rules.length
|
||||
? chalk.cyan(`[${plural('rule', a.rules.length, true)}]`)
|
||||
: // for legacy reasons, we might have situations
|
||||
// where the deployment was deleted and the alias
|
||||
// not collected appropriately, and we need to handle it
|
||||
a.deployment && a.deployment.url
|
||||
? a.deployment.url
|
||||
: chalk.gray('–'),
|
||||
// where the deployment was deleted and the alias
|
||||
// not collected appropriately, and we need to handle it
|
||||
a.deployment && a.deployment.url
|
||||
? a.deployment.url
|
||||
: chalk.gray('–'),
|
||||
a.alias,
|
||||
ms(Date.now() - new Date(a.created))
|
||||
])
|
||||
|
||||
@@ -38,7 +38,7 @@ export default async function set(
|
||||
const {
|
||||
authConfig: { token },
|
||||
config,
|
||||
localConfig
|
||||
localConfig,
|
||||
} = ctx;
|
||||
|
||||
const { currentTeam } = config;
|
||||
@@ -48,14 +48,14 @@ export default async function set(
|
||||
const {
|
||||
'--debug': debugEnabled,
|
||||
'--no-verify': noVerify,
|
||||
'--rules': rulesPath
|
||||
'--rules': rulesPath,
|
||||
} = opts;
|
||||
|
||||
const client = new Client({
|
||||
apiUrl,
|
||||
token,
|
||||
currentTeam,
|
||||
debug: debugEnabled
|
||||
debug: debugEnabled,
|
||||
});
|
||||
let contextName = null;
|
||||
let user = null;
|
||||
@@ -79,12 +79,14 @@ export default async function set(
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!isValidName(args[0])) {
|
||||
output.error(`The provided argument "${args[0]}" is not a valid deployment`);
|
||||
if (args.length >= 1 && !isValidName(args[0])) {
|
||||
output.error(
|
||||
`The provided argument "${args[0]}" is not a valid deployment`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!isValidName(args[1])) {
|
||||
if (args.length >= 2 && !isValidName(args[1])) {
|
||||
output.error(`The provided argument "${args[1]}" is not a valid domain`);
|
||||
return 1;
|
||||
}
|
||||
@@ -212,6 +214,7 @@ export default async function set(
|
||||
for (const target of targets) {
|
||||
output.log(`Assigning alias ${target} to deployment ${deployment.url}`);
|
||||
|
||||
const isWildcard = isWildcardAlias(target);
|
||||
const record = await assignAlias(
|
||||
output,
|
||||
client,
|
||||
@@ -222,13 +225,14 @@ export default async function set(
|
||||
);
|
||||
const handleResult = handleSetupDomainError(
|
||||
output,
|
||||
handleCreateAliasError(output, record)
|
||||
handleCreateAliasError(output, record),
|
||||
isWildcard
|
||||
);
|
||||
if (handleResult === 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const prefix = isWildcardAlias(handleResult.alias) ? '' : 'https://';
|
||||
const prefix = isWildcard ? '' : 'https://';
|
||||
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} ${chalk.bold(
|
||||
@@ -246,10 +250,15 @@ 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) {
|
||||
const { nsVerification, txtVerification, domain } = error.meta;
|
||||
if (
|
||||
error instanceof ERRORS.DomainVerificationFailed ||
|
||||
error instanceof ERRORS.DomainNsNotVerifiedForWildcard
|
||||
) {
|
||||
const { nsVerification, domain } = error.meta;
|
||||
|
||||
output.error(
|
||||
`We could not alias since the domain ${domain} could not be verified due to the following reasons:\n`
|
||||
);
|
||||
@@ -265,25 +274,34 @@ function handleSetupDomainError<T>(
|
||||
{ extraSpace: ' ' }
|
||||
)}\n\n`
|
||||
);
|
||||
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`
|
||||
);
|
||||
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;
|
||||
}
|
||||
@@ -349,9 +367,7 @@ function handleSetupDomainError<T>(
|
||||
|
||||
if (error instanceof ERRORS.DomainPurchasePending) {
|
||||
output.error(
|
||||
`The domain ${
|
||||
error.meta.domain
|
||||
} is processing and will be available once the order is completed.`
|
||||
`The domain ${error.meta.domain} is processing and will be available once the order is completed.`
|
||||
);
|
||||
output.print(
|
||||
` An email will be sent upon completion so you can alias to your new domain.\n`
|
||||
@@ -467,9 +483,7 @@ function handleCreateAliasError<T>(
|
||||
}
|
||||
if (error instanceof ERRORS.ForbiddenScaleMinInstances) {
|
||||
output.error(
|
||||
`You can't scale to more than ${
|
||||
error.meta.max
|
||||
} min instances with your current plan.`
|
||||
`You can't scale to more than ${error.meta.max} min instances with your current plan.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -490,9 +504,7 @@ function handleCreateAliasError<T>(
|
||||
|
||||
if (error instanceof ERRORS.CertMissing) {
|
||||
output.error(
|
||||
`There is no certificate for the domain ${
|
||||
error.meta.domain
|
||||
} and it could not be created.`
|
||||
`There is no certificate for the domain ${error.meta.domain} and it could not be created.`
|
||||
);
|
||||
output.log(
|
||||
`Please generate a new certificate manually with ${cmd(
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
// @ts-ignore
|
||||
import Now from '../../util';
|
||||
import Client from '../../util/client.ts';
|
||||
import getScope from '../../util/get-scope.ts';
|
||||
import stamp from '../../util/output/stamp.ts';
|
||||
import Client from '../../util/client';
|
||||
import getScope from '../../util/get-scope';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import wait from '../../util/output/wait';
|
||||
import createCertFromFile from '../../util/certs/create-cert-from-file';
|
||||
import createCertForCns from '../../util/certs/create-cert-for-cns';
|
||||
import { NowContext } from '../../types';
|
||||
import { Output } from '../../util/output';
|
||||
|
||||
import {
|
||||
DomainPermissionDenied,
|
||||
@@ -14,7 +17,20 @@ import {
|
||||
} from '../../util/errors-ts';
|
||||
import handleCertError from '../../util/certs/handle-cert-error';
|
||||
|
||||
async function add(ctx, opts, args, output) {
|
||||
interface Options {
|
||||
'--overwrite'?: boolean;
|
||||
'--debug'?: boolean;
|
||||
'--crt'?: string;
|
||||
'--key'?: string;
|
||||
'--ca'?: string;
|
||||
}
|
||||
|
||||
async function add(
|
||||
ctx: NowContext,
|
||||
opts: Options,
|
||||
args: string[],
|
||||
output: Output
|
||||
): Promise<number> {
|
||||
const {
|
||||
authConfig: { token },
|
||||
config
|
||||
@@ -77,10 +93,12 @@ async function add(ctx, opts, args, output) {
|
||||
|
||||
// Create a custom certificate from the given file paths
|
||||
cert = await createCertFromFile(now, keyPath, crtPath, caPath, contextName);
|
||||
|
||||
if (cert instanceof InvalidCert) {
|
||||
output.error(`The provided certificate is not valid and can't be added.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cert instanceof DomainPermissionDenied) {
|
||||
output.error(
|
||||
`You don't have permissions over domain ${chalk.underline(
|
||||
@@ -97,6 +115,7 @@ async function add(ctx, opts, args, output) {
|
||||
'now certs issue <cn> <cns>'
|
||||
)} instead`
|
||||
);
|
||||
|
||||
if (args.length < 1) {
|
||||
output.error(
|
||||
`Invalid number of arguments to create a custom certificate entry. Usage:`
|
||||
@@ -107,34 +126,43 @@ async function add(ctx, opts, args, output) {
|
||||
}
|
||||
|
||||
// Create the certificate from the given array of CNs
|
||||
const cns = args.reduce((res, item) => [...res, ...item.split(',')], []);
|
||||
const cns = args.reduce<string[]>((res, item) => res.concat(item.split(',')), []);
|
||||
const cancelWait = wait(
|
||||
`Generating a certificate for ${chalk.bold(cns.join(', '))}`
|
||||
);
|
||||
|
||||
cert = await createCertForCns(now, cns, contextName);
|
||||
cancelWait();
|
||||
|
||||
const result = handleCertError(output, cert);
|
||||
if (result === 1) {
|
||||
return result
|
||||
}
|
||||
|
||||
if (cert instanceof DomainPermissionDenied) {
|
||||
output.error(
|
||||
`You don't have permissions over domain ${chalk.underline(
|
||||
cert.meta.domain
|
||||
)} under ${chalk.bold(cert.meta.context)}.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Print success message
|
||||
output.success(
|
||||
`Certificate entry for ${chalk.bold(
|
||||
cert.cns.join(', ')
|
||||
)} created ${addStamp()}`
|
||||
);
|
||||
const result = handleCertError(output, cert);
|
||||
|
||||
if (result === 1) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (cert instanceof DomainPermissionDenied) {
|
||||
output.error(
|
||||
`You don't have permissions over domain ${chalk.underline(
|
||||
cert.meta.domain
|
||||
)} under ${chalk.bold(cert.meta.context)}.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cert instanceof Error) {
|
||||
// All cert errors are handled above,
|
||||
// so this is only for typescript
|
||||
throw cert;
|
||||
} else {
|
||||
// Print success message
|
||||
output.success(
|
||||
`Certificate entry for ${chalk.bold(
|
||||
cert.cns.join(', ')
|
||||
)} created ${addStamp()}`
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
import chalk from 'chalk';
|
||||
|
||||
// @ts-ignore
|
||||
import { handleError } from '../../util/error';
|
||||
|
||||
import createOutput from '../../util/output';
|
||||
@@ -12,6 +12,7 @@ import add from './add';
|
||||
import issue from './issue';
|
||||
import ls from './ls';
|
||||
import rm from './rm';
|
||||
import { NowContext } from '../../types';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -71,7 +72,7 @@ const COMMAND_CONFIG = {
|
||||
rm: ['rm', 'remove']
|
||||
};
|
||||
|
||||
export default async function main(ctx) {
|
||||
export default async function main(ctx: NowContext) {
|
||||
let argv;
|
||||
|
||||
try {
|
||||
@@ -3,16 +3,29 @@ import ms from 'ms';
|
||||
import plural from 'pluralize';
|
||||
import psl from 'psl';
|
||||
import table from 'text-table';
|
||||
// @ts-ignore
|
||||
import Now from '../../util';
|
||||
import cmd from '../../util/output/cmd';
|
||||
import Client from '../../util/client.ts';
|
||||
import getScope from '../../util/get-scope.ts';
|
||||
import stamp from '../../util/output/stamp.ts';
|
||||
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-ts';
|
||||
import strlen from '../../util/strlen.ts';
|
||||
import strlen from '../../util/strlen';
|
||||
import { Output } from '../../util/output';
|
||||
import { NowContext, Cert } from '../../types';
|
||||
|
||||
async function ls(ctx, opts, args, output) {
|
||||
interface Options {
|
||||
'--debug'?: boolean;
|
||||
'--after'?: string;
|
||||
}
|
||||
|
||||
async function ls(
|
||||
ctx: NowContext,
|
||||
opts: Options,
|
||||
args: string[],
|
||||
output: Output
|
||||
): Promise<number> {
|
||||
const { authConfig: { token }, config } = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
@@ -32,7 +45,6 @@ async function ls(ctx, opts, args, output) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
const now = new Now({ apiUrl, token, debug, currentTeam });
|
||||
const lsStamp = stamp();
|
||||
|
||||
@@ -55,7 +67,6 @@ async function ls(ctx, opts, args, output) {
|
||||
throw certificates;
|
||||
}
|
||||
|
||||
const { uid: lastCert } = certificates[certificates.length - 1];
|
||||
const certs = sortByCn(certificates);
|
||||
|
||||
output.log(
|
||||
@@ -65,7 +76,8 @@ async function ls(ctx, opts, args, output) {
|
||||
);
|
||||
|
||||
if (certs.length >= 100) {
|
||||
output.note(`There may be more certificates that can be retrieved with ${cmd(`now ${process.argv.slice(2).join(' ')} --after=${lastCert}`)}.`);
|
||||
const { uid: lastCert } = certificates[certificates.length - 1];
|
||||
output.note(`There may be more certificates that can be retrieved with ${cmd(`now ${process.argv.slice(2).join(' ')} --after=${lastCert}`)}.\n`);
|
||||
}
|
||||
|
||||
if (certs.length > 0) {
|
||||
@@ -75,7 +87,7 @@ async function ls(ctx, opts, args, output) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function formatCertsTable(certsList) {
|
||||
function formatCertsTable(certsList: Cert[]) {
|
||||
return `${table(
|
||||
[formatCertsTableHead(), ...formatCertsTableBody(certsList)],
|
||||
{
|
||||
@@ -86,7 +98,7 @@ function formatCertsTable(certsList) {
|
||||
).replace(/^(.*)/gm, ' $1')}\n`;
|
||||
}
|
||||
|
||||
function formatCertsTableHead() {
|
||||
function formatCertsTableHead(): string[] {
|
||||
return [
|
||||
chalk.dim('id'),
|
||||
chalk.dim('cns'),
|
||||
@@ -96,15 +108,12 @@ function formatCertsTableHead() {
|
||||
];
|
||||
}
|
||||
|
||||
function formatCertsTableBody(certsList) {
|
||||
function formatCertsTableBody(certsList: Cert[]) {
|
||||
const now = new Date();
|
||||
return certsList.reduce(
|
||||
(result, cert) => [...result, ...formatCert(now, cert)],
|
||||
[]
|
||||
);
|
||||
return certsList.reduce<string[][]>((result, cert) => result.concat(formatCert(now, cert)), []);
|
||||
}
|
||||
|
||||
function formatCert(time, cert) {
|
||||
function formatCert(time: Date, cert: Cert) {
|
||||
return cert.cns.map(
|
||||
(cn, idx) =>
|
||||
idx === 0
|
||||
@@ -113,26 +122,26 @@ function formatCert(time, cert) {
|
||||
);
|
||||
}
|
||||
|
||||
function formatCertNonFirstCn(cn, multiple) {
|
||||
function formatCertNonFirstCn(cn: string, multiple: boolean): string[] {
|
||||
return ['', formatCertCn(cn, multiple), '', '', ''];
|
||||
}
|
||||
|
||||
function formatCertCn(cn, multiple) {
|
||||
function formatCertCn(cn: string, multiple: boolean) {
|
||||
return multiple ? `${chalk.gray('-')} ${chalk.bold(cn)}` : chalk.bold(cn);
|
||||
}
|
||||
|
||||
function formatCertFirstCn(time, cert, cn, multiple) {
|
||||
function formatCertFirstCn(time: Date, cert: Cert, cn: string, multiple: boolean): string[] {
|
||||
return [
|
||||
cert.uid,
|
||||
formatCertCn(cn, multiple),
|
||||
formatExpirationDate(new Date(cert.expiration)),
|
||||
cert.autoRenew ? 'yes' : 'no',
|
||||
chalk.gray(ms(time - new Date(cert.created)))
|
||||
chalk.gray(ms(time.getTime() - new Date(cert.created).getTime()))
|
||||
];
|
||||
}
|
||||
|
||||
function formatExpirationDate(date) {
|
||||
const diff = date - Date.now();
|
||||
function formatExpirationDate(date: Date) {
|
||||
const diff = date.getTime() - Date.now();
|
||||
return diff < 0
|
||||
? chalk.gray(`${ms(-diff)} ago`)
|
||||
: chalk.gray(`in ${ms(diff)}`);
|
||||
@@ -143,8 +152,8 @@ function formatExpirationDate(date) {
|
||||
* to 'wildcard' since that will allow psl get the root domain
|
||||
* properly to make the comparison.
|
||||
*/
|
||||
function sortByCn(certsList) {
|
||||
return certsList.concat().sort((a, b) => {
|
||||
function sortByCn(certsList: Cert[]) {
|
||||
return certsList.concat().sort((a: Cert, b: Cert) => {
|
||||
const domainA = psl.get(a.cns[0].replace('*', 'wildcard'));
|
||||
const domainB = psl.get(b.cns[0].replace('*', 'wildcard'));
|
||||
if (!domainA || !domainB) return 0;
|
||||
@@ -10,7 +10,9 @@ export const latestHelp = () => `
|
||||
|
||||
${chalk.dim('Basic')}
|
||||
|
||||
deploy [path] Performs a deployment ${chalk.bold('(default)')}
|
||||
deploy [path] Performs a deployment ${chalk.bold(
|
||||
'(default)'
|
||||
)}
|
||||
dev Start a local development server
|
||||
init [example] Initialize an example project
|
||||
ls | list [app] Lists deployments
|
||||
@@ -18,7 +20,6 @@ export const latestHelp = () => `
|
||||
login [email] Logs into your account or creates a new one
|
||||
logout Logs out of your account
|
||||
switch [scope] Switches between teams and your personal account
|
||||
update Updates Now CLI to the latest version
|
||||
help [cmd] Displays complete help for [cmd]
|
||||
|
||||
${chalk.dim('Advanced')}
|
||||
@@ -29,7 +30,6 @@ export const latestHelp = () => `
|
||||
certs [cmd] Manages your SSL certificates
|
||||
secrets [name] Manages your secret environment variables
|
||||
logs [url] Displays the logs for a deployment
|
||||
scale [args] Scales the instance count of a deployment
|
||||
teams Manages your teams
|
||||
whoami Shows the username of the currently logged in user
|
||||
|
||||
@@ -115,7 +115,7 @@ export const latestArgs = {
|
||||
'-e': '--env',
|
||||
'-b': '--build-env',
|
||||
'-C': '--no-clipboard',
|
||||
'-m': '--meta'
|
||||
'-m': '--meta',
|
||||
};
|
||||
|
||||
export const legacyArgsMri = {
|
||||
@@ -126,7 +126,7 @@ export const legacyArgsMri = {
|
||||
'meta',
|
||||
'session-affinity',
|
||||
'regions',
|
||||
'dotenv'
|
||||
'dotenv',
|
||||
],
|
||||
boolean: [
|
||||
'help',
|
||||
@@ -143,11 +143,11 @@ export const legacyArgsMri = {
|
||||
'public',
|
||||
'no-scale',
|
||||
'no-verify',
|
||||
'dotenv'
|
||||
'dotenv',
|
||||
],
|
||||
default: {
|
||||
C: false,
|
||||
clipboard: true
|
||||
clipboard: true,
|
||||
},
|
||||
alias: {
|
||||
env: 'e',
|
||||
@@ -164,8 +164,8 @@ export const legacyArgsMri = {
|
||||
'session-affinity': 'S',
|
||||
name: 'n',
|
||||
project: 'P',
|
||||
alias: 'a'
|
||||
}
|
||||
alias: 'a',
|
||||
},
|
||||
};
|
||||
|
||||
// The following arg parsing is simply to make it compatible
|
||||
|
||||
@@ -3,18 +3,14 @@ import bytes from 'bytes';
|
||||
import { write as copy } from 'clipboardy';
|
||||
import chalk from 'chalk';
|
||||
import title from 'title';
|
||||
import Progress from 'progress';
|
||||
import Client from '../../util/client';
|
||||
import wait from '../../util/output/wait';
|
||||
import { handleError } from '../../util/error';
|
||||
import getArgs from '../../util/get-args';
|
||||
import toHumanPath from '../../util/humanize-path';
|
||||
import Now from '../../util';
|
||||
import stamp from '../../util/output/stamp.ts';
|
||||
import { isReady, isDone, isFailed } from '../../util/build-state';
|
||||
import createDeploy from '../../util/deploy/create-deploy';
|
||||
import getDeploymentByIdOrHost from '../../util/deploy/get-deployment-by-id-or-host';
|
||||
import sleep from '../../util/sleep';
|
||||
import parseMeta from '../../util/parse-meta';
|
||||
import code from '../../util/output/code';
|
||||
import param from '../../util/output/param';
|
||||
@@ -36,12 +32,15 @@ import {
|
||||
AliasDomainConfigured,
|
||||
MissingBuildScript,
|
||||
ConflictingFilePath,
|
||||
ConflictingPathSegment
|
||||
ConflictingPathSegment,
|
||||
BuildError,
|
||||
NotDomainOwner,
|
||||
} from '../../util/errors-ts';
|
||||
import { SchemaValidationFailed } from '../../util/errors';
|
||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||
import handleCertError from '../../util/certs/handle-cert-error';
|
||||
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
|
||||
import shouldDeployDir from '../../util/deploy/should-deploy-dir';
|
||||
|
||||
const addProcessEnv = async (log, env) => {
|
||||
let val;
|
||||
@@ -72,11 +71,12 @@ const addProcessEnv = async (log, env) => {
|
||||
};
|
||||
|
||||
const deploymentErrorMsg = `Your deployment failed. Please retry later. More: https://err.sh/now/deployment-error`;
|
||||
const prepareAlias = input => isWildcardAlias(input) ? input : `https://${input}`;
|
||||
const prepareAlias = input =>
|
||||
isWildcardAlias(input) ? input : `https://${input}`;
|
||||
|
||||
const printDeploymentStatus = async (
|
||||
output,
|
||||
{ url, readyState, alias: aliasList, aliasError },
|
||||
{ readyState, alias: aliasList, aliasError },
|
||||
deployStamp,
|
||||
clipboardEnabled,
|
||||
localConfig,
|
||||
@@ -94,10 +94,18 @@ const printDeploymentStatus = async (
|
||||
const preparedAlias = prepareAlias(firstAlias);
|
||||
try {
|
||||
await copy(`https://${firstAlias}`);
|
||||
output.ready(`Deployed to ${chalk.bold(chalk.cyan(preparedAlias))} ${chalk.gray('[in clipboard]')} ${deployStamp()}`);
|
||||
output.ready(
|
||||
`Deployed to ${chalk.bold(
|
||||
chalk.cyan(preparedAlias)
|
||||
)} ${chalk.gray('[in clipboard]')} ${deployStamp()}`
|
||||
);
|
||||
} catch (err) {
|
||||
output.debug(`Error copying to clipboard: ${err}`);
|
||||
output.ready(`Deployed to ${chalk.bold(chalk.cyan(preparedAlias))} ${deployStamp()}`);
|
||||
output.ready(
|
||||
`Deployed to ${chalk.bold(
|
||||
chalk.cyan(preparedAlias)
|
||||
)} ${deployStamp()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -109,13 +117,17 @@ const printDeploymentStatus = async (
|
||||
|
||||
for (const alias of aliasList) {
|
||||
const index = aliasList.indexOf(alias);
|
||||
const isLast = index === (aliasList.length - 1);
|
||||
const isLast = index === aliasList.length - 1;
|
||||
const shouldCopy = matching ? alias === matching : isLast;
|
||||
|
||||
if (shouldCopy && clipboardEnabled) {
|
||||
try {
|
||||
await copy(`https://${alias}`);
|
||||
output.print(`- ${chalk.bold(chalk.cyan(prepareAlias(alias)))} ${chalk.gray('[in clipboard]')}\n`);
|
||||
output.print(
|
||||
`- ${chalk.bold(chalk.cyan(prepareAlias(alias)))} ${chalk.gray(
|
||||
'[in clipboard]'
|
||||
)}\n`
|
||||
);
|
||||
|
||||
continue;
|
||||
} catch (err) {
|
||||
@@ -138,20 +150,6 @@ const printDeploymentStatus = async (
|
||||
return 1;
|
||||
}
|
||||
|
||||
const failedBuilds = builds.filter(isFailed);
|
||||
const amount = failedBuilds.length;
|
||||
|
||||
if (amount > 0) {
|
||||
output.error('Build failed');
|
||||
output.error(
|
||||
`Check your logs at https://${url}/_logs or run ${code(
|
||||
`now logs ${url}`
|
||||
)}`
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
output.error(deploymentErrorMsg);
|
||||
return 1;
|
||||
};
|
||||
@@ -206,7 +204,15 @@ export default async function main(
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { apiUrl, authConfig: { token }, config: { currentTeam } } = ctx;
|
||||
if (!(await shouldDeployDir(argv._[0], output))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const {
|
||||
apiUrl,
|
||||
authConfig: { token },
|
||||
config: { currentTeam },
|
||||
} = ctx;
|
||||
const { log, debug, error, warn } = output;
|
||||
const paths = Object.keys(stats);
|
||||
const debugEnabled = argv['--debug'];
|
||||
@@ -236,7 +242,6 @@ export default async function main(
|
||||
parseMeta(argv['--meta'])
|
||||
);
|
||||
|
||||
let syncCount;
|
||||
let deployStamp = stamp();
|
||||
let deployment = null;
|
||||
|
||||
@@ -289,11 +294,15 @@ export default async function main(
|
||||
parseEnv(argv['--env'])
|
||||
);
|
||||
|
||||
// Enable debug mode for builders
|
||||
const buildDebugEnv = debugEnabled ? { NOW_BUILDER_DEBUG: '1' } : {};
|
||||
|
||||
// Merge build env out of `build.env` from now.json, and `--build-env` args
|
||||
const deploymentBuildEnv = Object.assign(
|
||||
{},
|
||||
parseEnv(localConfig.build && localConfig.build.env),
|
||||
parseEnv(argv['--build-env'])
|
||||
parseEnv(argv['--build-env']),
|
||||
buildDebugEnv
|
||||
);
|
||||
|
||||
// If there's any undefined values, then inherit them from this process
|
||||
@@ -313,33 +322,45 @@ export default async function main(
|
||||
|
||||
try {
|
||||
// $FlowFixMe
|
||||
const project = getProjectName({argv, nowConfig: localConfig, isFile, paths});
|
||||
const project = getProjectName({
|
||||
argv,
|
||||
nowConfig: localConfig,
|
||||
isFile,
|
||||
paths,
|
||||
});
|
||||
log(`Using project ${chalk.bold(project)}`);
|
||||
|
||||
const createArgs = {
|
||||
name: project,
|
||||
env: deploymentEnv,
|
||||
build: { env: deploymentBuildEnv },
|
||||
forceNew: argv['--force'],
|
||||
quiet,
|
||||
wantsPublic: argv['--public'] || localConfig.public,
|
||||
isFile,
|
||||
type: null,
|
||||
nowConfig: localConfig,
|
||||
regions,
|
||||
meta
|
||||
name: project,
|
||||
env: deploymentEnv,
|
||||
build: { env: deploymentBuildEnv },
|
||||
forceNew: argv['--force'],
|
||||
quiet,
|
||||
wantsPublic: argv['--public'] || localConfig.public,
|
||||
isFile,
|
||||
type: null,
|
||||
nowConfig: localConfig,
|
||||
regions,
|
||||
meta,
|
||||
deployStamp,
|
||||
};
|
||||
|
||||
if (argv['--target']) {
|
||||
const deprecatedTarget = argv['--target'];
|
||||
|
||||
if (!['staging', 'production'].includes(deprecatedTarget)) {
|
||||
error(`The specified ${param('--target')} ${code(deprecatedTarget)} is not valid`);
|
||||
error(
|
||||
`The specified ${param('--target')} ${code(
|
||||
deprecatedTarget
|
||||
)} is not valid`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (deprecatedTarget === 'production') {
|
||||
warn('We recommend using the much shorter `--prod` option instead of `--target production` (deprecated)');
|
||||
warn(
|
||||
'We recommend using the much shorter `--prod` option instead of `--target production` (deprecated)'
|
||||
);
|
||||
}
|
||||
|
||||
output.debug(`Setting target to ${deprecatedTarget}`);
|
||||
@@ -351,7 +372,7 @@ export default async function main(
|
||||
|
||||
deployStamp = stamp();
|
||||
|
||||
const firstDeployCall = await createDeploy(
|
||||
deployment = await createDeploy(
|
||||
output,
|
||||
now,
|
||||
contextName,
|
||||
@@ -360,13 +381,49 @@ export default async function main(
|
||||
ctx
|
||||
);
|
||||
|
||||
if (deployment instanceof NotDomainOwner) {
|
||||
output.error(deployment);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const deploymentResponse = handleCertError(
|
||||
output,
|
||||
await getDeploymentByIdOrHost(now, contextName, deployment.id, 'v9')
|
||||
);
|
||||
|
||||
if (deploymentResponse === 1) {
|
||||
return deploymentResponse;
|
||||
}
|
||||
|
||||
if (
|
||||
firstDeployCall instanceof DomainNotFound &&
|
||||
firstDeployCall.meta && firstDeployCall.meta.domain
|
||||
deploymentResponse instanceof DeploymentNotFound ||
|
||||
deploymentResponse instanceof DeploymentPermissionDenied ||
|
||||
deploymentResponse instanceof InvalidDeploymentId
|
||||
) {
|
||||
output.debug(`The domain ${
|
||||
firstDeployCall.meta.domain
|
||||
} was not found, trying to purchase it`);
|
||||
output.error(deploymentResponse.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (handleCertError(output, deployment) === 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (deployment === null) {
|
||||
error('Uploading failed. Please try again.');
|
||||
return 1;
|
||||
}
|
||||
} catch (err) {
|
||||
debug(`Error: ${err}\n${err.stack}`);
|
||||
|
||||
if (err instanceof NotDomainOwner) {
|
||||
output.error(err.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (err instanceof DomainNotFound && err.meta && err.meta.domain) {
|
||||
output.debug(
|
||||
`The domain ${err.meta.domain} was not found, trying to purchase it`
|
||||
);
|
||||
|
||||
const purchase = await purchaseDomainIfAvailable(
|
||||
output,
|
||||
@@ -374,16 +431,14 @@ export default async function main(
|
||||
apiUrl: ctx.apiUrl,
|
||||
token: ctx.authConfig.token,
|
||||
currentTeam: ctx.config.currentTeam,
|
||||
debug: debugEnabled
|
||||
debug: debugEnabled,
|
||||
}),
|
||||
firstDeployCall.meta.domain,
|
||||
err.meta.domain,
|
||||
contextName
|
||||
);
|
||||
|
||||
if (purchase === true) {
|
||||
output.success(`Successfully purchased the domain ${
|
||||
firstDeployCall.meta.domain
|
||||
}!`);
|
||||
output.success(`Successfully purchased the domain ${err.meta.domain}!`);
|
||||
|
||||
// We exit if the purchase is completed since
|
||||
// the domain verification can take some time
|
||||
@@ -391,7 +446,7 @@ export default async function main(
|
||||
}
|
||||
|
||||
if (purchase === false || purchase instanceof UserAborted) {
|
||||
handleCreateDeployError(output, firstDeployCall);
|
||||
handleCreateDeployError(output, deployment);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -399,120 +454,36 @@ export default async function main(
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (handleCertError(output, firstDeployCall) === 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (
|
||||
firstDeployCall instanceof DomainNotFound ||
|
||||
firstDeployCall instanceof DomainNotVerified ||
|
||||
firstDeployCall instanceof DomainPermissionDenied ||
|
||||
firstDeployCall instanceof DomainVerificationFailed ||
|
||||
firstDeployCall instanceof SchemaValidationFailed ||
|
||||
firstDeployCall instanceof InvalidDomain ||
|
||||
firstDeployCall instanceof DeploymentNotFound ||
|
||||
firstDeployCall instanceof BuildsRateLimited ||
|
||||
firstDeployCall instanceof DeploymentsRateLimited ||
|
||||
firstDeployCall instanceof AliasDomainConfigured ||
|
||||
firstDeployCall instanceof MissingBuildScript ||
|
||||
firstDeployCall instanceof ConflictingFilePath ||
|
||||
firstDeployCall instanceof ConflictingPathSegment
|
||||
err instanceof DomainNotFound ||
|
||||
err instanceof DomainNotVerified ||
|
||||
err instanceof NotDomainOwner ||
|
||||
err instanceof DomainPermissionDenied ||
|
||||
err instanceof DomainVerificationFailed ||
|
||||
err instanceof SchemaValidationFailed ||
|
||||
err instanceof InvalidDomain ||
|
||||
err instanceof DeploymentNotFound ||
|
||||
err instanceof BuildsRateLimited ||
|
||||
err instanceof DeploymentsRateLimited ||
|
||||
err instanceof AliasDomainConfigured ||
|
||||
err instanceof MissingBuildScript ||
|
||||
err instanceof ConflictingFilePath ||
|
||||
err instanceof ConflictingPathSegment
|
||||
) {
|
||||
handleCreateDeployError(output, firstDeployCall);
|
||||
handleCreateDeployError(output, err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
deployment = firstDeployCall;
|
||||
if (err instanceof BuildError) {
|
||||
output.error('Build failed');
|
||||
output.error(
|
||||
`Check your logs at ${now.url}/_logs or run ${code(
|
||||
`now logs ${now.url}`
|
||||
)}`
|
||||
);
|
||||
|
||||
if (now.syncFileCount > 0) {
|
||||
const uploadStamp = stamp();
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
if (now.syncFileCount !== now.fileCount) {
|
||||
debug(`Total files ${now.fileCount}, ${now.syncFileCount} changed`);
|
||||
}
|
||||
|
||||
const size = bytes(now.syncAmount);
|
||||
syncCount = `${now.syncFileCount} file${now.syncFileCount > 1
|
||||
? 's'
|
||||
: ''}`;
|
||||
const bar = new Progress(
|
||||
`${chalk.gray(
|
||||
'>'
|
||||
)} Upload [:bar] :percent :etas (${size}) [${syncCount}]`,
|
||||
{
|
||||
width: 20,
|
||||
complete: '=',
|
||||
incomplete: '',
|
||||
total: now.syncAmount,
|
||||
clear: true
|
||||
}
|
||||
);
|
||||
|
||||
now.upload({ scale: {} });
|
||||
|
||||
now.on('upload', ({ names, data }) => {
|
||||
debug(`Uploaded: ${names.join(' ')} (${bytes(data.length)})`);
|
||||
});
|
||||
|
||||
now.on('uploadProgress', progress => {
|
||||
bar.tick(progress);
|
||||
});
|
||||
|
||||
now.on('complete', resolve);
|
||||
|
||||
now.on('error', err => {
|
||||
error('Upload failed');
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
if (!quiet && syncCount) {
|
||||
log(`Synced ${syncCount} (${bytes(now.syncAmount)}) ${uploadStamp()}`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 4; i += 1) {
|
||||
deployStamp = stamp();
|
||||
const secondDeployCall = await createDeploy(
|
||||
output,
|
||||
now,
|
||||
contextName,
|
||||
paths,
|
||||
createArgs
|
||||
);
|
||||
|
||||
if (handleCertError(output, secondDeployCall) === 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (
|
||||
secondDeployCall instanceof DomainPermissionDenied ||
|
||||
secondDeployCall instanceof DomainVerificationFailed ||
|
||||
secondDeployCall instanceof SchemaValidationFailed ||
|
||||
secondDeployCall instanceof DeploymentNotFound ||
|
||||
secondDeployCall instanceof DeploymentsRateLimited ||
|
||||
secondDeployCall instanceof AliasDomainConfigured ||
|
||||
secondDeployCall instanceof MissingBuildScript ||
|
||||
secondDeployCall instanceof ConflictingFilePath ||
|
||||
secondDeployCall instanceof ConflictingPathSegment
|
||||
) {
|
||||
handleCreateDeployError(output, secondDeployCall);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (now.syncFileCount === 0) {
|
||||
deployment = secondDeployCall;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (deployment === null) {
|
||||
error('Uploading failed. Please try again.');
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
} catch (err) {
|
||||
debug(`Error: ${err}\n${err.stack}`);
|
||||
|
||||
if (err.keyword === 'additionalProperties' && err.dataPath === '.scale') {
|
||||
const { additionalProperty = '' } = err.params || {};
|
||||
@@ -531,114 +502,14 @@ export default async function main(
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { url } = now;
|
||||
|
||||
if (isTTY) {
|
||||
log(`${url} ${chalk.gray(`[v2]`)} ${deployStamp()}`);
|
||||
} else {
|
||||
process.stdout.write(url);
|
||||
}
|
||||
|
||||
// If an error occurred, we want to let it fall down to rendering
|
||||
// builds so the user can see in which build the error occurred.
|
||||
if (isReady(deployment)) {
|
||||
return printDeploymentStatus(output, deployment, deployStamp, !argv['--no-clipboard'], localConfig);
|
||||
}
|
||||
|
||||
const sleepingTime = ms('1.5s');
|
||||
const allBuildsTime = stamp();
|
||||
const times = {};
|
||||
const buildsUrl = `/v1/now/deployments/${deployment.id}/builds`;
|
||||
|
||||
let builds = [];
|
||||
let buildsCompleted = false;
|
||||
let buildSpinner = null;
|
||||
|
||||
let deploymentSpinner = null;
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
if (!buildsCompleted) {
|
||||
const { builds: freshBuilds } = await now.fetch(buildsUrl);
|
||||
|
||||
// If there are no builds, we need to exit.
|
||||
if (freshBuilds.length === 0 || freshBuilds.every(isDone)) {
|
||||
builds = freshBuilds;
|
||||
buildsCompleted = true;
|
||||
} else {
|
||||
for (const build of freshBuilds) {
|
||||
const id = build.id;
|
||||
const done = isDone(build);
|
||||
|
||||
if (times[id]) {
|
||||
if (done && typeof times[id] === 'function') {
|
||||
times[id] = times[id]();
|
||||
}
|
||||
} else {
|
||||
times[id] = done ? allBuildsTime() : stamp();
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON.stringify(builds) !== JSON.stringify(freshBuilds)) {
|
||||
builds = freshBuilds;
|
||||
|
||||
if (buildSpinner === null) {
|
||||
buildSpinner = wait('Building...');
|
||||
}
|
||||
|
||||
buildsCompleted = builds.every(isDone);
|
||||
|
||||
if (builds.some(isFailed)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const deploymentResponse = handleCertError(
|
||||
output,
|
||||
await getDeploymentByIdOrHost(now, contextName, deployment.id, 'v9')
|
||||
)
|
||||
|
||||
if (deploymentResponse === 1) {
|
||||
return deploymentResponse;
|
||||
}
|
||||
|
||||
if (
|
||||
deploymentResponse instanceof DeploymentNotFound ||
|
||||
deploymentResponse instanceof DeploymentPermissionDenied ||
|
||||
deploymentResponse instanceof InvalidDeploymentId
|
||||
) {
|
||||
output.error(deploymentResponse.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (isReady(deploymentResponse) || isFailed(deploymentResponse)) {
|
||||
deployment = deploymentResponse;
|
||||
|
||||
if (typeof deploymentSpinner === 'function') {
|
||||
// This stops it
|
||||
deploymentSpinner();
|
||||
}
|
||||
|
||||
break;
|
||||
} else if (!deploymentSpinner) {
|
||||
if (typeof buildSpinner === 'function') {
|
||||
buildSpinner();
|
||||
}
|
||||
|
||||
deploymentSpinner = wait('Finalizing...');
|
||||
}
|
||||
}
|
||||
|
||||
await sleep(sleepingTime);
|
||||
}
|
||||
|
||||
if (typeof buildSpinner === 'function') {
|
||||
buildSpinner();
|
||||
}
|
||||
|
||||
return printDeploymentStatus(output, deployment, deployStamp, !argv['--no-clipboard'], localConfig, builds);
|
||||
};
|
||||
return printDeploymentStatus(
|
||||
output,
|
||||
deployment,
|
||||
deployStamp,
|
||||
!argv['--no-clipboard'],
|
||||
localConfig
|
||||
);
|
||||
}
|
||||
|
||||
function handleCreateDeployError(output, error) {
|
||||
if (error instanceof InvalidDomain) {
|
||||
@@ -708,18 +579,20 @@ function handleCreateDeployError(output, error) {
|
||||
}
|
||||
if (error instanceof TooManyRequests) {
|
||||
output.error(
|
||||
`Too many requests detected for ${error.meta
|
||||
.api} API. Try again in ${ms(error.meta.retryAfter * 1000, {
|
||||
long: true
|
||||
})}.`
|
||||
`Too many requests detected for ${error.meta.api} API. Try again in ${ms(
|
||||
error.meta.retryAfter * 1000,
|
||||
{
|
||||
long: true,
|
||||
}
|
||||
)}.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
if (error instanceof DomainNotVerified) {
|
||||
output.error(
|
||||
`The domain used as an alias ${
|
||||
chalk.underline(error.meta.domain)
|
||||
} is not verified yet. Please verify it.`
|
||||
`The domain used as an alias ${chalk.underline(
|
||||
error.meta.domain
|
||||
)} is not verified yet. Please verify it.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -730,6 +603,7 @@ function handleCreateDeployError(output, error) {
|
||||
}
|
||||
if (
|
||||
error instanceof DeploymentNotFound ||
|
||||
error instanceof NotDomainOwner ||
|
||||
error instanceof DeploymentsRateLimited ||
|
||||
error instanceof AliasDomainConfigured ||
|
||||
error instanceof MissingBuildScript ||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { resolve, basename, join } from 'path';
|
||||
import { eraseLines } from 'ansi-escapes';
|
||||
// @ts-ignore
|
||||
import { write as copy } from 'clipboardy';
|
||||
import bytes from 'bytes';
|
||||
import chalk from 'chalk';
|
||||
import dotenv from 'dotenv';
|
||||
import fs from 'fs-extra';
|
||||
@@ -13,7 +12,6 @@ import ms from 'ms';
|
||||
// @ts-ignore
|
||||
import title from 'title';
|
||||
import plural from 'pluralize';
|
||||
import Progress from 'progress';
|
||||
// @ts-ignore
|
||||
import { handleError } from '../../util/error';
|
||||
import chars from '../../util/output/chars';
|
||||
@@ -34,19 +32,16 @@ import promptOptions from '../../util/prompt-options';
|
||||
// @ts-ignore
|
||||
import readMetaData from '../../util/read-metadata';
|
||||
import toHumanPath from '../../util/humanize-path';
|
||||
import combineAsyncGenerators from '../../util/combine-async-generators';
|
||||
// @ts-ignore
|
||||
import createDeploy from '../../util/deploy/create-deploy';
|
||||
import eventListenerToGenerator from '../../util/event-listener-to-generator';
|
||||
// @ts-ignore
|
||||
import formatLogCmd from '../../util/output/format-log-cmd';
|
||||
// @ts-ignore
|
||||
import formatLogOutput from '../../util/output/format-log-output';
|
||||
// @ts-ignore
|
||||
import getEventsStream from '../../util/deploy/get-events-stream';
|
||||
import shouldDeployDir from '../../util/deploy/should-deploy-dir';
|
||||
// @ts-ignore
|
||||
import getInstanceIndex from '../../util/deploy/get-instance-index';
|
||||
import getStateChangeFromPolling from '../../util/deploy/get-state-change-from-polling';
|
||||
import joinWords from '../../util/output/join-words';
|
||||
// @ts-ignore
|
||||
import normalizeRegionsList from '../../util/scale/normalize-regions-list';
|
||||
@@ -67,11 +62,12 @@ import {
|
||||
DomainVerificationFailed,
|
||||
TooManyRequests,
|
||||
VerifyScaleTimeout,
|
||||
DeploymentsRateLimited
|
||||
DeploymentsRateLimited,
|
||||
NotDomainOwner,
|
||||
} from '../../util/errors-ts';
|
||||
import {
|
||||
InvalidAllForScale,
|
||||
InvalidRegionOrDCForScale
|
||||
InvalidRegionOrDCForScale,
|
||||
} from '../../util/errors';
|
||||
import { SchemaValidationFailed } from '../../util/errors';
|
||||
import handleCertError from '../../util/certs/handle-cert-error';
|
||||
@@ -198,11 +194,10 @@ const promptForEnvFields = async (list: string[]) => {
|
||||
for (const field of list) {
|
||||
questions.push({
|
||||
name: field,
|
||||
message: field
|
||||
message: field,
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
require('../../util/input/patch-inquirer');
|
||||
|
||||
log('Please enter values for the following environment variables:');
|
||||
@@ -221,7 +216,7 @@ const promptForEnvFields = async (list: string[]) => {
|
||||
|
||||
async function canUseZeroConfig(cwd: string): Promise<boolean> {
|
||||
try {
|
||||
const pkg = (await readPackage(join(cwd, 'package.json')));
|
||||
const pkg = await readPackage(join(cwd, 'package.json'));
|
||||
|
||||
if (!pkg || pkg instanceof Error) {
|
||||
return false;
|
||||
@@ -251,7 +246,7 @@ async function canUseZeroConfig(cwd: string): Promise<boolean> {
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} catch(_) {}
|
||||
} catch (_) {}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -276,6 +271,10 @@ export default async function main(
|
||||
paths = [process.cwd()];
|
||||
}
|
||||
|
||||
if (!(await shouldDeployDir(argv._[0], output))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Options
|
||||
forceNew = argv.force;
|
||||
deploymentName = argv.name;
|
||||
@@ -297,15 +296,17 @@ export default async function main(
|
||||
quiet = !isTTY;
|
||||
({ log, error, note, debug, warn } = output);
|
||||
|
||||
const infoUrl = await canUseZeroConfig(paths[0])
|
||||
const infoUrl = (await canUseZeroConfig(paths[0]))
|
||||
? 'https://zeit.co/guides/migrate-to-zeit-now'
|
||||
: 'https://zeit.co/docs/v2/advanced/platform/changes-in-now-2-0'
|
||||
: 'https://zeit.co/docs/v2/advanced/platform/changes-in-now-2-0';
|
||||
|
||||
warn(`You are using an old version of the Now Platform. More: ${link(infoUrl)}`);
|
||||
warn(
|
||||
`You are using an old version of the Now Platform. More: ${link(infoUrl)}`
|
||||
);
|
||||
|
||||
const {
|
||||
authConfig: { token },
|
||||
config
|
||||
config,
|
||||
} = ctx;
|
||||
|
||||
try {
|
||||
@@ -315,7 +316,7 @@ export default async function main(
|
||||
token,
|
||||
config,
|
||||
firstRun: true,
|
||||
deploymentType: undefined
|
||||
deploymentType: undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
await stopDeployment(err);
|
||||
@@ -328,7 +329,7 @@ async function sync({
|
||||
token,
|
||||
config: { currentTeam },
|
||||
firstRun,
|
||||
deploymentType
|
||||
deploymentType,
|
||||
}: SyncOptions): Promise<void> {
|
||||
return new Promise(async (_resolve, reject) => {
|
||||
let deployStamp = stamp();
|
||||
@@ -477,7 +478,7 @@ async function sync({
|
||||
|
||||
// XXX: legacy
|
||||
deploymentType,
|
||||
sessionAffinity
|
||||
sessionAffinity,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -487,7 +488,7 @@ async function sync({
|
||||
meta,
|
||||
deploymentName,
|
||||
deploymentType,
|
||||
sessionAffinity
|
||||
sessionAffinity,
|
||||
} = await readMeta(
|
||||
paths[0],
|
||||
deploymentName,
|
||||
@@ -500,7 +501,7 @@ async function sync({
|
||||
'dockerfile_missing',
|
||||
'no_dockerfile_commands',
|
||||
'unsupported_deployment_type',
|
||||
'multiple_manifests'
|
||||
'multiple_manifests',
|
||||
];
|
||||
|
||||
if (
|
||||
@@ -538,7 +539,7 @@ async function sync({
|
||||
// Read scale and fail if we have both regions and scale
|
||||
if (regions.length > 0 && Object.keys(scaleFromConfig).length > 0) {
|
||||
error(
|
||||
'Can\'t set both `regions` and `scale` options simultaneously',
|
||||
"Can't set both `regions` and `scale` options simultaneously",
|
||||
'regions-and-scale-at-once'
|
||||
);
|
||||
await exit(1);
|
||||
@@ -549,9 +550,7 @@ async function sync({
|
||||
dcIds = normalizeRegionsList(regions);
|
||||
if (dcIds instanceof InvalidRegionOrDCForScale) {
|
||||
error(
|
||||
`The value "${
|
||||
dcIds.meta.regionOrDC
|
||||
}" is not a valid region or DC identifier`
|
||||
`The value "${dcIds.meta.regionOrDC}" is not a valid region or DC identifier`
|
||||
);
|
||||
await exit(1);
|
||||
return 1;
|
||||
@@ -566,7 +565,7 @@ async function sync({
|
||||
scale = dcIds.reduce(
|
||||
(result: DcScale, dcId: string) => ({
|
||||
...result,
|
||||
[dcId]: { min: 0, max: 1 }
|
||||
[dcId]: { min: 0, max: 1 },
|
||||
}),
|
||||
{}
|
||||
);
|
||||
@@ -662,8 +661,9 @@ async function sync({
|
||||
}
|
||||
|
||||
const hasSecrets = Object.keys(deploymentEnv).some(key =>
|
||||
deploymentEnv[key].startsWith('@')
|
||||
(deploymentEnv[key] || '').startsWith('@')
|
||||
);
|
||||
|
||||
const secretsPromise = hasSecrets ? now.listSecrets() : null;
|
||||
|
||||
const findSecret = async (uidOrName: string) => {
|
||||
@@ -755,15 +755,13 @@ async function sync({
|
||||
parseMeta(argv.meta)
|
||||
);
|
||||
|
||||
let syncCount;
|
||||
|
||||
try {
|
||||
meta.name = getProjectName({
|
||||
argv,
|
||||
nowConfig,
|
||||
isFile,
|
||||
paths,
|
||||
pre: meta.name
|
||||
pre: meta.name,
|
||||
});
|
||||
log(`Using project ${chalk.bold(meta.name)}`);
|
||||
const createArgs = Object.assign(
|
||||
@@ -777,13 +775,15 @@ async function sync({
|
||||
scale,
|
||||
wantsPublic,
|
||||
sessionAffinity,
|
||||
isFile
|
||||
isFile,
|
||||
nowConfig,
|
||||
deployStamp,
|
||||
},
|
||||
meta
|
||||
);
|
||||
|
||||
deployStamp = stamp();
|
||||
const firstDeployCall = await createDeploy(
|
||||
deployment = await createDeploy(
|
||||
output,
|
||||
now,
|
||||
contextName,
|
||||
@@ -791,118 +791,24 @@ async function sync({
|
||||
createArgs
|
||||
);
|
||||
|
||||
const handledResult = handleCertError(output, firstDeployCall);
|
||||
const handledResult = handleCertError(output, deployment);
|
||||
if (handledResult === 1) {
|
||||
return handledResult;
|
||||
}
|
||||
|
||||
if (
|
||||
firstDeployCall instanceof DomainNotFound ||
|
||||
firstDeployCall instanceof DomainPermissionDenied ||
|
||||
firstDeployCall instanceof DomainVerificationFailed ||
|
||||
firstDeployCall instanceof SchemaValidationFailed ||
|
||||
firstDeployCall instanceof DeploymentNotFound ||
|
||||
firstDeployCall instanceof DeploymentsRateLimited
|
||||
deployment instanceof DomainNotFound ||
|
||||
deployment instanceof NotDomainOwner ||
|
||||
deployment instanceof DomainPermissionDenied ||
|
||||
deployment instanceof DomainVerificationFailed ||
|
||||
deployment instanceof SchemaValidationFailed ||
|
||||
deployment instanceof DeploymentNotFound ||
|
||||
deployment instanceof DeploymentsRateLimited
|
||||
) {
|
||||
handleCreateDeployError(output, firstDeployCall);
|
||||
handleCreateDeployError(output, deployment);
|
||||
await exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
deployment = firstDeployCall;
|
||||
|
||||
if (now.syncFileCount > 0) {
|
||||
const uploadStamp = stamp();
|
||||
await new Promise(resolve => {
|
||||
if (now.syncFileCount !== now.fileCount) {
|
||||
debug(`Total files ${now.fileCount}, ${now.syncFileCount} changed`);
|
||||
}
|
||||
|
||||
const size = bytes(now.syncAmount);
|
||||
syncCount = `${now.syncFileCount} file${
|
||||
now.syncFileCount > 1 ? 's' : ''
|
||||
}`;
|
||||
const bar = new Progress(
|
||||
`${chalk.gray(
|
||||
'>'
|
||||
)} Upload [:bar] :percent :etas (${size}) [${syncCount}]`,
|
||||
{
|
||||
width: 20,
|
||||
complete: '=',
|
||||
incomplete: '',
|
||||
total: now.syncAmount,
|
||||
clear: true
|
||||
}
|
||||
);
|
||||
|
||||
now.upload({ scale });
|
||||
|
||||
now.on(
|
||||
'upload',
|
||||
({ names, data }: { names: string[]; data: Buffer }) => {
|
||||
debug(`Uploaded: ${names.join(' ')} (${bytes(data.length)})`);
|
||||
}
|
||||
);
|
||||
|
||||
now.on('uploadProgress', (progress: number) => {
|
||||
bar.tick(progress);
|
||||
});
|
||||
|
||||
now.on('complete', resolve);
|
||||
|
||||
now.on('error', (err: Error) => {
|
||||
error('Upload failed');
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
if (!quiet && syncCount) {
|
||||
log(
|
||||
`Synced ${syncCount} (${bytes(now.syncAmount)}) ${uploadStamp()}`
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 4; i += 1) {
|
||||
deployStamp = stamp();
|
||||
const secondDeployCall = await createDeploy(
|
||||
output,
|
||||
now,
|
||||
contextName,
|
||||
paths,
|
||||
createArgs
|
||||
);
|
||||
|
||||
const handledResult = handleCertError(output, secondDeployCall);
|
||||
if (handledResult === 1) {
|
||||
return handledResult;
|
||||
}
|
||||
|
||||
if (
|
||||
secondDeployCall instanceof DomainNotFound ||
|
||||
secondDeployCall instanceof DomainPermissionDenied ||
|
||||
secondDeployCall instanceof DomainVerificationFailed ||
|
||||
secondDeployCall instanceof SchemaValidationFailed ||
|
||||
secondDeployCall instanceof TooManyRequests ||
|
||||
secondDeployCall instanceof DeploymentNotFound ||
|
||||
secondDeployCall instanceof DeploymentsRateLimited
|
||||
) {
|
||||
handleCreateDeployError(output, secondDeployCall);
|
||||
await exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (now.syncFileCount === 0) {
|
||||
deployment = secondDeployCall;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (deployment === null) {
|
||||
error('Uploading failed. Please try again.');
|
||||
await exit(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code === 'plan_requires_public') {
|
||||
if (!wantsPublic) {
|
||||
@@ -915,7 +821,7 @@ async function sync({
|
||||
|
||||
if (isTTY) {
|
||||
proceed = await promptBool('Are you sure you want to proceed?', {
|
||||
trailing: eraseLines(1)
|
||||
trailing: eraseLines(1),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -955,10 +861,10 @@ async function sync({
|
||||
output,
|
||||
token,
|
||||
config: {
|
||||
currentTeam
|
||||
currentTeam,
|
||||
},
|
||||
firstRun: false,
|
||||
deploymentType
|
||||
deploymentType,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1003,8 +909,6 @@ async function sync({
|
||||
} else {
|
||||
log(`${chalk.bold(chalk.cyan(url))}${dcs} ${deployStamp()}`);
|
||||
}
|
||||
} else {
|
||||
process.stdout.write(url);
|
||||
}
|
||||
|
||||
if (deploymentType === 'static') {
|
||||
@@ -1023,96 +927,52 @@ async function sync({
|
||||
// Show build logs
|
||||
// (We have to add this check for flow but it will never happen)
|
||||
if (deployment !== null) {
|
||||
// If the created deployment is ready it was a deduping and we should exit
|
||||
if (deployment.readyState !== 'READY') {
|
||||
require('assert')(deployment); // mute linter
|
||||
const instanceIndex = getInstanceIndex();
|
||||
const eventsStream = await maybeGetEventsStream(now, deployment);
|
||||
const eventsGenerator = getEventsGenerator(
|
||||
const instanceIndex = getInstanceIndex();
|
||||
const eventsStream = await maybeGetEventsStream(now, deployment);
|
||||
|
||||
if (!noVerify) {
|
||||
output.log(
|
||||
`Verifying instantiation in ${joinWords(
|
||||
Object.keys(deployment.scale).map(dc => chalk.bold(dc))
|
||||
)}`
|
||||
);
|
||||
const verifyStamp = stamp();
|
||||
const verifyDCsGenerator = getVerifyDCsGenerator(
|
||||
output,
|
||||
now,
|
||||
contextName,
|
||||
deployment,
|
||||
eventsStream
|
||||
);
|
||||
|
||||
for await (const _event of eventsGenerator) {
|
||||
const event = _event as any;
|
||||
// Stop when the deployment is ready
|
||||
if (
|
||||
event.type === 'state-change' &&
|
||||
event.payload.value === 'READY'
|
||||
) {
|
||||
output.log(`Build completed`);
|
||||
break;
|
||||
}
|
||||
|
||||
// Stop then there is an error state
|
||||
if (
|
||||
event.type === 'state-change' &&
|
||||
event.payload.value === 'ERROR'
|
||||
) {
|
||||
output.error(`Build failed`);
|
||||
await exit(1);
|
||||
}
|
||||
|
||||
// For any relevant event we receive, print the result
|
||||
if (event.type === 'build-start') {
|
||||
output.log('Building…');
|
||||
} else if (event.type === 'command') {
|
||||
output.log(formatLogCmd(event.payload.text));
|
||||
} else if (event.type === 'stdout' || event.type === 'stderr') {
|
||||
formatLogOutput(event.payload.text).forEach((msg: string) =>
|
||||
output.log(msg)
|
||||
for await (const _dcOrEvent of verifyDCsGenerator) {
|
||||
const dcOrEvent = _dcOrEvent as any;
|
||||
if (dcOrEvent instanceof VerifyScaleTimeout) {
|
||||
output.error(
|
||||
`Instance verification timed out (${ms(dcOrEvent.meta.timeout)})`
|
||||
);
|
||||
output.log(
|
||||
'Read more: https://err.sh/now-cli/verification-timeout'
|
||||
);
|
||||
await exit(1);
|
||||
} else if (Array.isArray(dcOrEvent)) {
|
||||
const [dc, instances] = dcOrEvent;
|
||||
output.log(
|
||||
`${chalk.cyan(chars.tick)} Scaled ${plural(
|
||||
'instance',
|
||||
instances,
|
||||
true
|
||||
)} in ${chalk.bold(dc)} ${verifyStamp()}`
|
||||
);
|
||||
} else if (
|
||||
dcOrEvent &&
|
||||
(dcOrEvent.type === 'stdout' || dcOrEvent.type === 'stderr')
|
||||
) {
|
||||
const prefix = chalk.gray(
|
||||
`[${instanceIndex(dcOrEvent.payload.instanceId)}] `
|
||||
);
|
||||
formatLogOutput(dcOrEvent.payload.text, prefix).forEach(
|
||||
(msg: string) => output.log(msg)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!noVerify) {
|
||||
output.log(
|
||||
`Verifying instantiation in ${joinWords(
|
||||
Object.keys(deployment.scale).map(dc => chalk.bold(dc))
|
||||
)}`
|
||||
);
|
||||
const verifyStamp = stamp();
|
||||
const verifyDCsGenerator = getVerifyDCsGenerator(
|
||||
output,
|
||||
now,
|
||||
deployment,
|
||||
eventsStream
|
||||
);
|
||||
|
||||
for await (const _dcOrEvent of verifyDCsGenerator) {
|
||||
const dcOrEvent = _dcOrEvent as any;
|
||||
if (dcOrEvent instanceof VerifyScaleTimeout) {
|
||||
output.error(
|
||||
`Instance verification timed out (${ms(
|
||||
dcOrEvent.meta.timeout
|
||||
)})`
|
||||
);
|
||||
output.log(
|
||||
'Read more: https://err.sh/now/verification-timeout'
|
||||
);
|
||||
await exit(1);
|
||||
} else if (Array.isArray(dcOrEvent)) {
|
||||
const [dc, instances] = dcOrEvent;
|
||||
output.log(
|
||||
`${chalk.cyan(chars.tick)} Scaled ${plural(
|
||||
'instance',
|
||||
instances,
|
||||
true
|
||||
)} in ${chalk.bold(dc)} ${verifyStamp()}`
|
||||
);
|
||||
} else if (
|
||||
dcOrEvent &&
|
||||
(dcOrEvent.type === 'stdout' || dcOrEvent.type === 'stderr')
|
||||
) {
|
||||
const prefix = chalk.gray(
|
||||
`[${instanceIndex(dcOrEvent.payload.instanceId)}] `
|
||||
);
|
||||
formatLogOutput(dcOrEvent.payload.text, prefix).forEach(
|
||||
(msg: string) => output.log(msg)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1134,7 +994,7 @@ async function readMeta(
|
||||
deploymentType,
|
||||
deploymentName: _deploymentName,
|
||||
quiet: true,
|
||||
sessionAffinity: _sessionAffinity
|
||||
sessionAffinity: _sessionAffinity,
|
||||
});
|
||||
|
||||
if (!deploymentType) {
|
||||
@@ -1151,7 +1011,7 @@ async function readMeta(
|
||||
meta,
|
||||
deploymentName: _deploymentName,
|
||||
deploymentType,
|
||||
sessionAffinity: _sessionAffinity
|
||||
sessionAffinity: _sessionAffinity,
|
||||
};
|
||||
} catch (err) {
|
||||
if (isTTY && err.code === 'multiple_manifests') {
|
||||
@@ -1165,7 +1025,7 @@ async function readMeta(
|
||||
try {
|
||||
deploymentType = await promptOptions([
|
||||
['npm', `${chalk.bold('package.json')}\t${chalk.gray(' --npm')} `],
|
||||
['docker', `${chalk.bold('Dockerfile')}\t${chalk.gray('--docker')} `]
|
||||
['docker', `${chalk.bold('Dockerfile')}\t${chalk.gray('--docker')} `],
|
||||
]);
|
||||
} catch (_) {
|
||||
throw err;
|
||||
@@ -1191,35 +1051,13 @@ async function maybeGetEventsStream(now: Now, deployment: any) {
|
||||
try {
|
||||
return await getEventsStream(now, deployment.deploymentId, {
|
||||
direction: 'forward',
|
||||
follow: true
|
||||
follow: true,
|
||||
});
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getEventsGenerator(
|
||||
now: Now,
|
||||
contextName: string,
|
||||
deployment: any,
|
||||
eventsStream: any
|
||||
) {
|
||||
const stateChangeFromPollingGenerator = getStateChangeFromPolling(
|
||||
now,
|
||||
contextName,
|
||||
deployment.deploymentId,
|
||||
deployment.readyState
|
||||
);
|
||||
if (eventsStream !== null) {
|
||||
return combineAsyncGenerators(
|
||||
eventListenerToGenerator('data', eventsStream),
|
||||
stateChangeFromPollingGenerator
|
||||
);
|
||||
}
|
||||
|
||||
return stateChangeFromPollingGenerator;
|
||||
}
|
||||
|
||||
function getVerifyDCsGenerator(
|
||||
output: Output,
|
||||
now: Now,
|
||||
@@ -1229,7 +1067,7 @@ function getVerifyDCsGenerator(
|
||||
const verifyDeployment = verifyDeploymentScale(
|
||||
output,
|
||||
now,
|
||||
deployment.deploymentId,
|
||||
deployment.deploymentId || deployment.uid,
|
||||
deployment.scale
|
||||
);
|
||||
|
||||
@@ -1296,9 +1134,9 @@ function handleCreateDeployError(output: Output, error: Error) {
|
||||
output.error(
|
||||
`Failed to validate ${highlight(
|
||||
'now.json'
|
||||
)}: ${message}\nDocumentation: ${
|
||||
link('https://zeit.co/docs/v2/advanced/configuration')
|
||||
}`
|
||||
)}: ${message}\nDocumentation: ${link(
|
||||
'https://zeit.co/docs/v2/advanced/configuration'
|
||||
)}`
|
||||
);
|
||||
|
||||
return 1;
|
||||
@@ -1308,7 +1146,7 @@ function handleCreateDeployError(output: Output, error: Error) {
|
||||
`Too many requests detected for ${error.meta.api} API. Try again in ${ms(
|
||||
error.meta.retryAfter * 1000,
|
||||
{
|
||||
long: true
|
||||
long: true,
|
||||
}
|
||||
)}.`
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import { PackageJson } from '@now/build-utils';
|
||||
|
||||
import getArgs from '../../util/get-args';
|
||||
import getSubcommand from '../../util/get-subcommand';
|
||||
@@ -11,11 +12,10 @@ import logo from '../../util/output/logo';
|
||||
import cmd from '../../util/output/cmd';
|
||||
import dev from './dev';
|
||||
import readPackage from '../../util/read-package';
|
||||
import { Package } from '../../util/dev/types';
|
||||
import readConfig from '../../util/config/read-config';
|
||||
|
||||
const COMMAND_CONFIG = {
|
||||
dev: ['dev']
|
||||
dev: ['dev'],
|
||||
};
|
||||
|
||||
const help = () => {
|
||||
@@ -54,7 +54,7 @@ export default async function main(ctx: NowContext) {
|
||||
|
||||
// Deprecated
|
||||
'--port': Number,
|
||||
'-p': '--port'
|
||||
'-p': '--port',
|
||||
});
|
||||
const debug = argv['--debug'];
|
||||
args = getSubcommand(argv._.slice(1), COMMAND_CONFIG).args;
|
||||
@@ -90,7 +90,7 @@ export default async function main(ctx: NowContext) {
|
||||
const pkg = await readPackage(path.join(dir, 'package.json'));
|
||||
|
||||
if (pkg) {
|
||||
const { scripts } = pkg as Package;
|
||||
const { scripts } = pkg as PackageJson;
|
||||
|
||||
if (scripts && scripts.dev && /\bnow\b\W+\bdev\b/.test(scripts.dev)) {
|
||||
output.error(
|
||||
@@ -98,9 +98,7 @@ export default async function main(ctx: NowContext) {
|
||||
'package.json'
|
||||
)} must not contain ${cmd('now dev')}`
|
||||
);
|
||||
output.error(
|
||||
`More details: http://err.sh/now/now-dev-as-dev-script`
|
||||
);
|
||||
output.error(`More details: http://err.sh/now/now-dev-as-dev-script`);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export default async function verify(
|
||||
) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
config
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
@@ -122,16 +122,11 @@ export default async function verify(
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (result.txtVerifiedAt) {
|
||||
if (result.nsVerifiedAt) {
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
|
||||
domain.name
|
||||
)} was verified using DNS TXT record. ${verifyStamp()}`
|
||||
);
|
||||
output.print(
|
||||
` You can verify with nameservers too. Run ${cmd(
|
||||
`now domains inspect ${domain.name}`
|
||||
)} to find out the intended set.\n`
|
||||
)} was verified using nameservers. ${verifyStamp()}`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
@@ -139,7 +134,12 @@ export default async function verify(
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
|
||||
domain.name
|
||||
)} was verified using nameservers. ${verifyStamp()}`
|
||||
)} was verified using DNS TXT record. ${verifyStamp()}`
|
||||
);
|
||||
output.print(
|
||||
` You can verify with nameservers too. Run ${cmd(
|
||||
`now domains inspect ${domain.name}`
|
||||
)} to find out the intended set.\n`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export default async function main(ctx) {
|
||||
'--all': Boolean,
|
||||
'--meta': [String],
|
||||
'-a': '--all',
|
||||
'-m': '--meta'
|
||||
'-m': '--meta',
|
||||
});
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
@@ -84,7 +84,7 @@ export default async function main(ctx) {
|
||||
const debugEnabled = argv['--debug'];
|
||||
|
||||
const { print, log, error, note, debug } = createOutput({
|
||||
debug: debugEnabled
|
||||
debug: debugEnabled,
|
||||
});
|
||||
|
||||
if (argv._.length > 2) {
|
||||
@@ -103,13 +103,16 @@ export default async function main(ctx) {
|
||||
}
|
||||
|
||||
const meta = parseMeta(argv['--meta']);
|
||||
const { authConfig: { token }, config } = ctx;
|
||||
const {
|
||||
authConfig: { token },
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam, includeScheme } = config;
|
||||
const client = new Client({
|
||||
apiUrl,
|
||||
token,
|
||||
currentTeam,
|
||||
debug: debugEnabled
|
||||
debug: debugEnabled,
|
||||
});
|
||||
let contextName = null;
|
||||
|
||||
@@ -202,7 +205,16 @@ export default async function main(ctx) {
|
||||
const item = aliases.find(e => e.uid === app || e.alias === app);
|
||||
|
||||
if (item) {
|
||||
debug('Found alias that matches app name');
|
||||
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,
|
||||
@@ -250,7 +262,9 @@ export default async function main(ctx) {
|
||||
|
||||
// information to help the user find other deployments or instances
|
||||
if (app == null) {
|
||||
log(`To list more deployments for a project run ${cmd('now ls [project]')}`);
|
||||
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]')}`);
|
||||
}
|
||||
@@ -260,7 +274,9 @@ export default async function main(ctx) {
|
||||
console.log(
|
||||
`${table(
|
||||
[
|
||||
['project', 'latest deployment', 'inst #', 'type', 'state', 'age'].map(s => chalk.dim(s)),
|
||||
['project', 'latest deployment', 'inst #', 'type', 'state', 'age'].map(
|
||||
s => chalk.dim(s)
|
||||
),
|
||||
...deployments
|
||||
.sort(sortRecent())
|
||||
.map(dep => [
|
||||
@@ -272,7 +288,7 @@ export default async function main(ctx) {
|
||||
: dep.instanceCount,
|
||||
dep.type === 'LAMBDAS' ? chalk.gray('-') : dep.type,
|
||||
stateString(dep.state),
|
||||
chalk.gray(ms(Date.now() - new Date(dep.created)))
|
||||
chalk.gray(ms(Date.now() - new Date(dep.created))),
|
||||
],
|
||||
...(argv['--all']
|
||||
? dep.instances.map(i => [
|
||||
@@ -280,9 +296,9 @@ export default async function main(ctx) {
|
||||
` ${chalk.gray('-')} ${i.url} `,
|
||||
'',
|
||||
'',
|
||||
''
|
||||
'',
|
||||
])
|
||||
: [])
|
||||
: []),
|
||||
])
|
||||
// flatten since the previous step returns a nested
|
||||
// array of the deployment and (optionally) its instances
|
||||
@@ -293,12 +309,12 @@ export default async function main(ctx) {
|
||||
// we only want to render one deployment per app
|
||||
filterUniqueApps()
|
||||
: () => true
|
||||
)
|
||||
),
|
||||
],
|
||||
{
|
||||
align: ['l', 'l', 'r', 'l', 'b'],
|
||||
hsep: ' '.repeat(4),
|
||||
stringLength: strlen
|
||||
stringLength: strlen,
|
||||
}
|
||||
).replace(/^/gm, ' ')}\n\n`
|
||||
);
|
||||
@@ -310,7 +326,7 @@ function getProjectName(d) {
|
||||
return 'files';
|
||||
}
|
||||
|
||||
return d.name
|
||||
return d.name;
|
||||
}
|
||||
|
||||
// renders the state string
|
||||
|
||||
@@ -84,8 +84,8 @@ export default async function main(ctx) {
|
||||
debug: 'd',
|
||||
query: 'q',
|
||||
follow: 'f',
|
||||
output: 'o'
|
||||
}
|
||||
output: 'o',
|
||||
},
|
||||
});
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
@@ -136,14 +136,17 @@ export default async function main(ctx) {
|
||||
types = argv.all ? [] : ['command', 'stdout', 'stderr', 'exit'];
|
||||
outputMode = argv.output in logPrinters ? argv.output : 'short';
|
||||
|
||||
const { authConfig: { token }, config } = ctx;
|
||||
const {
|
||||
authConfig: { token },
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const now = new Now({ apiUrl, token, debug, currentTeam });
|
||||
const client = new Client({
|
||||
apiUrl,
|
||||
token,
|
||||
currentTeam,
|
||||
debug: debugEnabled
|
||||
debug: debugEnabled,
|
||||
});
|
||||
let contextName = null;
|
||||
|
||||
@@ -206,7 +209,7 @@ export default async function main(ctx) {
|
||||
types,
|
||||
instanceId,
|
||||
since,
|
||||
until
|
||||
until,
|
||||
}; // no follow
|
||||
const storage = [];
|
||||
const storeEvent = event => storage.push(event);
|
||||
@@ -216,7 +219,7 @@ export default async function main(ctx) {
|
||||
onEvent: storeEvent,
|
||||
quiet: false,
|
||||
debug,
|
||||
findOpts: findOpts1
|
||||
findOpts: findOpts1,
|
||||
});
|
||||
|
||||
const printedEventIds = new Set();
|
||||
@@ -238,14 +241,14 @@ export default async function main(ctx) {
|
||||
types,
|
||||
instanceId,
|
||||
since: since2,
|
||||
follow: true
|
||||
follow: true,
|
||||
};
|
||||
await printEvents(now, deployment.uid || deployment.id, currentTeam, {
|
||||
mode: 'logs',
|
||||
onEvent: printEvent,
|
||||
quiet: false,
|
||||
debug,
|
||||
findOpts: findOpts2
|
||||
findOpts: findOpts2,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -280,25 +283,53 @@ function printLogShort(log) {
|
||||
` ${obj.status} ${obj.bodyBytesSent}`;
|
||||
} else if (log.type === 'event') {
|
||||
data = `EVENT ${log.event} ${JSON.stringify(log.payload)}`;
|
||||
} else if (obj) {
|
||||
data = JSON.stringify(obj, null, 2);
|
||||
} else {
|
||||
data = obj
|
||||
? JSON.stringify(obj, null, 2)
|
||||
: (log.text || '')
|
||||
data = (log.text || '')
|
||||
.replace(/\n$/, '')
|
||||
.replace(/^\n/, '')
|
||||
// eslint-disable-next-line no-control-regex
|
||||
.replace(/\x1b\[1000D/g, '')
|
||||
.replace(/\x1b\[0K/g, '')
|
||||
.replace(/\x1b\[1A/g, '');
|
||||
if (/warning/i.test(data)) {
|
||||
data = chalk.yellow(data);
|
||||
} else if (log.type === 'stderr') {
|
||||
data = chalk.red(data);
|
||||
}
|
||||
}
|
||||
|
||||
const date = new Date(log.created).toISOString();
|
||||
|
||||
data.split('\n').forEach((line, i) => {
|
||||
if (
|
||||
line.includes('START RequestId:') ||
|
||||
line.includes('END RequestId:') ||
|
||||
line.includes('XRAY TraceId:')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.includes('REPORT RequestId:')) {
|
||||
line = line.substring(line.indexOf('Duration:'), line.length);
|
||||
|
||||
if (line.includes('Init Duration:')) {
|
||||
line = line.substring(0, line.indexOf('Init Duration:'));
|
||||
}
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
console.log(`${chalk.dim(date)} ${line}`);
|
||||
console.log(
|
||||
`${chalk.dim(date)} ${line.replace('[now-builder-debug] ', '')}`
|
||||
);
|
||||
} else {
|
||||
console.log(`${' '.repeat(date.length)} ${line}`);
|
||||
console.log(
|
||||
`${' '.repeat(date.length)} ${line.replace(
|
||||
'[now-builder-debug] ',
|
||||
''
|
||||
)}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -327,7 +358,7 @@ function printLogRaw(log) {
|
||||
|
||||
const logPrinters = {
|
||||
short: printLogShort,
|
||||
raw: printLogRaw
|
||||
raw: printLogRaw,
|
||||
};
|
||||
|
||||
function toTimestamp(datestr) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import Client from '../util/client.ts';
|
||||
import logo from '../util/output/logo';
|
||||
import getScope from '../util/get-scope';
|
||||
|
||||
const e = encodeURIComponent
|
||||
const e = encodeURIComponent;
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -48,8 +48,8 @@ const main = async ctx => {
|
||||
argv = mri(ctx.argv.slice(2), {
|
||||
boolean: ['help'],
|
||||
alias: {
|
||||
help: 'h'
|
||||
}
|
||||
help: 'h',
|
||||
},
|
||||
});
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
@@ -63,7 +63,10 @@ const main = async ctx => {
|
||||
await exit(0);
|
||||
}
|
||||
|
||||
const { authConfig: { token }, config: { currentTeam }} = ctx;
|
||||
const {
|
||||
authConfig: { token },
|
||||
config: { currentTeam },
|
||||
} = ctx;
|
||||
const client = new Client({ apiUrl, token, currentTeam, debug });
|
||||
|
||||
const { contextName } = await getScope(client);
|
||||
@@ -93,17 +96,21 @@ async function run({ client, contextName }) {
|
||||
if (args.length !== 0) {
|
||||
console.error(
|
||||
error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan('`now projects ls`')}`
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
'`now projects ls`'
|
||||
)}`
|
||||
)
|
||||
);
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
const list = await client.fetch('/projects/list', {method: 'GET'});
|
||||
const list = await client.fetch('/v2/projects/', { method: 'GET' });
|
||||
const elapsed = ms(new Date() - start);
|
||||
|
||||
console.log(
|
||||
`> ${plural('project', list.length, true)} found under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
`> ${plural('project', list.length, true)} found under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
|
||||
if (list.length > 0) {
|
||||
@@ -114,19 +121,19 @@ async function run({ client, contextName }) {
|
||||
header.concat(
|
||||
list.map(secret => [
|
||||
'',
|
||||
chalk.bold(secret.name),
|
||||
chalk.gray(`${ms(cur - new Date(secret.updatedAt)) } ago`)
|
||||
])
|
||||
chalk.bold(secret.name),
|
||||
chalk.gray(`${ms(cur - new Date(secret.updatedAt))} ago`),
|
||||
])
|
||||
),
|
||||
{
|
||||
align: ['l', 'l', 'l'],
|
||||
hsep: ' '.repeat(2),
|
||||
stringLength: strlen
|
||||
stringLength: strlen,
|
||||
}
|
||||
);
|
||||
|
||||
if (out) {
|
||||
console.log(`\n${ out }\n`);
|
||||
console.log(`\n${out}\n`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -148,11 +155,11 @@ async function run({ client, contextName }) {
|
||||
|
||||
// Check the existence of the project
|
||||
try {
|
||||
await client.fetch(`/projects/info/${e(name)}`)
|
||||
} catch(err) {
|
||||
await client.fetch(`/projects/info/${e(name)}`);
|
||||
} catch (err) {
|
||||
if (err.status === 404) {
|
||||
console.error(error('No such project exists'))
|
||||
return exit(1)
|
||||
console.error(error('No such project exists'));
|
||||
return exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +169,9 @@ async function run({ client, contextName }) {
|
||||
return exit(0);
|
||||
}
|
||||
|
||||
await client.fetch('/projects/remove', {method: 'DELETE', body: {name}});
|
||||
await client.fetch(`/v2/projects/${name}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
const elapsed = ms(new Date() - start);
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Project ${chalk.bold(
|
||||
@@ -193,7 +202,10 @@ async function run({ client, contextName }) {
|
||||
}
|
||||
|
||||
const [name] = args;
|
||||
await client.fetch('/projects/ensure-project', {method: 'POST', body: {name}});
|
||||
await client.fetch('/projects/ensure-project', {
|
||||
method: 'POST',
|
||||
body: { name },
|
||||
});
|
||||
const elapsed = ms(new Date() - start);
|
||||
|
||||
console.log(
|
||||
@@ -204,9 +216,7 @@ async function run({ client, contextName }) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(
|
||||
error('Please specify a valid subcommand: ls | add | rm')
|
||||
);
|
||||
console.error(error('Please specify a valid subcommand: ls | add | rm'));
|
||||
help();
|
||||
exit(1);
|
||||
}
|
||||
@@ -220,7 +230,7 @@ function readConfirmation(projectName) {
|
||||
return new Promise(resolve => {
|
||||
process.stdout.write(
|
||||
`The project: ${chalk.bold(projectName)} will be removed permanently.\n` +
|
||||
`It will also delete everything under the project including deployments.\n`
|
||||
`It will also delete everything under the project including deployments.\n`
|
||||
);
|
||||
|
||||
process.stdout.write(
|
||||
|
||||
@@ -70,11 +70,12 @@ let subcommand;
|
||||
|
||||
const main = async ctx => {
|
||||
argv = mri(ctx.argv.slice(2), {
|
||||
boolean: ['help', 'debug'],
|
||||
boolean: ['help', 'debug', 'yes'],
|
||||
alias: {
|
||||
help: 'h',
|
||||
debug: 'd'
|
||||
}
|
||||
debug: 'd',
|
||||
yes: 'y',
|
||||
},
|
||||
});
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
@@ -88,7 +89,10 @@ const main = async ctx => {
|
||||
await exit(0);
|
||||
}
|
||||
|
||||
const { authConfig: { token }, config: { currentTeam } } = ctx;
|
||||
const {
|
||||
authConfig: { token },
|
||||
config: { currentTeam },
|
||||
} = ctx;
|
||||
const output = createOutput({ debug });
|
||||
const client = new Client({ apiUrl, token, currentTeam, debug });
|
||||
let contextName = null;
|
||||
@@ -105,7 +109,7 @@ const main = async ctx => {
|
||||
}
|
||||
|
||||
try {
|
||||
await run({ token, contextName, currentTeam });
|
||||
await run({ output, token, contextName, currentTeam });
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
exit(1);
|
||||
@@ -121,7 +125,7 @@ export default async ctx => {
|
||||
}
|
||||
};
|
||||
|
||||
async function run({ token, contextName, currentTeam }) {
|
||||
async function run({ output, token, contextName, currentTeam }) {
|
||||
const secrets = new NowSecrets({ apiUrl, token, debug, currentTeam });
|
||||
const args = argv._.slice(1);
|
||||
const start = Date.now();
|
||||
@@ -153,13 +157,13 @@ async function run({ token, contextName, currentTeam }) {
|
||||
list.map(secret => [
|
||||
'',
|
||||
chalk.bold(secret.name),
|
||||
chalk.gray(`${ms(cur - new Date(secret.created))} ago`)
|
||||
chalk.gray(`${ms(cur - new Date(secret.created))} ago`),
|
||||
])
|
||||
),
|
||||
{
|
||||
align: ['l', 'l', 'l'],
|
||||
hsep: ' '.repeat(2),
|
||||
stringLength: strlen
|
||||
stringLength: strlen,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -185,7 +189,7 @@ async function run({ token, contextName, currentTeam }) {
|
||||
const theSecret = list.find(secret => secret.name === args[0]);
|
||||
|
||||
if (theSecret) {
|
||||
const yes = await readConfirmation(theSecret);
|
||||
const yes = argv.yes || (await readConfirmation(theSecret));
|
||||
if (!yes) {
|
||||
console.error(error('User abort'));
|
||||
return exit(0);
|
||||
@@ -250,6 +254,10 @@ async function run({ token, contextName, currentTeam }) {
|
||||
await secrets.add(name, value);
|
||||
const elapsed = ms(new Date() - start);
|
||||
|
||||
if (name !== name.toLowerCase()) {
|
||||
output.warn(`Your secret name was converted to lower-case`);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
|
||||
name.toLowerCase()
|
||||
@@ -275,7 +283,7 @@ function readConfirmation(secret) {
|
||||
const time = chalk.gray(`${ms(new Date() - new Date(secret.created))} ago`);
|
||||
const tbl = table([[chalk.bold(secret.name), time]], {
|
||||
align: ['r', 'l'],
|
||||
hsep: ' '.repeat(6)
|
||||
hsep: ' '.repeat(6),
|
||||
});
|
||||
|
||||
process.stdout.write(
|
||||
|
||||
@@ -17,7 +17,7 @@ import info from './util/output/info';
|
||||
import getNowDir from './util/config/global-path';
|
||||
import {
|
||||
getDefaultConfig,
|
||||
getDefaultAuthConfig
|
||||
getDefaultAuthConfig,
|
||||
} from './util/config/get-default';
|
||||
import hp from './util/humanize-path';
|
||||
import commands from './commands/index.ts';
|
||||
@@ -53,7 +53,7 @@ sourceMap.install();
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
release: `now-cli@${pkg.version}`,
|
||||
environment: pkg.version.includes('canary') ? 'canary' : 'stable'
|
||||
environment: pkg.version.includes('canary') ? 'canary' : 'stable',
|
||||
});
|
||||
|
||||
let debug = () => {};
|
||||
@@ -71,7 +71,7 @@ const main = async argv_ => {
|
||||
'--version': Boolean,
|
||||
'-v': '--version',
|
||||
'--debug': Boolean,
|
||||
'-d': '--debug'
|
||||
'-d': '--debug',
|
||||
},
|
||||
{ permissive: true }
|
||||
);
|
||||
@@ -102,7 +102,10 @@ const main = async argv_ => {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (localConfig instanceof NowError && !(localConfig instanceof ERRORS.CantFindConfig)) {
|
||||
if (
|
||||
localConfig instanceof NowError &&
|
||||
!(localConfig instanceof ERRORS.CantFindConfig)
|
||||
) {
|
||||
output.error(`Failed to load local config file: ${localConfig.message}`);
|
||||
return 1;
|
||||
}
|
||||
@@ -118,7 +121,7 @@ const main = async argv_ => {
|
||||
if (targetOrSubcommand !== 'update') {
|
||||
update = await checkForUpdate(pkg, {
|
||||
interval: ms('1d'),
|
||||
distTag: pkg.version.includes('canary') ? 'canary' : 'latest'
|
||||
distTag: pkg.version.includes('canary') ? 'canary' : 'latest',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -135,7 +138,15 @@ const main = async argv_ => {
|
||||
console.log(
|
||||
info(
|
||||
`${chalk.bgRed('UPDATE AVAILABLE')} ` +
|
||||
`Run ${cmd(await getUpdateCommand())} to install Now CLI ${update.latest}`
|
||||
`Run ${cmd(await getUpdateCommand())} to install Now CLI ${
|
||||
update.latest
|
||||
}`
|
||||
)
|
||||
);
|
||||
|
||||
console.log(
|
||||
info(
|
||||
`Changelog: https://github.com/zeit/now/releases/tag/now@${update.latest}`
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -307,9 +318,9 @@ const main = async argv_ => {
|
||||
console.error(
|
||||
error(
|
||||
`${'An unexpected error occurred while trying to write the ' +
|
||||
`default now config to "${hp(
|
||||
NOW_AUTH_CONFIG_PATH
|
||||
)}" `}${err.message}`
|
||||
`default now config to "${hp(NOW_AUTH_CONFIG_PATH)}" `}${
|
||||
err.message
|
||||
}`
|
||||
)
|
||||
);
|
||||
return 1;
|
||||
@@ -329,7 +340,7 @@ const main = async argv_ => {
|
||||
config,
|
||||
authConfig,
|
||||
localConfig,
|
||||
argv: argv_
|
||||
argv: argv_,
|
||||
};
|
||||
|
||||
let subcommand;
|
||||
@@ -339,7 +350,8 @@ const main = async argv_ => {
|
||||
const targetPath = join(process.cwd(), targetOrSubcommand);
|
||||
const targetPathExists = existsSync(targetPath);
|
||||
const subcommandExists =
|
||||
GLOBAL_COMMANDS.has(targetOrSubcommand) || commands.has(targetOrSubcommand);
|
||||
GLOBAL_COMMANDS.has(targetOrSubcommand) ||
|
||||
commands.has(targetOrSubcommand);
|
||||
|
||||
if (targetPathExists && subcommandExists) {
|
||||
console.error(
|
||||
@@ -412,7 +424,7 @@ const main = async argv_ => {
|
||||
message:
|
||||
'No existing credentials found. Please run ' +
|
||||
`${param('now login')} or pass ${param('--token')}`,
|
||||
slug: 'no-credentials-found'
|
||||
slug: 'no-credentials-found',
|
||||
})
|
||||
);
|
||||
|
||||
@@ -426,7 +438,7 @@ const main = async argv_ => {
|
||||
message: `This command doesn't work with ${param(
|
||||
'--token'
|
||||
)}. Please use ${param('--scope')}.`,
|
||||
slug: 'no-token-allowed'
|
||||
slug: 'no-token-allowed',
|
||||
})
|
||||
);
|
||||
|
||||
@@ -440,7 +452,7 @@ const main = async argv_ => {
|
||||
console.error(
|
||||
error({
|
||||
message: `You defined ${param('--token')}, but it's missing a value`,
|
||||
slug: 'missing-token-value'
|
||||
slug: 'missing-token-value',
|
||||
})
|
||||
);
|
||||
|
||||
@@ -459,11 +471,22 @@ const main = async argv_ => {
|
||||
const targetCommand = commands.get(subcommand);
|
||||
|
||||
if (argv['--team']) {
|
||||
output.warn(`The ${param('--team')} flag is deprecated. Please use ${param('--scope')} instead.`);
|
||||
output.warn(
|
||||
`The ${param('--team')} flag is deprecated. Please use ${param(
|
||||
'--scope'
|
||||
)} instead.`
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof scope === 'string' && targetCommand !== 'login' && targetCommand !== 'dev' && !(targetCommand === 'teams' && argv._[3] !== 'invite')) {
|
||||
const { authConfig: { token } } = ctx;
|
||||
if (
|
||||
typeof scope === 'string' &&
|
||||
targetCommand !== 'login' &&
|
||||
targetCommand !== 'dev' &&
|
||||
!(targetCommand === 'teams' && argv._[3] !== 'invite')
|
||||
) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
} = ctx;
|
||||
const client = new Client({ apiUrl, token });
|
||||
|
||||
let user = null;
|
||||
@@ -475,7 +498,7 @@ const main = async argv_ => {
|
||||
console.error(
|
||||
error({
|
||||
message: `You do not have access to the specified account`,
|
||||
slug: 'scope-not-accessible'
|
||||
slug: 'scope-not-accessible',
|
||||
})
|
||||
);
|
||||
|
||||
@@ -499,7 +522,7 @@ const main = async argv_ => {
|
||||
console.error(
|
||||
error({
|
||||
message: `You do not have access to the specified team`,
|
||||
slug: 'scope-not-accessible'
|
||||
slug: 'scope-not-accessible',
|
||||
})
|
||||
);
|
||||
|
||||
@@ -517,7 +540,7 @@ const main = async argv_ => {
|
||||
console.error(
|
||||
error({
|
||||
message: 'The specified scope does not exist',
|
||||
slug: 'scope-not-existent'
|
||||
slug: 'scope-not-existent',
|
||||
})
|
||||
);
|
||||
|
||||
@@ -577,7 +600,8 @@ const main = async argv_ => {
|
||||
if (shouldCollectMetrics) {
|
||||
metric
|
||||
.event(eventCategory, '1', pkg.version)
|
||||
.exception(err.message).send();
|
||||
.exception(err.message)
|
||||
.send();
|
||||
}
|
||||
|
||||
return 1;
|
||||
@@ -586,7 +610,8 @@ const main = async argv_ => {
|
||||
if (shouldCollectMetrics) {
|
||||
metric
|
||||
.event(eventCategory, '1', pkg.version)
|
||||
.exception(err.message).send();
|
||||
.exception(err.message)
|
||||
.send();
|
||||
}
|
||||
|
||||
// Otherwise it is an unexpected error and we should show the trace
|
||||
@@ -647,9 +672,7 @@ process.on('uncaughtException', handleUnexpected);
|
||||
// subcommands waiting for further data won't work (like `logs` and `logout`)!
|
||||
main(process.argv)
|
||||
.then(exitCode => {
|
||||
process.exitCode = exitCode;
|
||||
process.emit('nowExit');
|
||||
process.on('beforeExit', () => {
|
||||
process.exit(exitCode);
|
||||
});
|
||||
})
|
||||
.catch(handleUnexpected);
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
import { JsonBody, StreamBody, context } from 'fetch-h2';
|
||||
|
||||
// Packages
|
||||
import { parse } from 'url';
|
||||
import Sema from 'async-sema';
|
||||
import createOutput, { Output } from './output/create-output';
|
||||
|
||||
const MAX_REQUESTS_PER_CONNECTION = 1000;
|
||||
|
||||
type CurrentContext = ReturnType<typeof context> & {
|
||||
fetchesMade: number;
|
||||
ongoingFetches: number;
|
||||
};
|
||||
|
||||
export interface AgentFetchOptions {
|
||||
method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
||||
body?: NodeJS.ReadableStream | string;
|
||||
headers: { [key: string]: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a `fetch` version with a similar API to the browser's configured with a
|
||||
* HTTP2 agent. It encodes `body` automatically as JSON.
|
||||
*
|
||||
* @param {String} host
|
||||
* @return {Function} fetch
|
||||
*/
|
||||
export default class NowAgent {
|
||||
_contexts: ReturnType<typeof context>[];
|
||||
_currContext: CurrentContext;
|
||||
_output: Output;
|
||||
_protocol?: string;
|
||||
_sema: Sema;
|
||||
_url: string;
|
||||
|
||||
constructor(url: string, { debug = false } = {}) {
|
||||
// We use multiple contexts because each context represent one connection
|
||||
// With nginx, we're limited to 1000 requests before a connection is closed
|
||||
// http://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_requests
|
||||
// To get arround this, we keep track of requests made on a connection. when we're about to hit 1000
|
||||
// we start up a new connection, and re-route all future traffic through the new connection
|
||||
// and when the final request from the old connection resolves, we auto-close the old connection
|
||||
this._contexts = [context()];
|
||||
this._currContext = {
|
||||
...this._contexts[0],
|
||||
fetchesMade: 0,
|
||||
ongoingFetches: 0
|
||||
};
|
||||
|
||||
const parsed = parse(url);
|
||||
this._url = url;
|
||||
this._protocol = parsed.protocol;
|
||||
this._sema = new Sema(20);
|
||||
this._output = createOutput({ debug });
|
||||
}
|
||||
|
||||
setConcurrency({
|
||||
maxStreams,
|
||||
capacity
|
||||
}: {
|
||||
maxStreams: number;
|
||||
capacity: number;
|
||||
}) {
|
||||
this._sema = new Sema(maxStreams || 20, { capacity });
|
||||
}
|
||||
|
||||
async fetch(path: string, opts: AgentFetchOptions) {
|
||||
const { debug } = this._output;
|
||||
await this._sema.acquire();
|
||||
let currentContext: CurrentContext;
|
||||
this._currContext.fetchesMade++;
|
||||
if (this._currContext.fetchesMade >= MAX_REQUESTS_PER_CONNECTION) {
|
||||
const ctx = { ...context(), fetchesMade: 1, ongoingFetches: 0 };
|
||||
this._contexts.push(ctx);
|
||||
this._currContext = ctx;
|
||||
}
|
||||
|
||||
// If we're changing contexts, we don't want to record the ongoingFetch on the old context
|
||||
// That'll cause an off-by-one error when trying to close the old socket later
|
||||
this._currContext.ongoingFetches++;
|
||||
currentContext = this._currContext;
|
||||
|
||||
debug(
|
||||
`Total requests made on socket #${this._contexts.length}: ${this
|
||||
._currContext.fetchesMade}`
|
||||
);
|
||||
debug(
|
||||
`Concurrent requests on socket #${this._contexts.length}: ${this
|
||||
._currContext.ongoingFetches}`
|
||||
);
|
||||
|
||||
let body: JsonBody | StreamBody | string | undefined;
|
||||
if (opts.body && typeof opts.body === 'object') {
|
||||
if (typeof (<NodeJS.ReadableStream>opts.body).pipe === 'function') {
|
||||
body = new StreamBody(<NodeJS.ReadableStream>opts.body);
|
||||
} else {
|
||||
opts.headers['Content-Type'] = 'application/json';
|
||||
body = new JsonBody(opts.body);
|
||||
}
|
||||
} else {
|
||||
body = opts.body;
|
||||
}
|
||||
|
||||
const { host, protocol } = parse(path);
|
||||
const url = host ? `${protocol}//${host}` : this._url;
|
||||
const handleCompleted = async <T>(res: T) => {
|
||||
currentContext.ongoingFetches--;
|
||||
if (
|
||||
(currentContext !== this._currContext || host) &&
|
||||
currentContext.ongoingFetches <= 0
|
||||
) {
|
||||
// We've completely moved on to a new socket
|
||||
// close the old one
|
||||
|
||||
// TODO: Fix race condition:
|
||||
// If the response is a stream, and the server is still streaming data
|
||||
// we should check if the stream has closed before disconnecting
|
||||
// hasCompleted CAN technically be called before the res body stream is closed
|
||||
debug('Closing old socket');
|
||||
currentContext.disconnect(url);
|
||||
}
|
||||
|
||||
this._sema.release();
|
||||
return res;
|
||||
};
|
||||
|
||||
return currentContext
|
||||
.fetch((host ? '' : this._url) + path, { ...opts, body })
|
||||
.then(res => handleCompleted(res))
|
||||
.catch((err: Error) => {
|
||||
handleCompleted(null);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
const { debug } = this._output;
|
||||
debug('Closing agent');
|
||||
|
||||
this._currContext.disconnect(this._url);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,11 @@ export default async function getDeploymentForAlias(
|
||||
}
|
||||
|
||||
const appName = await getAppName(output, localConfig, localConfigPath);
|
||||
|
||||
if (!appName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const deployment = await getAppLastDeployment(
|
||||
output,
|
||||
client,
|
||||
|
||||
@@ -7,7 +7,11 @@ export default async function getInferredTargets(
|
||||
output: Output,
|
||||
config: Config
|
||||
) {
|
||||
output.warn(`The ${cmd('now alias')} command (no arguments) was deprecated in favour of ${cmd('now --prod')}.`);
|
||||
output.warn(
|
||||
`The ${cmd(
|
||||
'now alias'
|
||||
)} command (no arguments) was deprecated in favor of ${cmd('now --prod')}.`
|
||||
);
|
||||
|
||||
// This field is deprecated, warn about it
|
||||
if (config.aliases) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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 Agent, { AgentFetchOptions } from './agent';
|
||||
import responseError from './response-error';
|
||||
import ua from './ua';
|
||||
|
||||
@@ -17,7 +17,6 @@ export type FetchOptions = {
|
||||
};
|
||||
|
||||
export default class Client extends EventEmitter {
|
||||
_agent: Agent;
|
||||
_apiUrl: string;
|
||||
_debug: boolean;
|
||||
_forceNew: boolean;
|
||||
@@ -30,7 +29,7 @@ export default class Client extends EventEmitter {
|
||||
token,
|
||||
currentTeam,
|
||||
forceNew = false,
|
||||
debug = false
|
||||
debug = false,
|
||||
}: {
|
||||
apiUrl: string;
|
||||
token: string;
|
||||
@@ -44,30 +43,23 @@ export default class Client extends EventEmitter {
|
||||
this._forceNew = forceNew;
|
||||
this._output = createOutput({ debug });
|
||||
this._apiUrl = apiUrl;
|
||||
this._agent = new Agent(apiUrl, { debug });
|
||||
this._onRetry = this._onRetry.bind(this);
|
||||
this.currentTeam = currentTeam;
|
||||
|
||||
const closeAgent = () => {
|
||||
this._agent.close();
|
||||
process.removeListener('nowExit', closeAgent);
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
process.on('nowExit', closeAgent);
|
||||
}
|
||||
|
||||
retry<T>(fn: RetryFunction<T>, { retries = 3, maxTimeout = Infinity } = {}) {
|
||||
return retry(fn, {
|
||||
retries,
|
||||
maxTimeout,
|
||||
onRetry: this._onRetry
|
||||
onRetry: this._onRetry,
|
||||
});
|
||||
}
|
||||
|
||||
_fetch(_url: string, opts: FetchOptions = {}) {
|
||||
const parsedUrl = parseUrl(_url, true);
|
||||
const apiUrl = parsedUrl.host ? `${parsedUrl.protocol}//${parsedUrl.host}` : '';
|
||||
const apiUrl = parsedUrl.host
|
||||
? `${parsedUrl.protocol}//${parsedUrl.host}`
|
||||
: '';
|
||||
|
||||
if (opts.useCurrentTeam !== false && this.currentTeam) {
|
||||
const query = parsedUrl.query;
|
||||
@@ -80,20 +72,19 @@ export default class Client extends EventEmitter {
|
||||
Object.assign(opts, {
|
||||
body: JSON.stringify(opts.body),
|
||||
headers: Object.assign({}, opts.headers, {
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
opts.headers = opts.headers || {};
|
||||
opts.headers.authorization = `Bearer ${this._token}`;
|
||||
opts.headers.Authorization = `Bearer ${this._token}`;
|
||||
opts.headers['user-agent'] = ua;
|
||||
|
||||
const url = `${apiUrl ? '' : this._apiUrl}${_url}`;
|
||||
return this._output.time(
|
||||
`${opts.method || 'GET'} ${apiUrl ? '' : this._apiUrl}${_url} ${JSON.stringify(
|
||||
opts.body
|
||||
) || ''}`,
|
||||
this._agent.fetch(_url, opts as AgentFetchOptions)
|
||||
`${opts.method || 'GET'} ${url} ${JSON.stringify(opts.body) || ''}`,
|
||||
fetch(url, opts as RequestInit)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -126,7 +117,5 @@ export default class Client extends EventEmitter {
|
||||
this._output.debug(`Retrying: ${error}\n${error.stack}`);
|
||||
}
|
||||
|
||||
close() {
|
||||
this._agent.close();
|
||||
}
|
||||
close() {}
|
||||
}
|
||||
|
||||
@@ -15,40 +15,45 @@ export default async function createDeploy(
|
||||
return await now.create(paths, createArgs);
|
||||
} catch (error) {
|
||||
if (error.code === 'rate_limited') {
|
||||
return new ERRORS_TS.DeploymentsRateLimited(error.message);
|
||||
throw new ERRORS_TS.DeploymentsRateLimited(error.message);
|
||||
}
|
||||
|
||||
// Means that the domain used as a suffix no longer exists
|
||||
if (error.code === 'domain_missing') {
|
||||
return new ERRORS_TS.DomainNotFound(error.value);
|
||||
throw new ERRORS_TS.DomainNotFound(error.value);
|
||||
}
|
||||
|
||||
if (error.code === 'domain_not_found' && error.domain) {
|
||||
return new ERRORS_TS.DomainNotFound(error.domain);
|
||||
throw new ERRORS_TS.DomainNotFound(error.domain);
|
||||
}
|
||||
|
||||
// This error occures when a domain used in the `alias`
|
||||
// is not yet verified
|
||||
if (error.code === 'domain_not_verified' && error.domain) {
|
||||
return new ERRORS_TS.DomainNotVerified(error.domain);
|
||||
throw new ERRORS_TS.DomainNotVerified(error.domain);
|
||||
}
|
||||
|
||||
// If the domain used as a suffix is not verified, we fail
|
||||
if (error.code === 'domain_not_verified' && error.value) {
|
||||
return new ERRORS_TS.DomainVerificationFailed(error.value);
|
||||
throw new ERRORS_TS.DomainVerificationFailed(error.value);
|
||||
}
|
||||
|
||||
// If the domain isn't owned by the user
|
||||
if (error.code === 'not_domain_owner') {
|
||||
throw new ERRORS_TS.NotDomainOwner(error.message);
|
||||
}
|
||||
|
||||
if (error.code === 'builds_rate_limited') {
|
||||
return new ERRORS_TS.BuildsRateLimited(error.message);
|
||||
throw new ERRORS_TS.BuildsRateLimited(error.message);
|
||||
}
|
||||
|
||||
// If the user doesn't have permissions over the domain used as a suffix we fail
|
||||
if (error.code === 'forbidden') {
|
||||
return new ERRORS_TS.DomainPermissionDenied(error.value, contextName);
|
||||
throw new ERRORS_TS.DomainPermissionDenied(error.value, contextName);
|
||||
}
|
||||
|
||||
if (error.code === 'bad_request' && error.keyword) {
|
||||
return new ERRORS.SchemaValidationFailed(
|
||||
throw new ERRORS.SchemaValidationFailed(
|
||||
error.message,
|
||||
error.keyword,
|
||||
error.dataPath,
|
||||
@@ -57,19 +62,19 @@ export default async function createDeploy(
|
||||
}
|
||||
|
||||
if (error.code === 'domain_configured') {
|
||||
return new ERRORS_TS.AliasDomainConfigured(error);
|
||||
throw new ERRORS_TS.AliasDomainConfigured(error);
|
||||
}
|
||||
|
||||
if (error.code === 'missing_build_script') {
|
||||
return new ERRORS_TS.MissingBuildScript(error);
|
||||
throw new ERRORS_TS.MissingBuildScript(error);
|
||||
}
|
||||
|
||||
if (error.code === 'conflicting_file_path') {
|
||||
return new ERRORS_TS.ConflictingFilePath(error);
|
||||
throw new ERRORS_TS.ConflictingFilePath(error);
|
||||
}
|
||||
|
||||
if (error.code === 'conflicting_path_segment') {
|
||||
return new ERRORS_TS.ConflictingPathSegment(error);
|
||||
throw new ERRORS_TS.ConflictingPathSegment(error);
|
||||
}
|
||||
|
||||
// If the cert is missing we try to generate a new one and the retry
|
||||
@@ -87,10 +92,10 @@ export default async function createDeploy(
|
||||
}
|
||||
|
||||
if (error.code === 'not_found') {
|
||||
return new ERRORS_TS.DeploymentNotFound({ context: contextName });
|
||||
throw new ERRORS_TS.DeploymentNotFound({ context: contextName });
|
||||
}
|
||||
|
||||
const certError = mapCertError(error)
|
||||
const certError = mapCertError(error);
|
||||
if (certError) {
|
||||
return certError;
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
//
|
||||
import sleep from '../sleep';
|
||||
|
||||
import createPollingFn from '../create-polling-fn';
|
||||
|
||||
import getDeploymentByIdOrThrow from './get-deployment-by-id-or-throw';
|
||||
|
||||
const POLLING_INTERVAL = 5000;
|
||||
|
||||
async function* getStatusChangeFromPolling(
|
||||
now: any,
|
||||
contextName: string,
|
||||
idOrHost: string,
|
||||
initialState: string
|
||||
) {
|
||||
const pollDeployment = createPollingFn(
|
||||
getDeploymentByIdOrThrow,
|
||||
POLLING_INTERVAL
|
||||
);
|
||||
let prevState = initialState;
|
||||
for await (const deployment of pollDeployment(now, contextName, idOrHost)) {
|
||||
if (prevState !== deployment.state) {
|
||||
await sleep(5000);
|
||||
yield {
|
||||
type: 'state-change',
|
||||
created: Date.now(),
|
||||
payload: { value: deployment.state }
|
||||
};
|
||||
} else {
|
||||
prevState = deployment.state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default getStatusChangeFromPolling;
|
||||
222
packages/now-cli/src/util/deploy/process-deployment.ts
Normal file
222
packages/now-cli/src/util/deploy/process-deployment.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
import bytes from 'bytes';
|
||||
import Progress from 'progress';
|
||||
import chalk from 'chalk';
|
||||
import pluralize from 'pluralize';
|
||||
import {
|
||||
createDeployment,
|
||||
createLegacyDeployment,
|
||||
DeploymentOptions,
|
||||
} from '../../../../now-client';
|
||||
import wait from '../output/wait';
|
||||
import { Output } from '../output';
|
||||
// @ts-ignore
|
||||
import Now from '../../util';
|
||||
import { NowConfig } from '../dev/types';
|
||||
|
||||
export default async function processDeployment({
|
||||
now,
|
||||
output,
|
||||
hashes,
|
||||
paths,
|
||||
requestBody,
|
||||
uploadStamp,
|
||||
deployStamp,
|
||||
legacy,
|
||||
env,
|
||||
quiet,
|
||||
nowConfig,
|
||||
}: {
|
||||
now: Now;
|
||||
output: Output;
|
||||
hashes: { [key: string]: any };
|
||||
paths: string[];
|
||||
requestBody: DeploymentOptions;
|
||||
uploadStamp: () => number;
|
||||
deployStamp: () => number;
|
||||
legacy: boolean;
|
||||
env: any;
|
||||
quiet: boolean;
|
||||
nowConfig?: NowConfig;
|
||||
}) {
|
||||
const { warn, log, debug, note } = output;
|
||||
let bar: Progress | null = null;
|
||||
|
||||
const path0 = paths[0];
|
||||
const opts: DeploymentOptions = {
|
||||
...requestBody,
|
||||
debug: now._debug,
|
||||
};
|
||||
|
||||
if (!legacy) {
|
||||
let buildSpinner = null;
|
||||
let deploySpinner = null;
|
||||
|
||||
for await (const event of createDeployment(path0, opts, nowConfig)) {
|
||||
if (event.type === 'hashes-calculated') {
|
||||
hashes = event.payload;
|
||||
}
|
||||
|
||||
if (event.type === 'warning') {
|
||||
warn(event.payload);
|
||||
}
|
||||
|
||||
if (event.type === 'notice') {
|
||||
note(event.payload);
|
||||
}
|
||||
|
||||
if (event.type === 'file_count') {
|
||||
debug(
|
||||
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
|
||||
);
|
||||
|
||||
if (!quiet) {
|
||||
log(
|
||||
`Synced ${pluralize(
|
||||
'file',
|
||||
event.payload.missing.length,
|
||||
true
|
||||
)} ${uploadStamp()}`
|
||||
);
|
||||
}
|
||||
|
||||
const missingSize = event.payload.missing
|
||||
.map((sha: string) => event.payload.total.get(sha).data.length)
|
||||
.reduce((a: number, b: number) => a + b, 0);
|
||||
|
||||
bar = new Progress(`${chalk.gray('>')} Upload [:bar] :percent :etas`, {
|
||||
width: 20,
|
||||
complete: '=',
|
||||
incomplete: '',
|
||||
total: missingSize,
|
||||
clear: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (event.type === 'file-uploaded') {
|
||||
debug(
|
||||
`Uploaded: ${event.payload.file.names.join(' ')} (${bytes(
|
||||
event.payload.file.data.length
|
||||
)})`
|
||||
);
|
||||
|
||||
if (bar) {
|
||||
bar.tick(event.payload.file.data.length);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === 'created') {
|
||||
now._host = event.payload.url;
|
||||
|
||||
if (!quiet) {
|
||||
const version = legacy ? `${chalk.grey('[v1]')} ` : '';
|
||||
log(`https://${event.payload.url} ${version}${deployStamp()}`);
|
||||
} else {
|
||||
process.stdout.write(`https://${event.payload.url}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === 'build-state-changed') {
|
||||
if (buildSpinner === null) {
|
||||
buildSpinner = wait('Building...');
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === 'all-builds-completed') {
|
||||
if (buildSpinner) {
|
||||
buildSpinner();
|
||||
}
|
||||
|
||||
deploySpinner = wait('Finalizing...');
|
||||
}
|
||||
|
||||
// Handle error events
|
||||
if (event.type === 'error') {
|
||||
if (buildSpinner) {
|
||||
buildSpinner();
|
||||
}
|
||||
|
||||
if (deploySpinner) {
|
||||
deploySpinner();
|
||||
}
|
||||
|
||||
throw await now.handleDeploymentError(event.payload, { hashes, env });
|
||||
}
|
||||
|
||||
// Handle ready event
|
||||
if (event.type === 'ready') {
|
||||
if (deploySpinner) {
|
||||
deploySpinner();
|
||||
}
|
||||
|
||||
return event.payload;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for await (const event of createLegacyDeployment(path0, opts, nowConfig)) {
|
||||
if (event.type === 'hashes-calculated') {
|
||||
hashes = event.payload;
|
||||
}
|
||||
|
||||
if (event.type === 'file_count') {
|
||||
debug(
|
||||
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
|
||||
);
|
||||
if (!quiet) {
|
||||
log(
|
||||
`Synced ${pluralize(
|
||||
'file',
|
||||
event.payload.missing.length,
|
||||
true
|
||||
)} ${uploadStamp()}`
|
||||
);
|
||||
}
|
||||
|
||||
const missingSize = event.payload.missing
|
||||
.map((sha: string) => event.payload.total.get(sha).data.length)
|
||||
.reduce((a: number, b: number) => a + b, 0);
|
||||
|
||||
bar = new Progress(`${chalk.gray('>')} Upload [:bar] :percent :etas`, {
|
||||
width: 20,
|
||||
complete: '=',
|
||||
incomplete: '',
|
||||
total: missingSize,
|
||||
clear: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (event.type === 'file-uploaded') {
|
||||
debug(
|
||||
`Uploaded: ${event.payload.file.names.join(' ')} (${bytes(
|
||||
event.payload.file.data.length
|
||||
)})`
|
||||
);
|
||||
|
||||
if (bar) {
|
||||
bar.tick(event.payload.file.data.length);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === 'created') {
|
||||
now._host = event.payload.url;
|
||||
|
||||
if (!quiet) {
|
||||
const version = legacy ? `${chalk.grey('[v1]')} ` : '';
|
||||
log(`${event.payload.url} ${version}${deployStamp()}`);
|
||||
} else {
|
||||
process.stdout.write(`https://${event.payload.url}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle error events
|
||||
if (event.type === 'error') {
|
||||
throw await now.handleDeploymentError(event.payload, { hashes, env });
|
||||
}
|
||||
|
||||
// Handle ready event
|
||||
if (event.type === 'ready') {
|
||||
log(`Build completed`);
|
||||
return event.payload;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
packages/now-cli/src/util/deploy/should-deploy-dir.ts
Normal file
18
packages/now-cli/src/util/deploy/should-deploy-dir.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { homedir } from 'os';
|
||||
import promptBool from '../input/prompt-bool';
|
||||
import { Output } from '../output';
|
||||
|
||||
export default async function shouldDeployDir(argv0: string, output: Output) {
|
||||
let yes = true;
|
||||
if (argv0 === homedir()) {
|
||||
if (
|
||||
!(await promptBool(
|
||||
'You are deploying your home directory. Do you want to continue?'
|
||||
))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
yes = false;
|
||||
}
|
||||
}
|
||||
return yes;
|
||||
}
|
||||
@@ -8,14 +8,15 @@ import { createHash } from 'crypto';
|
||||
import { createGunzip } from 'zlib';
|
||||
import { join, resolve } from 'path';
|
||||
import { funCacheDir } from '@zeit/fun';
|
||||
import cacheDirectory from 'cache-or-tmp-directory';
|
||||
import { PackageJson } from '@now/build-utils';
|
||||
import XDGAppPaths from 'xdg-app-paths';
|
||||
import {
|
||||
createReadStream,
|
||||
mkdirp,
|
||||
readFile,
|
||||
readJSON,
|
||||
writeFile,
|
||||
remove
|
||||
remove,
|
||||
} from 'fs-extra';
|
||||
import pkg from '../../../package.json';
|
||||
|
||||
@@ -23,10 +24,10 @@ import { NoBuilderCacheError, BuilderCacheCleanError } from '../errors-ts';
|
||||
import wait from '../output/wait';
|
||||
import { Output } from '../output';
|
||||
import { getDistTag } from '../get-dist-tag';
|
||||
import { devDependencies } from '../../../package.json';
|
||||
|
||||
import * as staticBuilder from './static-builder';
|
||||
import { BuilderWithPackage, Package } from './types';
|
||||
import { BuilderWithPackage } from './types';
|
||||
import { getBundledBuilders } from './get-bundled-builders';
|
||||
|
||||
const registryTypes = new Set(['version', 'tag', 'range']);
|
||||
|
||||
@@ -34,14 +35,10 @@ const localBuilders: { [key: string]: BuilderWithPackage } = {
|
||||
'@now/static': {
|
||||
runInProcess: true,
|
||||
builder: Object.freeze(staticBuilder),
|
||||
package: Object.freeze({ name: '@now/static', version: '' })
|
||||
}
|
||||
package: Object.freeze({ name: '@now/static', version: '' }),
|
||||
},
|
||||
};
|
||||
|
||||
const bundledBuilders = Object.keys(devDependencies).filter(d =>
|
||||
d.startsWith('@now/')
|
||||
);
|
||||
|
||||
const distTag = getDistTag(pkg.version);
|
||||
|
||||
export const cacheDirPromise = prepareCacheDir();
|
||||
@@ -80,7 +77,7 @@ export async function prepareCacheDir() {
|
||||
const { NOW_BUILDER_CACHE_DIR } = process.env;
|
||||
const designated = NOW_BUILDER_CACHE_DIR
|
||||
? resolve(NOW_BUILDER_CACHE_DIR)
|
||||
: cacheDirectory('co.zeit.now');
|
||||
: XDGAppPaths('co.zeit.now').cache();
|
||||
|
||||
if (!designated) {
|
||||
throw new NoBuilderCacheError();
|
||||
@@ -117,7 +114,7 @@ export async function prepareBuilderDir() {
|
||||
export async function prepareBuilderModulePath() {
|
||||
const [builderDir, builderContents] = await Promise.all([
|
||||
builderDirPromise,
|
||||
readFile(join(__dirname, 'builder-worker.js'))
|
||||
readFile(join(__dirname, 'builder-worker.js')),
|
||||
]);
|
||||
let needsWrite = false;
|
||||
const builderSha = getSha(builderContents);
|
||||
@@ -179,7 +176,7 @@ export function getBuildUtils(packages: string[]): string {
|
||||
export function filterPackage(
|
||||
builderSpec: string,
|
||||
distTag: string,
|
||||
buildersPkg: Package
|
||||
buildersPkg: PackageJson
|
||||
) {
|
||||
if (builderSpec in localBuilders) return false;
|
||||
const parsed = npa(builderSpec);
|
||||
@@ -187,7 +184,7 @@ export function filterPackage(
|
||||
parsed.name &&
|
||||
parsed.type === 'tag' &&
|
||||
parsed.fetchSpec === distTag &&
|
||||
bundledBuilders.includes(parsed.name) &&
|
||||
getBundledBuilders().includes(parsed.name) &&
|
||||
buildersPkg.dependencies
|
||||
) {
|
||||
const parsedInstalled = npa(
|
||||
@@ -259,10 +256,10 @@ export async function installBuilders(
|
||||
'--exact',
|
||||
'--no-lockfile',
|
||||
'--non-interactive',
|
||||
...packagesToInstall
|
||||
...packagesToInstall,
|
||||
],
|
||||
{
|
||||
cwd: builderDir
|
||||
cwd: builderDir,
|
||||
}
|
||||
);
|
||||
} finally {
|
||||
@@ -294,10 +291,10 @@ export async function updateBuilders(
|
||||
'--exact',
|
||||
'--no-lockfile',
|
||||
'--non-interactive',
|
||||
...packages.filter(p => p !== '@now/static')
|
||||
...packages.filter(p => p !== '@now/static'),
|
||||
],
|
||||
{
|
||||
cwd: builderDir
|
||||
cwd: builderDir,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -336,7 +333,7 @@ export async function getBuilder(
|
||||
const pkg = require(join(dest, 'package.json'));
|
||||
builderWithPkg = {
|
||||
builder: Object.freeze(mod),
|
||||
package: Object.freeze(pkg)
|
||||
package: Object.freeze(pkg),
|
||||
};
|
||||
} catch (err) {
|
||||
if (err.code === 'MODULE_NOT_FOUND') {
|
||||
@@ -357,7 +354,7 @@ export async function getBuilder(
|
||||
|
||||
function getPackageName(
|
||||
parsed: npa.Result,
|
||||
buildersPkg: Package
|
||||
buildersPkg: PackageJson
|
||||
): string | null {
|
||||
if (registryTypes.has(parsed.type)) {
|
||||
return parsed.name;
|
||||
@@ -378,7 +375,7 @@ function getSha(buffer: Buffer): string {
|
||||
}
|
||||
|
||||
function hasBundledBuilders(dependencies: { [name: string]: string }): boolean {
|
||||
for (const name of bundledBuilders) {
|
||||
for (const name of getBundledBuilders()) {
|
||||
if (!(name in dependencies)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import bytes from 'bytes';
|
||||
import { delimiter, dirname, join } from 'path';
|
||||
import { fork, ChildProcess } from 'child_process';
|
||||
import { createFunction } from '@zeit/fun';
|
||||
import { File, Lambda, FileBlob, FileFsRef } from '@now/build-utils';
|
||||
import { Builder, File, Lambda, FileBlob, FileFsRef } from '@now/build-utils';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import chalk from 'chalk';
|
||||
import which from 'which';
|
||||
@@ -23,12 +23,11 @@ import { builderModulePathPromise, getBuilder } from './builder-cache';
|
||||
import {
|
||||
EnvConfig,
|
||||
NowConfig,
|
||||
BuildConfig,
|
||||
BuildMatch,
|
||||
BuildResult,
|
||||
BuilderInputs,
|
||||
BuilderOutput,
|
||||
BuilderOutputs
|
||||
BuilderOutputs,
|
||||
} from './types';
|
||||
|
||||
interface BuildMessage {
|
||||
@@ -69,7 +68,7 @@ async function createBuildProcess(
|
||||
}
|
||||
const [execPath, modulePath] = await Promise.all([
|
||||
nodeBinPromise,
|
||||
builderModulePathPromise
|
||||
builderModulePathPromise,
|
||||
]);
|
||||
let PATH = `${dirname(execPath)}${delimiter}${process.env.PATH}`;
|
||||
if (yarnPath) {
|
||||
@@ -81,11 +80,11 @@ async function createBuildProcess(
|
||||
...process.env,
|
||||
PATH,
|
||||
...buildEnv,
|
||||
NOW_REGION: 'dev1'
|
||||
NOW_REGION: 'dev1',
|
||||
},
|
||||
execPath,
|
||||
execArgv: [],
|
||||
stdio: ['ignore', 'pipe', 'pipe', 'ipc']
|
||||
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
|
||||
});
|
||||
match.buildProcess = buildProcess;
|
||||
|
||||
@@ -122,7 +121,7 @@ export async function executeBuild(
|
||||
filesRemoved?: string[]
|
||||
): Promise<void> {
|
||||
const {
|
||||
builderWithPkg: { runInProcess, builder, package: pkg }
|
||||
builderWithPkg: { runInProcess, builder, package: pkg },
|
||||
} = match;
|
||||
const { src: entrypoint } = match;
|
||||
const { env, debug, buildEnv, yarnPath, cwd: workPath } = devServer;
|
||||
@@ -165,8 +164,8 @@ export async function executeBuild(
|
||||
filesChanged,
|
||||
filesRemoved,
|
||||
env,
|
||||
buildEnv
|
||||
}
|
||||
buildEnv,
|
||||
},
|
||||
};
|
||||
|
||||
let buildResultOrOutputs: BuilderOutputs | BuildResult;
|
||||
@@ -203,7 +202,7 @@ export async function executeBuild(
|
||||
buildProcess.send({
|
||||
type: 'build',
|
||||
builderName: pkg.name,
|
||||
buildParams
|
||||
buildParams,
|
||||
});
|
||||
|
||||
buildResultOrOutputs = await new Promise((resolve, reject) => {
|
||||
@@ -260,7 +259,11 @@ export async function executeBuild(
|
||||
result = {
|
||||
output: buildResultOrOutputs as BuilderOutputs,
|
||||
routes: [],
|
||||
watch: []
|
||||
watch: [],
|
||||
distPath:
|
||||
typeof buildResultOrOutputs.distPath === 'string'
|
||||
? buildResultOrOutputs.distPath
|
||||
: undefined,
|
||||
};
|
||||
} else {
|
||||
result = buildResultOrOutputs as BuildResult;
|
||||
@@ -346,9 +349,9 @@ export async function executeBuild(
|
||||
...nowConfig.env,
|
||||
...asset.environment,
|
||||
...env,
|
||||
NOW_REGION: 'dev1'
|
||||
}
|
||||
}
|
||||
NOW_REGION: 'dev1',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -382,7 +385,7 @@ export async function getBuildMatches(
|
||||
return matches;
|
||||
}
|
||||
|
||||
const noMatches: BuildConfig[] = [];
|
||||
const noMatches: Builder[] = [];
|
||||
const builds = nowConfig.builds || [{ src: '**', use: '@now/static' }];
|
||||
|
||||
for (const buildConfig of builds) {
|
||||
@@ -420,7 +423,7 @@ export async function getBuildMatches(
|
||||
builderWithPkg,
|
||||
buildOutput: {},
|
||||
buildResults: new Map(),
|
||||
buildTimestamp: 0
|
||||
buildTimestamp: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@ export const httpStatusDescriptionMap = new Map([
|
||||
[502, 'BAD_GATEWAY'],
|
||||
[503, 'SERVICE_UNAVAILABLE'],
|
||||
[504, 'GATEWAY_TIMEOUT'],
|
||||
[508, 'INFINITE_LOOP']
|
||||
[508, 'INFINITE_LOOP'],
|
||||
]);
|
||||
|
||||
export const errorMessageMap = new Map([
|
||||
[400, 'Bad request'],
|
||||
[402, 'Payment required'],
|
||||
[403, 'You don\'t have the required permissions'],
|
||||
[403, "You don't have the required permissions"],
|
||||
[404, 'The page could not be found'],
|
||||
[405, 'Method not allowed'],
|
||||
[410, 'The deployment has been removed'],
|
||||
@@ -28,7 +28,7 @@ export const errorMessageMap = new Map([
|
||||
[501, 'Not implemented'],
|
||||
[503, 'The deployment is currently unavailable'],
|
||||
[504, 'An error occurred with your deployment'],
|
||||
[508, 'Infinite loop detected']
|
||||
[508, 'Infinite loop detected'],
|
||||
]);
|
||||
|
||||
interface ErrorMessage {
|
||||
@@ -40,20 +40,20 @@ interface ErrorMessage {
|
||||
const appError = {
|
||||
title: 'An error occurred with this application.',
|
||||
subtitle: 'This is an error with the application itself, not the platform.',
|
||||
app_error: true
|
||||
app_error: true,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const infrastructureError = {
|
||||
title: 'An internal error occurred with ZEIT Now.',
|
||||
subtitle: 'This is an error with the platform itself, not the application.',
|
||||
app_error: false
|
||||
app_error: false,
|
||||
};
|
||||
|
||||
const pageNotFoundError = {
|
||||
title: 'The page could not be found.',
|
||||
subtitle: 'The page could not be found in the application.',
|
||||
app_error: true
|
||||
app_error: true,
|
||||
};
|
||||
|
||||
export function generateErrorMessage(
|
||||
@@ -68,7 +68,7 @@ export function generateErrorMessage(
|
||||
}
|
||||
return {
|
||||
title: errorMessageMap.get(statusCode) || 'Error occurred',
|
||||
app_error: false
|
||||
app_error: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
11
packages/now-cli/src/util/dev/get-bundled-builders.ts
Normal file
11
packages/now-cli/src/util/dev/get-bundled-builders.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export function getBundledBuilders() {
|
||||
return [
|
||||
'@now/go',
|
||||
'@now/next',
|
||||
'@now/node',
|
||||
'@now/ruby',
|
||||
'@now/python',
|
||||
'@now/static-build',
|
||||
'@now/build-utils',
|
||||
];
|
||||
}
|
||||
@@ -98,7 +98,7 @@ export default async function(
|
||||
headers: combinedHeaders,
|
||||
uri_args: query,
|
||||
matched_route: routeConfig,
|
||||
matched_route_idx: idx
|
||||
matched_route_idx: idx,
|
||||
};
|
||||
break;
|
||||
} else {
|
||||
@@ -114,7 +114,7 @@ export default async function(
|
||||
headers: combinedHeaders,
|
||||
uri_args: query,
|
||||
matched_route: routeConfig,
|
||||
matched_route_idx: idx
|
||||
matched_route_idx: idx,
|
||||
};
|
||||
break;
|
||||
}
|
||||
@@ -127,7 +127,7 @@ export default async function(
|
||||
found: false,
|
||||
dest: reqPathname,
|
||||
uri_args: query,
|
||||
headers: combinedHeaders
|
||||
headers: combinedHeaders,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,11 @@ import { basename, dirname, extname, join } from 'path';
|
||||
import directoryTemplate from 'serve-handler/src/directory';
|
||||
|
||||
import {
|
||||
Builder,
|
||||
FileFsRef,
|
||||
PackageJson,
|
||||
detectBuilders,
|
||||
detectRoutes
|
||||
detectRoutes,
|
||||
} from '@now/build-utils';
|
||||
|
||||
import { once } from '../once';
|
||||
@@ -33,7 +34,7 @@ import { version as cliVersion } from '../../../package.json';
|
||||
import {
|
||||
createIgnore,
|
||||
staticFiles as getFiles,
|
||||
getAllProjectFiles
|
||||
getAllProjectFiles,
|
||||
} from '../get-files';
|
||||
import { validateNowConfigBuilds, validateNowConfigRoutes } from './validate';
|
||||
|
||||
@@ -46,7 +47,7 @@ import { generateErrorMessage, generateHttpStatusDescription } from './errors';
|
||||
import {
|
||||
builderDirPromise,
|
||||
installBuilders,
|
||||
updateBuilders
|
||||
updateBuilders,
|
||||
} from './builder-cache';
|
||||
|
||||
// HTML templates
|
||||
@@ -60,7 +61,6 @@ import {
|
||||
EnvConfig,
|
||||
NowConfig,
|
||||
DevServerOptions,
|
||||
BuildConfig,
|
||||
BuildMatch,
|
||||
BuildResult,
|
||||
BuilderInputs,
|
||||
@@ -70,7 +70,7 @@ import {
|
||||
InvokeResult,
|
||||
ListenSpec,
|
||||
RouteConfig,
|
||||
RouteResult
|
||||
RouteResult,
|
||||
} from './types';
|
||||
|
||||
interface FSEvent {
|
||||
@@ -87,7 +87,7 @@ interface NodeRequire {
|
||||
|
||||
declare const __non_webpack_require__: NodeRequire;
|
||||
|
||||
function sortBuilders(buildA: BuildConfig, buildB: BuildConfig) {
|
||||
function sortBuilders(buildA: Builder, buildB: Builder) {
|
||||
if (buildA && buildA.use && buildA.use.startsWith('@now/static-build')) {
|
||||
return 1;
|
||||
}
|
||||
@@ -182,6 +182,20 @@ export default class DevServer {
|
||||
const filesChanged: Set<string> = new Set();
|
||||
const filesRemoved: Set<string> = new Set();
|
||||
|
||||
const distPaths: string[] = [];
|
||||
|
||||
for (const buildMatch of this.buildMatches.values()) {
|
||||
for (const buildResult of buildMatch.buildResults.values()) {
|
||||
if (buildResult.distPath) {
|
||||
distPaths.push(buildResult.distPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
events = events.filter(event =>
|
||||
distPaths.every(distPath => !event.path.startsWith(distPath))
|
||||
);
|
||||
|
||||
// First, update the `files` mapping of source files
|
||||
for (const event of events) {
|
||||
if (event.type === 'add') {
|
||||
@@ -255,9 +269,7 @@ export default class DevServer {
|
||||
});
|
||||
} else {
|
||||
this.output.debug(
|
||||
`Not rebuilding because \`shouldServe()\` returned \`false\` for "${
|
||||
match.use
|
||||
}" request path "${requestPath}"`
|
||||
`Not rebuilding because \`shouldServe()\` returned \`false\` for "${match.use}" request path "${requestPath}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -376,7 +388,7 @@ export default class DevServer {
|
||||
// Sort build matches to make sure `@now/static-build` is always last
|
||||
this.buildMatches = new Map(
|
||||
[...this.buildMatches.entries()].sort((matchA, matchB) => {
|
||||
return sortBuilders(matchA[1] as BuildConfig, matchB[1] as BuildConfig);
|
||||
return sortBuilders(matchA[1] as Builder, matchB[1] as Builder);
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -413,10 +425,10 @@ export default class DevServer {
|
||||
for (const buildMatch of this.buildMatches.values()) {
|
||||
const {
|
||||
src,
|
||||
builderWithPkg: { package: pkg }
|
||||
builderWithPkg: { package: pkg },
|
||||
} = buildMatch;
|
||||
if (pkg.name === '@now/static') continue;
|
||||
if (updatedBuilders.includes(pkg.name)) {
|
||||
if (pkg.name && updatedBuilders.includes(pkg.name)) {
|
||||
this.buildMatches.delete(src);
|
||||
this.output.debug(`Invalidated build match for "${src}"`);
|
||||
}
|
||||
@@ -441,7 +453,7 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.validateEnvConfig(fileName, base || {}, env);
|
||||
return this.validateEnvConfig(fileName, base || {}, env);
|
||||
} catch (err) {
|
||||
if (err instanceof MissingDotenvVarsError) {
|
||||
this.output.error(err.message);
|
||||
@@ -450,7 +462,7 @@ export default class DevServer {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return { ...base, ...env };
|
||||
return {};
|
||||
}
|
||||
|
||||
async getNowConfig(
|
||||
@@ -516,8 +528,8 @@ export default class DevServer {
|
||||
`filtered out ${allFiles.length - files.length} files`
|
||||
);
|
||||
|
||||
const { builders, errors } = await detectBuilders(files, pkg, {
|
||||
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest'
|
||||
const { builders, warnings, errors } = await detectBuilders(files, pkg, {
|
||||
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest',
|
||||
});
|
||||
|
||||
if (errors) {
|
||||
@@ -525,6 +537,10 @@ export default class DevServer {
|
||||
await this.exit();
|
||||
}
|
||||
|
||||
if (warnings && warnings.length > 0) {
|
||||
warnings.forEach(warning => this.output.warn(warning.message));
|
||||
}
|
||||
|
||||
if (builders) {
|
||||
const { defaultRoutes, error: routesError } = await detectRoutes(
|
||||
files,
|
||||
@@ -608,7 +624,9 @@ export default class DevServer {
|
||||
type: string,
|
||||
env: EnvConfig = {},
|
||||
localEnv: EnvConfig = {}
|
||||
): void {
|
||||
): EnvConfig {
|
||||
// Validate if there are any missing env vars defined in `now.json`,
|
||||
// but not in the `.env` / `.build.env` file
|
||||
const missing: string[] = Object.entries(env)
|
||||
.filter(
|
||||
([name, value]) =>
|
||||
@@ -617,9 +635,36 @@ export default class DevServer {
|
||||
!hasOwnProperty(localEnv, name)
|
||||
)
|
||||
.map(([name]) => name);
|
||||
if (missing.length >= 1) {
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw new MissingDotenvVarsError(type, missing);
|
||||
}
|
||||
|
||||
const merged: EnvConfig = { ...env, ...localEnv };
|
||||
|
||||
// Validate that the env var name matches what AWS Lambda allows:
|
||||
// - https://docs.aws.amazon.com/lambda/latest/dg/env_variables.html
|
||||
let hasInvalidName = false;
|
||||
for (const key of Object.keys(merged)) {
|
||||
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(key)) {
|
||||
this.output.warn(
|
||||
`Ignoring ${type
|
||||
.split('.')
|
||||
.slice(1)
|
||||
.reverse()
|
||||
.join(' ')} var ${JSON.stringify(key)} because name is invalid`
|
||||
);
|
||||
hasInvalidName = true;
|
||||
delete merged[key];
|
||||
}
|
||||
}
|
||||
if (hasInvalidName) {
|
||||
this.output.log(
|
||||
'Env var names must start with letters, and can only contain alphanumeric characters and underscores'
|
||||
);
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -652,7 +697,7 @@ export default class DevServer {
|
||||
const nowConfigBuild = nowConfig.build || {};
|
||||
const [env, buildEnv] = await Promise.all([
|
||||
this.getLocalEnv('.env', nowConfig.env),
|
||||
this.getLocalEnv('.env.build', nowConfigBuild.env)
|
||||
this.getLocalEnv('.env.build', nowConfigBuild.env),
|
||||
]);
|
||||
Object.assign(process.env, buildEnv);
|
||||
this.env = env;
|
||||
@@ -670,8 +715,8 @@ export default class DevServer {
|
||||
|
||||
const builders: Set<string> = new Set(
|
||||
(nowConfig.builds || [])
|
||||
.filter((b: BuildConfig) => b.use)
|
||||
.map((b: BuildConfig) => b.use as string)
|
||||
.filter((b: Builder) => b.use)
|
||||
.map((b: Builder) => b.use as string)
|
||||
);
|
||||
|
||||
await installBuilders(builders, this.yarnPath, this.output);
|
||||
@@ -716,7 +761,7 @@ export default class DevServer {
|
||||
ignoreInitial: true,
|
||||
useFsEvents: false,
|
||||
usePolling: false,
|
||||
persistent: true
|
||||
persistent: true,
|
||||
});
|
||||
this.watcher.on('add', (path: string) => {
|
||||
this.enqueueFsEvent('add', path);
|
||||
@@ -856,8 +901,8 @@ export default class DevServer {
|
||||
const json = JSON.stringify({
|
||||
error: {
|
||||
code: statusCode,
|
||||
message: errorMessage.title
|
||||
}
|
||||
message: errorMessage.title,
|
||||
},
|
||||
});
|
||||
body = `${json}\n`;
|
||||
} else if (accept.includes('html')) {
|
||||
@@ -870,7 +915,7 @@ export default class DevServer {
|
||||
http_status_code: statusCode,
|
||||
http_status_description,
|
||||
error_code,
|
||||
now_id: nowRequestId
|
||||
now_id: nowRequestId,
|
||||
});
|
||||
} else if (statusCode === 502) {
|
||||
view = errorTemplate502({
|
||||
@@ -878,19 +923,19 @@ export default class DevServer {
|
||||
http_status_code: statusCode,
|
||||
http_status_description,
|
||||
error_code,
|
||||
now_id: nowRequestId
|
||||
now_id: nowRequestId,
|
||||
});
|
||||
} else {
|
||||
view = errorTemplate({
|
||||
http_status_code: statusCode,
|
||||
http_status_description,
|
||||
now_id: nowRequestId
|
||||
now_id: nowRequestId,
|
||||
});
|
||||
}
|
||||
body = errorTemplateBase({
|
||||
http_status_code: statusCode,
|
||||
http_status_description,
|
||||
view
|
||||
view,
|
||||
});
|
||||
} else {
|
||||
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
||||
@@ -917,7 +962,7 @@ export default class DevServer {
|
||||
res.setHeader('content-type', 'application/json');
|
||||
const json = JSON.stringify({
|
||||
redirect: location,
|
||||
status: String(statusCode)
|
||||
status: String(statusCode),
|
||||
});
|
||||
body = `${json}\n`;
|
||||
} else if (accept.includes('html')) {
|
||||
@@ -949,7 +994,7 @@ export default class DevServer {
|
||||
server: 'now',
|
||||
'x-now-trace': 'dev1',
|
||||
'x-now-id': nowRequestId,
|
||||
'x-now-cache': 'MISS'
|
||||
'x-now-cache': 'MISS',
|
||||
};
|
||||
for (const [name, value] of Object.entries(allHeaders)) {
|
||||
res.setHeader(name, value);
|
||||
@@ -976,7 +1021,7 @@ export default class DevServer {
|
||||
'x-now-deployment-url': host,
|
||||
'x-now-id': nowRequestId,
|
||||
'x-now-log-id': nowRequestId.split('-')[2],
|
||||
'x-zeit-co-forwarded-for': ip
|
||||
'x-zeit-co-forwarded-for': ip,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1183,9 +1228,7 @@ export default class DevServer {
|
||||
Object.assign(origUrl.query, uri_args);
|
||||
const newUrl = url.format(origUrl);
|
||||
this.output.debug(
|
||||
`Checking build result's ${
|
||||
buildResult.routes.length
|
||||
} \`routes\` to match ${newUrl}`
|
||||
`Checking build result's ${buildResult.routes.length} \`routes\` to match ${newUrl}`
|
||||
);
|
||||
const matchedRoute = await devRouter(
|
||||
newUrl,
|
||||
@@ -1238,17 +1281,17 @@ export default class DevServer {
|
||||
headers: [
|
||||
{
|
||||
key: 'Content-Type',
|
||||
value: getMimeType(assetKey)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
value: getMimeType(assetKey),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
case 'FileBlob':
|
||||
const headers: http.OutgoingHttpHeaders = {
|
||||
'Content-Length': asset.data.length,
|
||||
'Content-Type': getMimeType(assetKey)
|
||||
'Content-Type': getMimeType(assetKey),
|
||||
};
|
||||
this.setResponseHeaders(res, nowRequestId, headers);
|
||||
res.end(asset.data);
|
||||
@@ -1273,7 +1316,7 @@ export default class DevServer {
|
||||
Object.assign(parsed.query, uri_args);
|
||||
const path = url.format({
|
||||
pathname: parsed.pathname,
|
||||
query: parsed.query
|
||||
query: parsed.query,
|
||||
});
|
||||
|
||||
const body = await rawBody(req);
|
||||
@@ -1283,7 +1326,7 @@ export default class DevServer {
|
||||
path,
|
||||
headers: this.getNowProxyHeaders(req, nowRequestId),
|
||||
encoding: 'base64',
|
||||
body: body.toString('base64')
|
||||
body: body.toString('base64'),
|
||||
};
|
||||
|
||||
this.output.debug(`Invoking lambda: "${assetKey}" with ${path}`);
|
||||
@@ -1292,7 +1335,7 @@ export default class DevServer {
|
||||
try {
|
||||
result = await asset.fn<InvokeResult>({
|
||||
Action: 'Invoke',
|
||||
body: JSON.stringify(payload)
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -1379,7 +1422,7 @@ export default class DevServer {
|
||||
relative: href,
|
||||
ext,
|
||||
title: href,
|
||||
base
|
||||
base,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1391,13 +1434,13 @@ export default class DevServer {
|
||||
const paths = [
|
||||
{
|
||||
name: directory,
|
||||
url: requestPath
|
||||
}
|
||||
url: requestPath,
|
||||
},
|
||||
];
|
||||
const directoryHtml = directoryTemplate({
|
||||
files,
|
||||
paths,
|
||||
directory
|
||||
directory,
|
||||
});
|
||||
this.setResponseHeaders(res, nowRequestId);
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
@@ -1459,7 +1502,7 @@ function proxyPass(
|
||||
ws: true,
|
||||
xfwd: true,
|
||||
ignorePath: true,
|
||||
target: dest
|
||||
target: dest,
|
||||
});
|
||||
|
||||
proxy.on('error', (error: NodeJS.ErrnoException) => {
|
||||
@@ -1490,7 +1533,7 @@ function serveStaticFile(
|
||||
public: cwd,
|
||||
cleanUrls: false,
|
||||
etag: true,
|
||||
...opts
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1546,7 +1589,7 @@ async function shouldServe(
|
||||
const {
|
||||
src: entrypoint,
|
||||
config,
|
||||
builderWithPkg: { builder }
|
||||
builderWithPkg: { builder },
|
||||
} = match;
|
||||
if (typeof builder.shouldServe === 'function') {
|
||||
const shouldServe = await builder.shouldServe({
|
||||
@@ -1554,7 +1597,7 @@ async function shouldServe(
|
||||
files,
|
||||
config,
|
||||
requestPath,
|
||||
workPath: devServer.cwd
|
||||
workPath: devServer.cwd,
|
||||
});
|
||||
if (shouldServe) {
|
||||
return true;
|
||||
|
||||
@@ -5,7 +5,7 @@ export const version = 2;
|
||||
|
||||
export function build({ files, entrypoint }: BuilderParams): BuildResult {
|
||||
const output = {
|
||||
[entrypoint]: files[entrypoint]
|
||||
[entrypoint]: files[entrypoint],
|
||||
};
|
||||
const watch = [entrypoint];
|
||||
|
||||
@@ -15,7 +15,7 @@ export function build({ files, entrypoint }: BuilderParams): BuildResult {
|
||||
export function shouldServe({
|
||||
entrypoint,
|
||||
files,
|
||||
requestPath
|
||||
requestPath,
|
||||
}: ShouldServeParams) {
|
||||
if (isIndex(entrypoint)) {
|
||||
const indexPath = join(requestPath, basename(entrypoint));
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import http from 'http';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import { Lambda as FunLambda } from '@zeit/fun';
|
||||
import { FileBlob, FileFsRef, Lambda } from '@now/build-utils';
|
||||
import {
|
||||
Builder as BuildConfig,
|
||||
FileBlob,
|
||||
FileFsRef,
|
||||
Lambda,
|
||||
PackageJson,
|
||||
} from '@now/build-utils';
|
||||
import { Output } from '../output';
|
||||
|
||||
export interface DevServerOptions {
|
||||
@@ -13,12 +19,6 @@ export interface EnvConfig {
|
||||
[name: string]: string | undefined;
|
||||
}
|
||||
|
||||
export interface BuildConfig {
|
||||
src: string;
|
||||
use?: string;
|
||||
config?: object;
|
||||
}
|
||||
|
||||
export interface BuildMatch extends BuildConfig {
|
||||
builderWithPkg: BuilderWithPackage;
|
||||
buildOutput: BuilderOutputs;
|
||||
@@ -119,6 +119,7 @@ export interface BuildResult {
|
||||
output: BuilderOutputs;
|
||||
routes: RouteConfig[];
|
||||
watch: string[];
|
||||
distPath?: string;
|
||||
}
|
||||
|
||||
export interface ShouldServeParams {
|
||||
@@ -129,18 +130,10 @@ export interface ShouldServeParams {
|
||||
workPath: string;
|
||||
}
|
||||
|
||||
export interface Package {
|
||||
name: string;
|
||||
version: string;
|
||||
scripts?: { [key: string]: string };
|
||||
dependencies?: { [name: string]: string };
|
||||
devDependencies?: { [name: string]: string };
|
||||
}
|
||||
|
||||
export interface BuilderWithPackage {
|
||||
runInProcess?: boolean;
|
||||
builder: Readonly<Builder>;
|
||||
package: Readonly<Package>;
|
||||
package: Readonly<PackageJson>;
|
||||
}
|
||||
|
||||
export interface HttpHeadersConfig {
|
||||
|
||||
@@ -16,16 +16,16 @@ const buildsSchema = {
|
||||
src: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
maxLength: 4096
|
||||
maxLength: 4096,
|
||||
},
|
||||
use: {
|
||||
type: 'string',
|
||||
minLength: 3,
|
||||
maxLength: 256
|
||||
maxLength: 256,
|
||||
},
|
||||
config: { type: 'object' }
|
||||
}
|
||||
}
|
||||
config: { type: 'object' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const validateBuilds = ajv.compile(buildsSchema);
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
writeFile,
|
||||
statSync,
|
||||
chmodSync,
|
||||
createReadStream
|
||||
createReadStream,
|
||||
} from 'fs-extra';
|
||||
import pipe from 'promisepipe';
|
||||
import { join } from 'path';
|
||||
@@ -63,7 +63,7 @@ async function installYarn(output: Output): Promise<string> {
|
||||
output.debug(`Downloading ${YARN_URL}`);
|
||||
const response = await fetch(YARN_URL, {
|
||||
compress: false,
|
||||
redirect: 'follow'
|
||||
redirect: 'follow',
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
@@ -90,7 +90,7 @@ async function installYarn(output: Output): Promise<string> {
|
||||
'@echo off',
|
||||
'@SETLOCAL',
|
||||
'@SET PATHEXT=%PATHEXT:;.JS;=;%',
|
||||
'node "%~dp0\\yarn" %*'
|
||||
'node "%~dp0\\yarn" %*',
|
||||
].join('\r\n')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import chalk from 'chalk';
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { Response } from 'fetch-h2'
|
||||
import { Response } from 'node-fetch';
|
||||
import { DomainNotFound, InvalidDomain } from '../errors-ts';
|
||||
import Client from '../client';
|
||||
import wait from '../output/wait';
|
||||
|
||||
type JSONResponse = {
|
||||
recordIds: string[]
|
||||
}
|
||||
recordIds: string[];
|
||||
};
|
||||
|
||||
export default async function importZonefile(
|
||||
client: Client,
|
||||
contextName: string,
|
||||
domain: string,
|
||||
zonefilePath: string,
|
||||
zonefilePath: string
|
||||
) {
|
||||
const cancelWait = wait(`Importing Zone file for domain ${domain} under ${chalk.bold(contextName)}`);
|
||||
const cancelWait = wait(
|
||||
`Importing Zone file for domain ${domain} under ${chalk.bold(contextName)}`
|
||||
);
|
||||
const zonefile = readFileSync(resolve(zonefilePath), 'utf8');
|
||||
|
||||
try {
|
||||
@@ -27,7 +29,7 @@ export default async function importZonefile(
|
||||
json: false,
|
||||
});
|
||||
|
||||
const { recordIds } = await res.json() as JSONResponse;
|
||||
const { recordIds } = (await res.json()) as JSONResponse;
|
||||
cancelWait();
|
||||
return recordIds;
|
||||
} catch (error) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import maybeGetDomainByName from './maybe-get-domain-by-name';
|
||||
import purchaseDomainIfAvailable from './purchase-domain-if-available';
|
||||
import verifyDomain from './verify-domain';
|
||||
import extractDomain from '../alias/extract-domain';
|
||||
import isWildcardAlias from '../alias/is-wildcard-alias';
|
||||
|
||||
export default async function setupDomain(
|
||||
output: Output,
|
||||
@@ -34,7 +35,7 @@ export default async function setupDomain(
|
||||
|
||||
if (info) {
|
||||
output.debug(`Domain ${domain} found for the given context`);
|
||||
if (!info.verified) {
|
||||
if (!info.verified || (!info.nsVerifiedAt && isWildcardAlias(alias))) {
|
||||
output.debug(
|
||||
`Domain ${domain} is not verified, trying to perform a verification`
|
||||
);
|
||||
@@ -47,8 +48,17 @@ export default async function setupDomain(
|
||||
output.debug(`Domain ${domain} verification failed`);
|
||||
return verificationResult;
|
||||
}
|
||||
if (!verificationResult.nsVerifiedAt && isWildcardAlias(alias)) {
|
||||
return new ERRORS.DomainNsNotVerifiedForWildcard({
|
||||
domain,
|
||||
nsVerification: {
|
||||
intendedNameservers: verificationResult.intendedNameservers,
|
||||
nameservers: verificationResult.nameservers
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
output.debug(`Domain ${domain} successfuly verified`);
|
||||
output.debug(`Domain ${domain} successfuly verified`);
|
||||
return maybeGetDomainByName(client, contextName, domain) as Promise<
|
||||
Domain
|
||||
>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import bytes from 'bytes';
|
||||
import { Response } from 'fetch-h2';
|
||||
import { Response } from 'node-fetch';
|
||||
import { NowError } from './now-error';
|
||||
import param from './output/param';
|
||||
import cmd from './output/cmd';
|
||||
@@ -53,7 +53,7 @@ export class TeamDeleted extends NowError<'TEAM_DELETED', {}> {
|
||||
message: `Your team was deleted. You can switch to a different one using ${param(
|
||||
'now switch'
|
||||
)}.`,
|
||||
meta: {}
|
||||
meta: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ export class InvalidToken extends NowError<'NOT_AUTHORIZED', {}> {
|
||||
super({
|
||||
code: `NOT_AUTHORIZED`,
|
||||
message: `The specified token is not valid`,
|
||||
meta: {}
|
||||
meta: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ export class MissingUser extends NowError<'MISSING_USER', {}> {
|
||||
super({
|
||||
code: 'MISSING_USER',
|
||||
message: `Not able to load user, missing from response`,
|
||||
meta: {}
|
||||
meta: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ export class DomainAlreadyExists extends NowError<
|
||||
super({
|
||||
code: 'DOMAIN_ALREADY_EXISTS',
|
||||
meta: { domain },
|
||||
message: `The domain ${domain} already exists under a different context.`
|
||||
message: `The domain ${domain} already exists under a different context.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,7 @@ export class DomainPermissionDenied extends NowError<
|
||||
super({
|
||||
code: 'DOMAIN_PERMISSION_DENIED',
|
||||
meta: { domain, context },
|
||||
message: `You don't have access to the domain ${domain} under ${context}.`
|
||||
message: `You don't have access to the domain ${domain} under ${context}.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,7 @@ export class DomainExternal extends NowError<
|
||||
super({
|
||||
code: 'DOMAIN_EXTERNAL',
|
||||
meta: { domain },
|
||||
message: `The domain ${domain} must point to zeit.world.`
|
||||
message: `The domain ${domain} must point to zeit.world.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ export class SourceNotFound extends NowError<'SOURCE_NOT_FOUND', {}> {
|
||||
meta: {},
|
||||
message: `Not able to purchase. Please add a payment method using ${cmd(
|
||||
'now billing add'
|
||||
)}.`
|
||||
)}.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@ export class InvalidTransferAuthCode extends NowError<
|
||||
super({
|
||||
code: 'INVALID_TRANSFER_AUTH_CODE',
|
||||
meta: { domain, authCode },
|
||||
message: `The provided auth code does not match with the one expected by the current registar`
|
||||
message: `The provided auth code does not match with the one expected by the current registar`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -169,7 +169,7 @@ export class DomainRegistrationFailed extends NowError<
|
||||
super({
|
||||
code: 'DOMAIN_REGISTRATION_FAILED',
|
||||
meta: { domain },
|
||||
message
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -185,7 +185,7 @@ export class DomainNotFound extends NowError<
|
||||
super({
|
||||
code: 'DOMAIN_NOT_FOUND',
|
||||
meta: { domain },
|
||||
message: `The domain ${domain} can't be found.`
|
||||
message: `The domain ${domain} can't be found.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ export class DomainNotVerified extends NowError<
|
||||
super({
|
||||
code: 'DOMAIN_NOT_VERIFIED',
|
||||
meta: { domain },
|
||||
message: `The domain ${domain} is not verified.`
|
||||
message: `The domain ${domain} is not verified.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -221,7 +221,7 @@ export class DomainVerificationFailed extends NowError<
|
||||
domain,
|
||||
nsVerification,
|
||||
txtVerification,
|
||||
purchased = false
|
||||
purchased = false,
|
||||
}: {
|
||||
domain: string;
|
||||
nsVerification: NSVerificationError;
|
||||
@@ -231,7 +231,7 @@ export class DomainVerificationFailed extends NowError<
|
||||
super({
|
||||
code: 'DOMAIN_VERIFICATION_FAILED',
|
||||
meta: { domain, nsVerification, txtVerification, purchased },
|
||||
message: `We can't verify the domain ${domain}. Both Name Servers and DNS TXT verifications failed.`
|
||||
message: `We can't verify the domain ${domain}. Both Name Servers and DNS TXT verifications failed.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -252,6 +252,31 @@ export type TXTVerificationError = {
|
||||
values: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* This error is returned when the domain is not verified by nameservers for wildcard alias.
|
||||
*/
|
||||
export class DomainNsNotVerifiedForWildcard extends NowError<
|
||||
'DOMAIN_NS_NOT_VERIFIED_FOR_WILDCARD',
|
||||
{
|
||||
domain: string;
|
||||
nsVerification: NSVerificationError;
|
||||
}
|
||||
> {
|
||||
constructor({
|
||||
domain,
|
||||
nsVerification,
|
||||
}: {
|
||||
domain: string;
|
||||
nsVerification: NSVerificationError;
|
||||
}) {
|
||||
super({
|
||||
code: 'DOMAIN_NS_NOT_VERIFIED_FOR_WILDCARD',
|
||||
meta: { domain, nsVerification },
|
||||
message: `The domain ${domain} is not verified by nameservers for wildcard alias.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when a domain is validated because we tried to add it to an account
|
||||
* via API or for any other reason.
|
||||
@@ -264,7 +289,17 @@ export class InvalidDomain extends NowError<
|
||||
super({
|
||||
code: 'INVALID_DOMAIN',
|
||||
meta: { domain },
|
||||
message: message || `The domain ${domain} is not valid.`
|
||||
message: message || `The domain ${domain} is not valid.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class NotDomainOwner extends NowError<'NOT_DOMAIN_OWNER', {}> {
|
||||
constructor(message: string) {
|
||||
super({
|
||||
code: 'NOT_DOMAIN_OWNER',
|
||||
meta: {},
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -277,7 +312,7 @@ export class InvalidDeploymentId extends NowError<
|
||||
super({
|
||||
code: 'INVALID_DEPLOYMENT_ID',
|
||||
meta: { id },
|
||||
message: `The deployment id "${id}" is not valid.`
|
||||
message: `The deployment id "${id}" is not valid.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -294,7 +329,7 @@ export class UnsupportedTLD extends NowError<
|
||||
super({
|
||||
code: 'UNSUPPORTED_TLD',
|
||||
meta: { domain },
|
||||
message: `The TLD for domain name ${domain} is not supported.`
|
||||
message: `The TLD for domain name ${domain} is not supported.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -311,7 +346,7 @@ export class DomainNotAvailable extends NowError<
|
||||
super({
|
||||
code: 'DOMAIN_NOT_AVAILABLE',
|
||||
meta: { domain },
|
||||
message: `The domain ${domain} is not available to be purchased.`
|
||||
message: `The domain ${domain} is not available to be purchased.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -328,7 +363,7 @@ export class DomainServiceNotAvailable extends NowError<
|
||||
super({
|
||||
code: 'DOMAIN_SERVICE_NOT_AVAILABLE',
|
||||
meta: { domain },
|
||||
message: `The domain purchase is unavailable, try again later.`
|
||||
message: `The domain purchase is unavailable, try again later.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -345,7 +380,7 @@ export class DomainNotTransferable extends NowError<
|
||||
super({
|
||||
code: 'DOMAIN_NOT_TRANSFERABLE',
|
||||
meta: { domain },
|
||||
message: `The domain ${domain} is not available to be transferred.`
|
||||
message: `The domain ${domain} is not available to be transferred.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -361,7 +396,7 @@ export class UnexpectedDomainPurchaseError extends NowError<
|
||||
super({
|
||||
code: 'UNEXPECTED_DOMAIN_PURCHASE_ERROR',
|
||||
meta: { domain },
|
||||
message: `An unexpected error happened while purchasing.`
|
||||
message: `An unexpected error happened while purchasing.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -374,7 +409,7 @@ export class DomainPaymentError extends NowError<'DOMAIN_PAYMENT_ERROR', {}> {
|
||||
super({
|
||||
code: 'DOMAIN_PAYMENT_ERROR',
|
||||
meta: {},
|
||||
message: `Your card was declined.`
|
||||
message: `Your card was declined.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -391,7 +426,7 @@ export class DomainPurchasePending extends NowError<
|
||||
super({
|
||||
code: 'DOMAIN_PURCHASE_PENDING',
|
||||
meta: { domain },
|
||||
message: `The domain purchase for ${domain} is pending.`
|
||||
message: `The domain purchase for ${domain} is pending.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -405,7 +440,7 @@ export class UserAborted extends NowError<'USER_ABORTED', {}> {
|
||||
super({
|
||||
code: 'USER_ABORTED',
|
||||
meta: {},
|
||||
message: `The user aborted the operation.`
|
||||
message: `The user aborted the operation.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -415,7 +450,7 @@ export class CertNotFound extends NowError<'CERT_NOT_FOUND', { id: string }> {
|
||||
super({
|
||||
code: 'CERT_NOT_FOUND',
|
||||
meta: { id },
|
||||
message: `The cert ${id} can't be found.`
|
||||
message: `The cert ${id} can't be found.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -428,7 +463,7 @@ export class CertsPermissionDenied extends NowError<
|
||||
super({
|
||||
code: 'CERTS_PERMISSION_DENIED',
|
||||
meta: { domain },
|
||||
message: `You don't have access to ${domain}'s certs under ${context}.`
|
||||
message: `You don't have access to ${domain}'s certs under ${context}.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -441,7 +476,7 @@ export class CertOrderNotFound extends NowError<
|
||||
super({
|
||||
code: 'CERT_ORDER_NOT_FOUND',
|
||||
meta: { cns },
|
||||
message: `No cert order could be found for cns ${cns.join(' ,')}`
|
||||
message: `No cert order could be found for cns ${cns.join(' ,')}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -459,7 +494,7 @@ export class TooManyRequests extends NowError<
|
||||
super({
|
||||
code: 'TOO_MANY_REQUESTS',
|
||||
meta: { api, retryAfter },
|
||||
message: `Rate limited. Too many requests to the same endpoint.`
|
||||
message: `Rate limited. Too many requests to the same endpoint.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -493,7 +528,7 @@ export class CertError extends NowError<
|
||||
cns,
|
||||
code,
|
||||
message,
|
||||
helpUrl
|
||||
helpUrl,
|
||||
}: {
|
||||
cns: string[];
|
||||
code: CertErrorCode;
|
||||
@@ -503,7 +538,7 @@ export class CertError extends NowError<
|
||||
super({
|
||||
code: `CERT_ERROR`,
|
||||
meta: { cns, code, helpUrl },
|
||||
message
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -522,7 +557,7 @@ export class CertConfigurationError extends NowError<
|
||||
message,
|
||||
external,
|
||||
type,
|
||||
helpUrl
|
||||
helpUrl,
|
||||
}: {
|
||||
cns: string[];
|
||||
message: string;
|
||||
@@ -533,7 +568,7 @@ export class CertConfigurationError extends NowError<
|
||||
super({
|
||||
code: `CERT_CONFIGURATION_ERROR`,
|
||||
meta: { cns, helpUrl, external, type },
|
||||
message
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -550,7 +585,7 @@ export class DeploymentNotFound extends NowError<
|
||||
super({
|
||||
code: 'DEPLOYMENT_NOT_FOUND',
|
||||
meta: { id, context },
|
||||
message: `Can't find the deployment ${id} under the context ${context}`
|
||||
message: `Can't find the deployment ${id} under the context ${context}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -561,13 +596,13 @@ export class DeploymentNotFound extends NowError<
|
||||
*/
|
||||
export class DeploymentNotReady extends NowError<
|
||||
'DEPLOYMENT_NOT_READY',
|
||||
{ url: string; }
|
||||
{ url: string }
|
||||
> {
|
||||
constructor({ url = '' }: { url: string }) {
|
||||
super({
|
||||
code: 'DEPLOYMENT_NOT_READY',
|
||||
meta: { url },
|
||||
message: `The deployment https://${url} is not ready.`
|
||||
message: `The deployment https://${url} is not ready.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -580,7 +615,7 @@ export class DeploymentFailedAliasImpossible extends NowError<
|
||||
super({
|
||||
code: 'DEPLOYMENT_FAILED_ALIAS_IMPOSSIBLE',
|
||||
meta: {},
|
||||
message: `The deployment build has failed and cannot be aliased`
|
||||
message: `The deployment build has failed and cannot be aliased`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -597,7 +632,7 @@ export class DeploymentPermissionDenied extends NowError<
|
||||
super({
|
||||
code: 'DEPLOYMENT_PERMISSION_DENIED',
|
||||
meta: { id, context },
|
||||
message: `You don't have access to the deployment ${id} under ${context}.`
|
||||
message: `You don't have access to the deployment ${id} under ${context}.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -610,7 +645,7 @@ export class DeploymentTypeUnsupported extends NowError<
|
||||
super({
|
||||
code: 'DEPLOYMENT_TYPE_UNSUPPORTED',
|
||||
meta: {},
|
||||
message: `This region only accepts Serverless Docker Deployments`
|
||||
message: `This region only accepts Serverless Docker Deployments`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -624,7 +659,7 @@ export class InvalidAlias extends NowError<'INVALID_ALIAS', { alias: string }> {
|
||||
super({
|
||||
code: 'INVALID_ALIAS',
|
||||
meta: { alias },
|
||||
message: `The given alias ${alias} is not valid`
|
||||
message: `The given alias ${alias} is not valid`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -638,7 +673,7 @@ export class AliasInUse extends NowError<'ALIAS_IN_USE', { alias: string }> {
|
||||
super({
|
||||
code: 'ALIAS_IN_USE',
|
||||
meta: { alias },
|
||||
message: `The alias is already in use`
|
||||
message: `The alias is already in use`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -653,7 +688,7 @@ export class CertMissing extends NowError<'ALIAS_IN_USE', { domain: string }> {
|
||||
super({
|
||||
code: 'ALIAS_IN_USE',
|
||||
meta: { domain },
|
||||
message: `The alias is already in use`
|
||||
message: `The alias is already in use`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -666,7 +701,7 @@ export class ForbiddenScaleMinInstances extends NowError<
|
||||
super({
|
||||
code: 'FORBIDDEN_SCALE_MIN_INSTANCES',
|
||||
meta: { url, max },
|
||||
message: `You can't scale to more than ${max} min instances with your current plan.`
|
||||
message: `You can't scale to more than ${max} min instances with your current plan.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -679,7 +714,7 @@ export class ForbiddenScaleMaxInstances extends NowError<
|
||||
super({
|
||||
code: 'FORBIDDEN_SCALE_MAX_INSTANCES',
|
||||
meta: { url, max },
|
||||
message: `You can't scale to more than ${max} max instances with your current plan.`
|
||||
message: `You can't scale to more than ${max} max instances with your current plan.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -692,7 +727,7 @@ export class InvalidScaleMinMaxRelation extends NowError<
|
||||
super({
|
||||
code: 'INVALID_SCALE_MIN_MAX_RELATION',
|
||||
meta: { url },
|
||||
message: `Min number of instances can't be higher than max.`
|
||||
message: `Min number of instances can't be higher than max.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -705,7 +740,7 @@ export class NotSupportedMinScaleSlots extends NowError<
|
||||
super({
|
||||
code: 'NOT_SUPPORTED_MIN_SCALE_SLOTS',
|
||||
meta: { url },
|
||||
message: `Cloud v2 does not yet support setting a non-zero min scale setting.`
|
||||
message: `Cloud v2 does not yet support setting a non-zero min scale setting.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -718,7 +753,7 @@ export class VerifyScaleTimeout extends NowError<
|
||||
super({
|
||||
code: 'VERIFY_SCALE_TIMEOUT',
|
||||
meta: { timeout },
|
||||
message: `Instance verification timed out (${timeout}ms)`
|
||||
message: `Instance verification timed out (${timeout}ms)`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -731,7 +766,7 @@ export class CantParseJSONFile extends NowError<
|
||||
super({
|
||||
code: 'CANT_PARSE_JSON_FILE',
|
||||
meta: { file },
|
||||
message: `Can't parse json file`
|
||||
message: `Can't parse json file`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -744,7 +779,7 @@ export class CantFindConfig extends NowError<
|
||||
super({
|
||||
code: 'CANT_FIND_CONFIG',
|
||||
meta: { paths },
|
||||
message: `Can't find a configuration file in the given locations.`
|
||||
message: `Can't find a configuration file in the given locations.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -754,7 +789,7 @@ export class FileNotFound extends NowError<'FILE_NOT_FOUND', { file: string }> {
|
||||
super({
|
||||
code: 'FILE_NOT_FOUND',
|
||||
meta: { file },
|
||||
message: `Can't find a file in provided location '${file}'.`
|
||||
message: `Can't find a file in provided location '${file}'.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -767,7 +802,7 @@ export class RulesFileValidationError extends NowError<
|
||||
super({
|
||||
code: 'PATH_ALIAS_VALIDATION_ERROR',
|
||||
meta: { location, message },
|
||||
message: `The provided rules format in file for path alias are invalid`
|
||||
message: `The provided rules format in file for path alias are invalid`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -777,7 +812,7 @@ export class NoAliasInConfig extends NowError<'NO_ALIAS_IN_CONFIG', {}> {
|
||||
super({
|
||||
code: 'NO_ALIAS_IN_CONFIG',
|
||||
meta: {},
|
||||
message: `There is no alias set up in config file.`
|
||||
message: `There is no alias set up in config file.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -790,7 +825,7 @@ export class InvalidAliasInConfig extends NowError<
|
||||
super({
|
||||
code: 'INVALID_ALIAS_IN_CONFIG',
|
||||
meta: { value },
|
||||
message: `Invalid alias option in configuration.`
|
||||
message: `Invalid alias option in configuration.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -803,7 +838,7 @@ export class RuleValidationFailed extends NowError<
|
||||
super({
|
||||
code: 'RULE_VALIDATION_FAILED',
|
||||
meta: { message },
|
||||
message: `The server validation for rules failed`
|
||||
message: `The server validation for rules failed`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -816,7 +851,7 @@ export class InvalidMinForScale extends NowError<
|
||||
super({
|
||||
code: 'INVALID_MIN_FOR_SCALE',
|
||||
meta: { value },
|
||||
message: `Invalid <min> parameter "${value}". A number or "auto" were expected`
|
||||
message: `Invalid <min> parameter "${value}". A number or "auto" were expected`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -829,7 +864,7 @@ export class InvalidArgsForMinMaxScale extends NowError<
|
||||
super({
|
||||
code: 'INVALID_ARGS_FOR_MIN_MAX_SCALE',
|
||||
meta: { min },
|
||||
message: `Invalid number of arguments: expected <min> ("${min}") and [max]`
|
||||
message: `Invalid number of arguments: expected <min> ("${min}") and [max]`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -842,7 +877,7 @@ export class InvalidMaxForScale extends NowError<
|
||||
super({
|
||||
code: 'INVALID_MAX_FOR_SCALE',
|
||||
meta: { value },
|
||||
message: `Invalid <max> parameter "${value}". A number or "auto" were expected`
|
||||
message: `Invalid <max> parameter "${value}". A number or "auto" were expected`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -852,7 +887,7 @@ export class InvalidCert extends NowError<'INVALID_CERT', {}> {
|
||||
super({
|
||||
code: 'INVALID_CERT',
|
||||
meta: {},
|
||||
message: `The provided custom certificate is invalid and couldn't be added`
|
||||
message: `The provided custom certificate is invalid and couldn't be added`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -865,7 +900,7 @@ export class DNSPermissionDenied extends NowError<
|
||||
super({
|
||||
code: 'DNS_PERMISSION_DENIED',
|
||||
meta: { domain },
|
||||
message: `You don't have access to the DNS records of ${domain}.`
|
||||
message: `You don't have access to the DNS records of ${domain}.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -875,7 +910,7 @@ export class DNSInvalidPort extends NowError<'DNS_INVALID_PORT', {}> {
|
||||
super({
|
||||
code: 'DNS_INVALID_PORT',
|
||||
meta: {},
|
||||
message: `Invalid <port> parameter. A number was expected`
|
||||
message: `Invalid <port> parameter. A number was expected`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -888,7 +923,7 @@ export class DNSInvalidType extends NowError<
|
||||
super({
|
||||
code: 'DNS_INVALID_TYPE',
|
||||
meta: { type },
|
||||
message: `Invalid <type> parameter "${type}". Expected one of A, AAAA, ALIAS, CAA, CNAME, MX, SRV, TXT`
|
||||
message: `Invalid <type> parameter "${type}". Expected one of A, AAAA, ALIAS, CAA, CNAME, MX, SRV, TXT`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -901,7 +936,7 @@ export class DNSConflictingRecord extends NowError<
|
||||
super({
|
||||
code: 'DNS_CONFLICTING_RECORD',
|
||||
meta: { record },
|
||||
message: ` A conflicting record exists "${record}".`
|
||||
message: ` A conflicting record exists "${record}".`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -924,7 +959,7 @@ export class DomainRemovalConflict extends NowError<
|
||||
pendingAsyncPurchase,
|
||||
resolvable,
|
||||
suffix,
|
||||
transferring
|
||||
transferring,
|
||||
}: {
|
||||
aliases: string[];
|
||||
certs: string[];
|
||||
@@ -942,9 +977,9 @@ export class DomainRemovalConflict extends NowError<
|
||||
pendingAsyncPurchase,
|
||||
suffix,
|
||||
transferring,
|
||||
resolvable
|
||||
resolvable,
|
||||
},
|
||||
message
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -957,7 +992,7 @@ export class DomainMoveConflict extends NowError<
|
||||
message,
|
||||
pendingAsyncPurchase,
|
||||
resolvable,
|
||||
suffix
|
||||
suffix,
|
||||
}: {
|
||||
message: string;
|
||||
pendingAsyncPurchase: boolean;
|
||||
@@ -969,9 +1004,9 @@ export class DomainMoveConflict extends NowError<
|
||||
meta: {
|
||||
pendingAsyncPurchase,
|
||||
resolvable,
|
||||
suffix
|
||||
suffix,
|
||||
},
|
||||
message
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -981,17 +1016,23 @@ export class InvalidEmail extends NowError<'INVALID_EMAIL', { email: string }> {
|
||||
super({
|
||||
code: 'INVALID_EMAIL',
|
||||
message,
|
||||
meta: { email }
|
||||
meta: { email },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountNotFound extends NowError<'ACCOUNT_NOT_FOUND', { email: string }> {
|
||||
constructor(email: string, message: string = `Please sign up: https://zeit.co/signup`) {
|
||||
export class AccountNotFound extends NowError<
|
||||
'ACCOUNT_NOT_FOUND',
|
||||
{ email: string }
|
||||
> {
|
||||
constructor(
|
||||
email: string,
|
||||
message: string = `Please sign up: https://zeit.co/signup`
|
||||
) {
|
||||
super({
|
||||
code: 'ACCOUNT_NOT_FOUND',
|
||||
message,
|
||||
meta: { email }
|
||||
meta: { email },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1004,7 +1045,7 @@ export class InvalidMoveDestination extends NowError<
|
||||
super({
|
||||
code: 'INVALID_MOVE_DESTINATION',
|
||||
message: `Invalid move destination "${destination}"`,
|
||||
meta: { destination }
|
||||
meta: { destination },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1017,7 +1058,7 @@ export class InvalidMoveToken extends NowError<
|
||||
super({
|
||||
code: 'INVALID_MOVE_TOKEN',
|
||||
message: `Invalid move token "${token}"`,
|
||||
meta: { token }
|
||||
meta: { token },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1027,7 +1068,7 @@ export class NoBuilderCacheError extends NowError<'NO_BUILDER_CACHE', {}> {
|
||||
super({
|
||||
code: 'NO_BUILDER_CACHE',
|
||||
message: 'Could not find cache directory for now-builders.',
|
||||
meta: {}
|
||||
meta: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1040,7 +1081,7 @@ export class BuilderCacheCleanError extends NowError<
|
||||
super({
|
||||
code: 'BUILDER_CACHE_CLEAN_FAILED',
|
||||
message: `Error cleaning builder cache: ${message}`,
|
||||
meta: { path }
|
||||
meta: { path },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1057,7 +1098,7 @@ export class LambdaSizeExceededError extends NowError<
|
||||
).toLowerCase()}) exceeds the maximum size limit (${bytes(
|
||||
maxLambdaSize
|
||||
).toLowerCase()}). Learn more: https://zeit.co/docs/v2/deployments/concepts/lambdas/#maximum-bundle-size`,
|
||||
meta: { size, maxLambdaSize }
|
||||
meta: { size, maxLambdaSize },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1076,7 +1117,7 @@ export class MissingDotenvVarsError extends NowError<
|
||||
} else {
|
||||
message = [
|
||||
`The following env vars are not defined in ${code(type)} file:`,
|
||||
...missing.map(name => ` - ${JSON.stringify(name)}`)
|
||||
...missing.map(name => ` - ${JSON.stringify(name)}`),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
@@ -1085,17 +1126,20 @@ export class MissingDotenvVarsError extends NowError<
|
||||
super({
|
||||
code: 'MISSING_DOTENV_VARS',
|
||||
message,
|
||||
meta: { type, missing }
|
||||
meta: { type, missing },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class DeploymentsRateLimited extends NowError<'DEPLOYMENTS_RATE_LIMITED', {}> {
|
||||
export class DeploymentsRateLimited extends NowError<
|
||||
'DEPLOYMENTS_RATE_LIMITED',
|
||||
{}
|
||||
> {
|
||||
constructor(message: string) {
|
||||
super({
|
||||
code: 'DEPLOYMENTS_RATE_LIMITED',
|
||||
meta: {},
|
||||
message
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1105,7 +1149,7 @@ export class BuildsRateLimited extends NowError<'BUILDS_RATE_LIMITED', {}> {
|
||||
super({
|
||||
code: 'BUILDS_RATE_LIMITED',
|
||||
meta: {},
|
||||
message
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1115,47 +1159,66 @@ export class ProjectNotFound extends NowError<'PROJECT_NOT_FOUND', {}> {
|
||||
super({
|
||||
code: 'PROJECT_NOT_FOUND',
|
||||
meta: {},
|
||||
message: `There is no project for "${nameOrId}"`
|
||||
message: `There is no project for "${nameOrId}"`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class AliasDomainConfigured extends NowError<'DOMAIN_CONFIGURED', {}> {
|
||||
constructor({ message }: { message: string; }) {
|
||||
constructor({ message }: { message: string }) {
|
||||
super({
|
||||
code: 'DOMAIN_CONFIGURED',
|
||||
meta: {},
|
||||
message
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingBuildScript extends NowError<'MISSING_BUILD_SCRIPT', {}> {
|
||||
constructor({ message }: { message: string; }) {
|
||||
constructor({ message }: { message: string }) {
|
||||
super({
|
||||
code: 'MISSING_BUILD_SCRIPT',
|
||||
meta: {},
|
||||
message
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictingFilePath extends NowError<'CONFLICTING_FILE_PATH', {}> {
|
||||
constructor({ message }: { message: string; }) {
|
||||
constructor({ message }: { message: string }) {
|
||||
super({
|
||||
code: 'CONFLICTING_FILE_PATH',
|
||||
meta: {},
|
||||
message
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictingPathSegment extends NowError<'CONFLICTING_PATH_SEGMENT', {}> {
|
||||
constructor({ message }: { message: string; }) {
|
||||
export class ConflictingPathSegment extends NowError<
|
||||
'CONFLICTING_PATH_SEGMENT',
|
||||
{}
|
||||
> {
|
||||
constructor({ message }: { message: string }) {
|
||||
super({
|
||||
code: 'CONFLICTING_PATH_SEGMENT',
|
||||
meta: {},
|
||||
message
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class BuildError extends NowError<'BUILD_ERROR', {}> {
|
||||
constructor({
|
||||
message,
|
||||
meta,
|
||||
}: {
|
||||
message: string;
|
||||
meta: { entrypoint: string };
|
||||
}) {
|
||||
super({
|
||||
code: 'BUILD_ERROR',
|
||||
meta,
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ export default async function getConfig(output: Output, configFile?: string) {
|
||||
if (config) {
|
||||
return config;
|
||||
}
|
||||
|
||||
// First try with the config supplied by the user via --local-config
|
||||
if (configFile) {
|
||||
const localFilePath = path.resolve(localPath, configFile);
|
||||
@@ -27,8 +26,7 @@ export default async function getConfig(output: Output, configFile?: string) {
|
||||
return localConfig;
|
||||
}
|
||||
if (localConfig !== null) {
|
||||
const castedConfig = localConfig;
|
||||
config = castedConfig;
|
||||
config = localConfig;
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ export default function handleError(
|
||||
|
||||
if ((<APIError>error).status === 403) {
|
||||
console.error(
|
||||
errorOutput('Authentication error. Run `now login` to log-in again.')
|
||||
errorOutput(
|
||||
error.message ||
|
||||
'Authentication error. Run `now login` to log-in again.'
|
||||
)
|
||||
);
|
||||
} else if ((<APIError>error).status === 429) {
|
||||
// Rate limited: display the message from the server-side,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { homedir } from 'os';
|
||||
import { resolve as resolvePath, join, basename } from 'path';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import EventEmitter from 'events';
|
||||
import qs from 'querystring';
|
||||
import { parse as parseUrl } from 'url';
|
||||
@@ -7,24 +7,22 @@ import bytes from 'bytes';
|
||||
import chalk from 'chalk';
|
||||
import retry from 'async-retry';
|
||||
import { parse as parseIni } from 'ini';
|
||||
import { createReadStream } from 'fs';
|
||||
import fs from 'fs-extra';
|
||||
import ms from 'ms';
|
||||
import fetch from 'node-fetch';
|
||||
import { URLSearchParams } from 'url';
|
||||
import {
|
||||
staticFiles as getFiles,
|
||||
npm as getNpmFiles,
|
||||
docker as getDockerFiles
|
||||
docker as getDockerFiles,
|
||||
} from './get-files';
|
||||
import Agent from './agent.ts';
|
||||
import ua from './ua.ts';
|
||||
import hash from './hash';
|
||||
import processDeployment from './deploy/process-deployment.ts';
|
||||
import highlight from './output/highlight';
|
||||
import createOutput from './output';
|
||||
import { responseError } from './error';
|
||||
|
||||
// How many concurrent HTTP/2 stream uploads
|
||||
const MAX_CONCURRENT = 50;
|
||||
import stamp from './output/stamp';
|
||||
import { BuildError } from './errors-ts';
|
||||
|
||||
// Check if running windows
|
||||
const IS_WIN = process.platform.startsWith('win');
|
||||
@@ -39,14 +37,8 @@ export default class Now extends EventEmitter {
|
||||
this._forceNew = forceNew;
|
||||
this._output = createOutput({ debug });
|
||||
this._apiUrl = apiUrl;
|
||||
this._agent = new Agent(apiUrl, { debug });
|
||||
this._onRetry = this._onRetry.bind(this);
|
||||
this.currentTeam = currentTeam;
|
||||
const closeAgent = () => {
|
||||
this._agent.close();
|
||||
process.removeListener('nowExit', closeAgent);
|
||||
};
|
||||
process.on('nowExit', closeAgent);
|
||||
}
|
||||
|
||||
async create(
|
||||
@@ -61,7 +53,6 @@ export default class Now extends EventEmitter {
|
||||
nowConfig = {},
|
||||
hasNowJson = false,
|
||||
sessionAffinity = 'random',
|
||||
isFile = false,
|
||||
atlas = false,
|
||||
|
||||
// Latest
|
||||
@@ -73,361 +64,152 @@ export default class Now extends EventEmitter {
|
||||
quiet = false,
|
||||
env,
|
||||
build,
|
||||
followSymlinks = true,
|
||||
forceNew = false,
|
||||
target = null
|
||||
target = null,
|
||||
deployStamp,
|
||||
}
|
||||
) {
|
||||
const { log, warn, time } = this._output;
|
||||
const opts = { output: this._output, hasNowJson };
|
||||
const { log, warn, debug } = this._output;
|
||||
const isBuilds = type === null;
|
||||
|
||||
let files = [];
|
||||
let hashes = {};
|
||||
const relatives = {};
|
||||
let engines;
|
||||
let deployment;
|
||||
let requestBody = {};
|
||||
|
||||
await time('Getting files', async () => {
|
||||
const opts = { output: this._output, hasNowJson };
|
||||
if (isBuilds) {
|
||||
requestBody = {
|
||||
token: this._token,
|
||||
teamId: this.currentTeam,
|
||||
env,
|
||||
build,
|
||||
public: wantsPublic || nowConfig.public,
|
||||
name,
|
||||
project,
|
||||
meta,
|
||||
regions,
|
||||
force: forceNew,
|
||||
};
|
||||
|
||||
if (type === 'npm') {
|
||||
files = await getNpmFiles(paths[0], pkg, nowConfig, opts);
|
||||
if (target) {
|
||||
requestBody.target = target;
|
||||
}
|
||||
} else if (type === 'npm') {
|
||||
files = await getNpmFiles(paths[0], pkg, nowConfig, opts);
|
||||
|
||||
// A `start` or `now-start` npm script, or a `server.js` file
|
||||
// in the root directory of the deployment are required
|
||||
if (
|
||||
!isBuilds &&
|
||||
!hasNpmStart(pkg) &&
|
||||
!hasFile(paths[0], files, 'server.js')
|
||||
) {
|
||||
const err = new Error(
|
||||
'Missing `start` (or `now-start`) script in `package.json`. ' +
|
||||
'See: https://docs.npmjs.com/cli/start'
|
||||
);
|
||||
throw err;
|
||||
// A `start` or `now-start` npm script, or a `server.js` file
|
||||
// in the root directory of the deployment are required
|
||||
if (
|
||||
!isBuilds &&
|
||||
!hasNpmStart(pkg) &&
|
||||
!hasFile(paths[0], files, 'server.js')
|
||||
) {
|
||||
const err = new Error(
|
||||
'Missing `start` (or `now-start`) script in `package.json`. ' +
|
||||
'See: https://docs.npmjs.com/cli/start'
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
|
||||
engines = nowConfig.engines || pkg.engines;
|
||||
forwardNpm = forwardNpm || nowConfig.forwardNpm;
|
||||
} else if (type === 'static') {
|
||||
if (paths.length === 1) {
|
||||
files = await getFiles(paths[0], nowConfig, opts);
|
||||
} else {
|
||||
if (!files) {
|
||||
files = [];
|
||||
}
|
||||
|
||||
engines = nowConfig.engines || pkg.engines;
|
||||
forwardNpm = forwardNpm || nowConfig.forwardNpm;
|
||||
} else if (type === 'static') {
|
||||
if (isFile) {
|
||||
files = [resolvePath(paths[0])];
|
||||
} else if (paths.length === 1) {
|
||||
files = await getFiles(paths[0], nowConfig, opts);
|
||||
} else {
|
||||
if (!files) {
|
||||
files = [];
|
||||
}
|
||||
for (const path of paths) {
|
||||
const list = await getFiles(path, {}, opts);
|
||||
files = files.concat(list);
|
||||
|
||||
for (const path of paths) {
|
||||
const list = await getFiles(path, {}, opts);
|
||||
files = files.concat(list);
|
||||
|
||||
for (const file of list) {
|
||||
relatives[file] = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type === 'docker') {
|
||||
files = await getDockerFiles(paths[0], nowConfig, opts);
|
||||
} else if (isBuilds) {
|
||||
opts.isBuilds = isBuilds;
|
||||
|
||||
if (isFile) {
|
||||
files = [resolvePath(paths[0])];
|
||||
} else if (paths.length === 1) {
|
||||
files = await getFiles(paths[0], {}, opts);
|
||||
} else {
|
||||
if (!files) {
|
||||
files = [];
|
||||
}
|
||||
|
||||
for (const path of paths) {
|
||||
const list = await getFiles(path, {}, opts);
|
||||
files = files.concat(list);
|
||||
|
||||
for (const file of list) {
|
||||
relatives[file] = path;
|
||||
}
|
||||
for (const file of list) {
|
||||
relatives[file] = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Read `registry.npmjs.org` authToken from .npmrc
|
||||
let authToken;
|
||||
|
||||
if (type === 'npm' && forwardNpm) {
|
||||
authToken =
|
||||
(await readAuthToken(paths[0])) || (await readAuthToken(homedir()));
|
||||
} else if (type === 'docker') {
|
||||
files = await getDockerFiles(paths[0], nowConfig, opts);
|
||||
}
|
||||
|
||||
const hashes = await time('Computing hashes', () => {
|
||||
const pkgDetails = Object.assign({ name }, pkg);
|
||||
return hash(files, pkgDetails);
|
||||
});
|
||||
const uploadStamp = stamp();
|
||||
|
||||
this._files = hashes;
|
||||
if (isBuilds) {
|
||||
deployment = await processDeployment({
|
||||
now: this,
|
||||
output: this._output,
|
||||
hashes,
|
||||
paths,
|
||||
requestBody,
|
||||
uploadStamp,
|
||||
deployStamp,
|
||||
quiet,
|
||||
nowConfig,
|
||||
});
|
||||
} else {
|
||||
// Read `registry.npmjs.org` authToken from .npmrc
|
||||
let authToken;
|
||||
|
||||
const deployment = await this.retry(async bail => {
|
||||
// Flatten the array to contain files to sync where each nested input
|
||||
// array has a group of files with the same sha but different path
|
||||
const files = await time(
|
||||
'Get files ready for deployment',
|
||||
Promise.all(
|
||||
Array.prototype.concat.apply(
|
||||
[],
|
||||
await Promise.all(
|
||||
Array.from(this._files).map(async ([sha, { data, names }]) => {
|
||||
const statFn = followSymlinks ? fs.stat : fs.lstat;
|
||||
|
||||
return names.map(async name => {
|
||||
const getMode = async () => {
|
||||
const st = await statFn(name);
|
||||
return st.mode;
|
||||
};
|
||||
|
||||
const mode = await getMode();
|
||||
const multipleStatic = Object.keys(relatives).length !== 0;
|
||||
|
||||
let file;
|
||||
|
||||
if (isFile) {
|
||||
file = basename(paths[0]);
|
||||
} else if (multipleStatic) {
|
||||
file = toRelative(name, join(relatives[name], '..'));
|
||||
} else {
|
||||
file = toRelative(name, paths[0]);
|
||||
}
|
||||
|
||||
return {
|
||||
sha,
|
||||
size: data.length,
|
||||
file,
|
||||
mode
|
||||
};
|
||||
});
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// This is a useful warning because it prevents people
|
||||
// from getting confused about a deployment that renders 404.
|
||||
if (
|
||||
files.length === 0 ||
|
||||
files.every(item => item.file.startsWith('.'))
|
||||
) {
|
||||
warn(
|
||||
'There are no files (or only files starting with a dot) inside your deployment.'
|
||||
);
|
||||
if (type === 'npm' && forwardNpm) {
|
||||
authToken =
|
||||
(await readAuthToken(paths[0])) || (await readAuthToken(homedir()));
|
||||
}
|
||||
|
||||
const queryProps = {};
|
||||
const requestBody = isBuilds
|
||||
? {
|
||||
version: 2,
|
||||
env,
|
||||
build,
|
||||
public: wantsPublic || nowConfig.public,
|
||||
name,
|
||||
project,
|
||||
files,
|
||||
meta,
|
||||
regions
|
||||
}
|
||||
: {
|
||||
env,
|
||||
build,
|
||||
meta,
|
||||
public: wantsPublic || nowConfig.public,
|
||||
forceNew,
|
||||
name,
|
||||
project,
|
||||
description,
|
||||
deploymentType: type,
|
||||
registryAuthToken: authToken,
|
||||
files,
|
||||
engines,
|
||||
scale,
|
||||
sessionAffinity,
|
||||
limits: nowConfig.limits,
|
||||
atlas
|
||||
};
|
||||
requestBody = {
|
||||
token: this._token,
|
||||
teamId: this.currentTeam,
|
||||
env,
|
||||
build,
|
||||
meta,
|
||||
public: wantsPublic || nowConfig.public,
|
||||
forceNew,
|
||||
name,
|
||||
project,
|
||||
description,
|
||||
deploymentType: type,
|
||||
registryAuthToken: authToken,
|
||||
engines,
|
||||
scale,
|
||||
sessionAffinity,
|
||||
limits: nowConfig.limits,
|
||||
atlas,
|
||||
config: nowConfig,
|
||||
};
|
||||
|
||||
if (Object.keys(nowConfig).length > 0) {
|
||||
if (isBuilds) {
|
||||
// These properties are only used inside Now CLI and
|
||||
// are not supported on the API.
|
||||
const exclude = ['github', 'scope'];
|
||||
|
||||
// Request properties that are made of a combination of
|
||||
// command flags and config properties were already set
|
||||
// earlier. Here, we are setting request properties that
|
||||
// are purely made of their equally-named config property.
|
||||
for (const key of Object.keys(nowConfig)) {
|
||||
const value = nowConfig[key];
|
||||
|
||||
if (!requestBody[key] && !exclude.includes(key)) {
|
||||
requestBody[key] = value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
requestBody.config = nowConfig;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBuilds) {
|
||||
if (forceNew) {
|
||||
queryProps.forceNew = 1;
|
||||
}
|
||||
|
||||
if (target) {
|
||||
requestBody.target = target;
|
||||
}
|
||||
|
||||
if (isFile) {
|
||||
requestBody.routes = [
|
||||
{
|
||||
src: '/',
|
||||
dest: `/${files[0].file}`
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const query = qs.stringify(queryProps);
|
||||
const version = isBuilds ? 'v9' : 'v4';
|
||||
|
||||
const res = await this._fetch(
|
||||
`/${version}/now/deployments${query ? `?${query}` : ''}`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: requestBody
|
||||
}
|
||||
);
|
||||
|
||||
// No retry on 4xx
|
||||
let body;
|
||||
|
||||
try {
|
||||
body = await res.json();
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Unexpected response error: ${err.message} (${
|
||||
res.status
|
||||
} status code)`
|
||||
);
|
||||
}
|
||||
|
||||
if (res.status === 429) {
|
||||
if (body.error && body.error.code === 'builds_rate_limited') {
|
||||
const err = new Error(body.error.message);
|
||||
err.status = res.status;
|
||||
err.retryAfter = 'never';
|
||||
err.code = body.error.code;
|
||||
|
||||
return bail(err);
|
||||
}
|
||||
|
||||
let msg = 'You have been creating deployments at a very fast pace. ';
|
||||
|
||||
if (body.error && body.error.limit && body.error.limit.reset) {
|
||||
const { reset } = body.error.limit;
|
||||
const difference = reset * 1000 - Date.now();
|
||||
|
||||
msg += `Please retry in ${ms(difference, { long: true })}.`;
|
||||
} else {
|
||||
msg += 'Please slow down.';
|
||||
}
|
||||
|
||||
const err = new Error(msg);
|
||||
|
||||
err.status = res.status;
|
||||
err.retryAfter = 'never';
|
||||
|
||||
return bail(err);
|
||||
}
|
||||
|
||||
// If the deployment domain is missing a cert, bail with the error
|
||||
if (
|
||||
res.status === 400 &&
|
||||
body.error &&
|
||||
body.error.code === 'cert_missing'
|
||||
) {
|
||||
bail(await responseError(res, null, body));
|
||||
}
|
||||
|
||||
if (
|
||||
res.status === 400 &&
|
||||
body.error &&
|
||||
body.error.code === 'missing_files'
|
||||
) {
|
||||
return body;
|
||||
}
|
||||
|
||||
if (res.status === 404 && body.error && body.error.code === 'not_found') {
|
||||
return body;
|
||||
}
|
||||
|
||||
if (res.status >= 400 && res.status < 500) {
|
||||
const err = new Error();
|
||||
|
||||
if (body.error) {
|
||||
const { code, unreferencedBuildSpecs } = body.error;
|
||||
|
||||
if (code === 'env_value_invalid_type') {
|
||||
const { key } = body.error;
|
||||
err.message =
|
||||
`The env key ${key} has an invalid type: ${typeof env[key]}. ` +
|
||||
'Please supply a String or a Number (https://err.sh/now/env-value-invalid-type)';
|
||||
} else if (code === 'unreferenced_build_specifications') {
|
||||
const count = unreferencedBuildSpecs.length;
|
||||
const prefix = count === 1 ? 'build' : 'builds';
|
||||
|
||||
err.message =
|
||||
`You defined ${count} ${prefix} that did not match any source files (please ensure they are NOT defined in ${highlight(
|
||||
'.nowignore'
|
||||
)}):` +
|
||||
`\n- ${unreferencedBuildSpecs
|
||||
.map(item => JSON.stringify(item))
|
||||
.join('\n- ')}`;
|
||||
} else {
|
||||
Object.assign(err, body.error);
|
||||
}
|
||||
} else {
|
||||
err.message = 'Not able to create deployment';
|
||||
}
|
||||
|
||||
return bail(err);
|
||||
}
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error(body.error.message);
|
||||
}
|
||||
|
||||
for (const [name, value] of res.headers.entries()) {
|
||||
if (name.startsWith('x-now-warning-')) {
|
||||
this._output.warn(value);
|
||||
}
|
||||
}
|
||||
|
||||
return body;
|
||||
});
|
||||
deployment = await processDeployment({
|
||||
legacy: true,
|
||||
now: this,
|
||||
output: this._output,
|
||||
hashes,
|
||||
paths,
|
||||
requestBody,
|
||||
uploadStamp,
|
||||
deployStamp,
|
||||
quiet,
|
||||
env,
|
||||
nowConfig,
|
||||
});
|
||||
}
|
||||
|
||||
// We report about files whose sizes are too big
|
||||
let missingVersion = false;
|
||||
|
||||
if (deployment.warnings) {
|
||||
if (deployment && deployment.warnings) {
|
||||
let sizeExceeded = 0;
|
||||
|
||||
deployment.warnings.forEach(warning => {
|
||||
if (warning.reason === 'size_limit_exceeded') {
|
||||
const { sha, limit } = warning;
|
||||
const n = hashes.get(sha).names.pop();
|
||||
const n = hashes[sha].names.pop();
|
||||
|
||||
warn(`Skipping file ${n} (size exceeded ${bytes(limit)}`);
|
||||
|
||||
hashes.get(sha).names.unshift(n); // Move name (hack, if duplicate matches we report them in order)
|
||||
hashes[sha].names.unshift(n); // Move name (hack, if duplicate matches we report them in order)
|
||||
sizeExceeded++;
|
||||
} else if (warning.reason === 'node_version_not_found') {
|
||||
warn(`Requested node version ${warning.wanted} is not available`);
|
||||
@@ -445,19 +227,10 @@ export default class Now extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
if (deployment.error && deployment.error.code === 'missing_files') {
|
||||
this._missing = deployment.error.missing || [];
|
||||
this._fileCount = files.length;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isBuilds && !quiet && type === 'npm' && deployment.nodeVersion) {
|
||||
if (engines && engines.node && !missingVersion) {
|
||||
log(
|
||||
chalk`Using Node.js {bold ${
|
||||
deployment.nodeVersion
|
||||
}} (requested: {dim \`${engines.node}\`})`
|
||||
chalk`Using Node.js {bold ${deployment.nodeVersion}} (requested: {dim \`${engines.node}\`})`
|
||||
);
|
||||
} else {
|
||||
log(chalk`Using Node.js {bold ${deployment.nodeVersion}} (default)`);
|
||||
@@ -472,81 +245,90 @@ export default class Now extends EventEmitter {
|
||||
return deployment;
|
||||
}
|
||||
|
||||
upload({ atlas = false, scale = {} } = {}) {
|
||||
const { debug, time } = this._output;
|
||||
debug(`Will upload ${this._missing.length} files`);
|
||||
async handleDeploymentError(error, { hashes, env }) {
|
||||
if (error.status === 429) {
|
||||
if (error.code === 'builds_rate_limited') {
|
||||
const err = new Error(error.message);
|
||||
err.status = error.status;
|
||||
err.retryAfter = 'never';
|
||||
err.code = error.code;
|
||||
|
||||
this._agent.setConcurrency({
|
||||
maxStreams: MAX_CONCURRENT,
|
||||
capacity: this._missing.length
|
||||
});
|
||||
return err;
|
||||
}
|
||||
|
||||
time(
|
||||
'Uploading files',
|
||||
Promise.all(
|
||||
this._missing.map(sha =>
|
||||
retry(
|
||||
async bail => {
|
||||
const file = this._files.get(sha);
|
||||
const fPath = file.names[0];
|
||||
const stream = createReadStream(fPath);
|
||||
const { data } = file;
|
||||
let msg = 'You have been creating deployments at a very fast pace. ';
|
||||
|
||||
const fstreamPush = stream.push;
|
||||
if (error.limit && error.limit.reset) {
|
||||
const { reset } = error.limit;
|
||||
const difference = reset * 1000 - Date.now();
|
||||
|
||||
let uploadedSoFar = 0;
|
||||
stream.push = chunk => {
|
||||
// If we're about to push the last chunk, then don't do it here
|
||||
// But instead, we'll "hang" the progress bar and do it on 200
|
||||
if (chunk && uploadedSoFar + chunk.length < data.length) {
|
||||
this.emit('uploadProgress', chunk.length);
|
||||
uploadedSoFar += chunk.length;
|
||||
}
|
||||
return fstreamPush.call(stream, chunk);
|
||||
};
|
||||
msg += `Please retry in ${ms(difference, { long: true })}.`;
|
||||
} else {
|
||||
msg += 'Please slow down.';
|
||||
}
|
||||
|
||||
const url = atlas ? '/v1/now/images' : '/v2/now/files';
|
||||
const additionalHeaders = atlas
|
||||
? {
|
||||
'x-now-dcs': Object.keys(scale).join(',')
|
||||
}
|
||||
: {};
|
||||
const res = await this._fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'x-now-digest': sha,
|
||||
'x-now-size': data.length,
|
||||
...additionalHeaders
|
||||
},
|
||||
body: stream
|
||||
});
|
||||
const err = new Error(msg);
|
||||
|
||||
if (res.status === 200) {
|
||||
// What we want
|
||||
this.emit('uploadProgress', file.data.length - uploadedSoFar);
|
||||
this.emit('upload', file);
|
||||
} else if (res.status > 200 && res.status < 500) {
|
||||
// If something is wrong with our request, we don't retry
|
||||
return bail(await responseError(res, `Failed to upload file with status: ${res.status}`));
|
||||
} else {
|
||||
// If something is wrong with the server, we retry
|
||||
throw await responseError(res, 'Failed to upload file');
|
||||
}
|
||||
},
|
||||
{
|
||||
retries: 3,
|
||||
randomize: true,
|
||||
onRetry: this._onRetry
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
this.emit('complete');
|
||||
})
|
||||
.catch(err => this.emit('error', err));
|
||||
err.status = error.status;
|
||||
err.retryAfter = 'never';
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
// If the deployment domain is missing a cert, bail with the error
|
||||
if (error.status === 400 && error.code === 'cert_missing') {
|
||||
return responseError(error, null, error);
|
||||
}
|
||||
|
||||
if (error.status === 400 && error.code === 'missing_files') {
|
||||
this._missing = error.missing || [];
|
||||
this._fileCount = hashes.length;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
if (error.status === 404 && error.code === 'not_found') {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (error.status >= 400 && error.status < 500) {
|
||||
const err = new Error();
|
||||
|
||||
const { code, unreferencedBuildSpecs } = error;
|
||||
|
||||
if (code === 'env_value_invalid_type') {
|
||||
const { key } = error;
|
||||
err.message =
|
||||
`The env key ${key} has an invalid type: ${typeof env[key]}. ` +
|
||||
'Please supply a String or a Number (https://err.sh/now-cli/env-value-invalid-type)';
|
||||
} else if (code === 'unreferenced_build_specifications') {
|
||||
const count = unreferencedBuildSpecs.length;
|
||||
const prefix = count === 1 ? 'build' : 'builds';
|
||||
|
||||
err.message =
|
||||
`You defined ${count} ${prefix} that did not match any source files (please ensure they are NOT defined in ${highlight(
|
||||
'.nowignore'
|
||||
)}):` +
|
||||
`\n- ${unreferencedBuildSpecs
|
||||
.map(item => JSON.stringify(item))
|
||||
.join('\n- ')}`;
|
||||
} else {
|
||||
Object.assign(err, error);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
// Handle build errors
|
||||
if (error.id && error.id.startsWith('bld_')) {
|
||||
return new BuildError({
|
||||
meta: {
|
||||
entrypoint: error.entrypoint,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return new Error(error.message);
|
||||
}
|
||||
|
||||
async listSecrets() {
|
||||
@@ -589,7 +371,7 @@ export default class Now extends EventEmitter {
|
||||
{
|
||||
retries: 3,
|
||||
minTimeout: 2500,
|
||||
onRetry: this._onRetry
|
||||
onRetry: this._onRetry,
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -597,7 +379,7 @@ export default class Now extends EventEmitter {
|
||||
if (!app && !Object.keys(meta).length) {
|
||||
// Get the 35 latest projects and their latest deployment
|
||||
const query = new URLSearchParams({ limit: 35 });
|
||||
const projects = await fetchRetry(`/projects/list?${query}`);
|
||||
const projects = await fetchRetry(`/v2/projects/?${query}`);
|
||||
|
||||
const deployments = await Promise.all(
|
||||
projects.map(async ({ id: projectId }) => {
|
||||
@@ -647,7 +429,7 @@ export default class Now extends EventEmitter {
|
||||
{
|
||||
retries: 3,
|
||||
minTimeout: 2500,
|
||||
onRetry: this._onRetry
|
||||
onRetry: this._onRetry,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -727,7 +509,7 @@ export default class Now extends EventEmitter {
|
||||
|
||||
await this.retry(async bail => {
|
||||
const res = await this._fetch(url, {
|
||||
method: 'DELETE'
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
@@ -748,7 +530,7 @@ export default class Now extends EventEmitter {
|
||||
return retry(fn, {
|
||||
retries,
|
||||
maxTimeout,
|
||||
onRetry: this._onRetry
|
||||
onRetry: this._onRetry,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -756,9 +538,7 @@ export default class Now extends EventEmitter {
|
||||
this._output.debug(`Retrying: ${err}\n${err.stack}`);
|
||||
}
|
||||
|
||||
close() {
|
||||
this._agent.close();
|
||||
}
|
||||
close() {}
|
||||
|
||||
get id() {
|
||||
return this._id;
|
||||
@@ -802,14 +582,21 @@ export default class Now extends EventEmitter {
|
||||
|
||||
opts.headers = opts.headers || {};
|
||||
opts.headers.accept = 'application/json';
|
||||
opts.headers.authorization = `Bearer ${this._token}`;
|
||||
opts.headers.Authorization = `Bearer ${this._token}`;
|
||||
opts.headers['user-agent'] = ua;
|
||||
|
||||
if (
|
||||
opts.body &&
|
||||
typeof opts.body === 'object' &&
|
||||
opts.body.constructor === Object
|
||||
) {
|
||||
opts.body = JSON.stringify(opts.body);
|
||||
opts.headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
return this._output.time(
|
||||
`${opts.method || 'GET'} ${this._apiUrl}${_url} ${JSON.stringify(
|
||||
opts.body
|
||||
) || ''}`,
|
||||
this._agent.fetch(_url, opts)
|
||||
`${opts.method || 'GET'} ${this._apiUrl}${_url} ${opts.body || ''}`,
|
||||
fetch(`${this._apiUrl}${_url}`, opts)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -827,8 +614,8 @@ export default class Now extends EventEmitter {
|
||||
opts = Object.assign({}, opts, {
|
||||
body: JSON.stringify(opts.body),
|
||||
headers: Object.assign({}, opts.headers, {
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
});
|
||||
}
|
||||
const res = await this._fetch(url, opts);
|
||||
@@ -875,6 +662,7 @@ function hasNpmStart(pkg) {
|
||||
|
||||
function hasFile(base, files, name) {
|
||||
const relative = files.map(file => toRelative(file, base));
|
||||
console.log(731, relative);
|
||||
return relative.indexOf(name) !== -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import inquirer from 'inquirer';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import eraseLines from '../output/erase-lines';
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import './patch-inquirer';
|
||||
|
||||
function getLength(string) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable import/no-unresolved */
|
||||
import path from 'path';
|
||||
import pkg from '../../package.json';
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { join } from 'path';
|
||||
import { exists } from 'fs-extra';
|
||||
import { PackageJson } from '@now/build-utils';
|
||||
|
||||
import Client from './client';
|
||||
import { Config } from '../types';
|
||||
import { Package } from './dev/types';
|
||||
import { CantParseJSONFile, ProjectNotFound } from './errors-ts';
|
||||
import getProjectByIdOrName from './projects/get-project-by-id-or-name';
|
||||
|
||||
@@ -26,14 +27,14 @@ export default async function preferV2Deployment({
|
||||
hasServerfile,
|
||||
pkg,
|
||||
localConfig,
|
||||
projectName
|
||||
projectName,
|
||||
}: {
|
||||
client?: Client,
|
||||
hasDockerfile: boolean,
|
||||
hasServerfile: boolean,
|
||||
pkg: Package | CantParseJSONFile | null,
|
||||
localConfig: Config | undefined,
|
||||
projectName?: string
|
||||
client?: Client;
|
||||
hasDockerfile: boolean;
|
||||
hasServerfile: boolean;
|
||||
pkg: PackageJson | CantParseJSONFile | null;
|
||||
localConfig: Config | undefined;
|
||||
projectName?: string;
|
||||
}): Promise<null | string> {
|
||||
if (localConfig && localConfig.version) {
|
||||
// We will prefer anything that is set here
|
||||
@@ -52,10 +53,14 @@ export default async function preferV2Deployment({
|
||||
const { scripts = {} } = pkg;
|
||||
|
||||
if (!scripts.start && !scripts['now-start']) {
|
||||
return `Deploying to Now 2.0, because ${highlight('package.json')} is missing a ${cmd('start')} script. ${INFO}`;
|
||||
return `Deploying to Now 2.0, because ${highlight(
|
||||
'package.json'
|
||||
)} is missing a ${cmd('start')} script. ${INFO}`;
|
||||
}
|
||||
} else if (!pkg && !hasDockerfile) {
|
||||
return `Deploying to Now 2.0, because no ${highlight('Dockerfile')} was found. ${INFO}`;
|
||||
return `Deploying to Now 2.0, because no ${highlight(
|
||||
'Dockerfile'
|
||||
)} was found. ${INFO}`;
|
||||
}
|
||||
|
||||
if (client && projectName) {
|
||||
|
||||
@@ -2,10 +2,10 @@ import path from 'path';
|
||||
import { CantParseJSONFile } from './errors-ts';
|
||||
import readJSONFile from './read-json-file';
|
||||
import { Config } from '../types';
|
||||
import { Package } from './dev/types';
|
||||
import { PackageJson } from '@now/build-utils';
|
||||
|
||||
interface CustomPackage extends Package {
|
||||
now?: Config
|
||||
interface CustomPackage extends PackageJson {
|
||||
now?: Config;
|
||||
}
|
||||
|
||||
export default async function readPackage(file?: string) {
|
||||
@@ -16,8 +16,8 @@ export default async function readPackage(file?: string) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (result){
|
||||
return result as CustomPackage
|
||||
if (result) {
|
||||
return result as CustomPackage;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -22,7 +22,7 @@ export default async (sentry, error, apiUrl, configFiles) => {
|
||||
if (user) {
|
||||
const spec = {
|
||||
email: user.email,
|
||||
id: user.uid
|
||||
id: user.uid,
|
||||
};
|
||||
|
||||
if (user.username) {
|
||||
@@ -44,7 +44,7 @@ export default async (sentry, error, apiUrl, configFiles) => {
|
||||
scope.setExtra('scopeError', {
|
||||
name: scopeError.name,
|
||||
message: scopeError.message,
|
||||
stack: scopeError.stack
|
||||
stack: scopeError.stack,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ export default async (sentry, error, apiUrl, configFiles) => {
|
||||
// Report information about the version of `node` being used
|
||||
scope.setExtra('node', {
|
||||
execPath: process.execPath,
|
||||
version: process.version
|
||||
version: process.version,
|
||||
platform: process.platform,
|
||||
});
|
||||
|
||||
sentry.captureException(error);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Response } from 'fetch-h2';
|
||||
import { Response } from 'node-fetch';
|
||||
import { APIError } from './errors-ts';
|
||||
|
||||
export default async function responseError(
|
||||
|
||||
1
packages/now-cli/test/dev-builder.unit.js
vendored
1
packages/now-cli/test/dev-builder.unit.js
vendored
@@ -1,5 +1,4 @@
|
||||
import test from 'ava';
|
||||
|
||||
import { filterPackage } from '../src/util/dev/builder-cache';
|
||||
|
||||
test('[dev-builder] filter install "latest", cached canary', async t => {
|
||||
|
||||
1
packages/now-cli/test/dev-router.unit.js
vendored
1
packages/now-cli/test/dev-router.unit.js
vendored
@@ -1,5 +1,4 @@
|
||||
import test from 'ava';
|
||||
|
||||
import devRouter from '../src/util/dev/router';
|
||||
|
||||
test('[dev-router] 301 redirection', async t => {
|
||||
|
||||
14
packages/now-cli/test/dev-server.unit.js
vendored
14
packages/now-cli/test/dev-server.unit.js
vendored
@@ -1,6 +1,8 @@
|
||||
import url from 'url';
|
||||
import test from 'ava';
|
||||
import path from 'path';
|
||||
import execa from 'execa';
|
||||
import fs from 'fs-extra';
|
||||
import fetch from 'node-fetch';
|
||||
import listen from 'async-listen';
|
||||
import { request, createServer } from 'http';
|
||||
@@ -9,9 +11,20 @@ import DevServer from '../src/util/dev/server';
|
||||
import { installBuilders, getBuildUtils } from '../src/util/dev/builder-cache';
|
||||
import parseListen from '../src/util/dev/parse-listen';
|
||||
|
||||
async function runNpmInstall(fixturePath) {
|
||||
if (await fs.exists(path.join(fixturePath, 'package.json'))) {
|
||||
return execa('yarn', ['install'], { cwd: fixturePath });
|
||||
}
|
||||
}
|
||||
|
||||
function testFixture(name, fn) {
|
||||
return async t => {
|
||||
let server;
|
||||
|
||||
const fixturePath = path.join(__dirname, 'fixtures', 'unit', name);
|
||||
|
||||
await runNpmInstall(fixturePath);
|
||||
|
||||
try {
|
||||
let readyResolve;
|
||||
let readyPromise = new Promise(resolve => {
|
||||
@@ -29,7 +42,6 @@ function testFixture(name, fn) {
|
||||
origReady(msg);
|
||||
};
|
||||
|
||||
const fixturePath = path.join(__dirname, `fixtures/unit/${name}`);
|
||||
server = new DevServer(fixturePath, { output, debug });
|
||||
|
||||
await server.start(0);
|
||||
|
||||
8
packages/now-cli/test/dev/fixtures/01-node/yarn.lock
Normal file
8
packages/now-cli/test/dev/fixtures/01-node/yarn.lock
Normal file
@@ -0,0 +1,8 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
moment@^2.24.0:
|
||||
version "2.24.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
||||
@@ -14,37 +14,37 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~8.1.0",
|
||||
"@angular/common": "~8.1.0",
|
||||
"@angular/compiler": "~8.1.0",
|
||||
"@angular/core": "~8.1.0",
|
||||
"@angular/forms": "~8.1.0",
|
||||
"@angular/platform-browser": "~8.1.0",
|
||||
"@angular/platform-browser-dynamic": "~8.1.0",
|
||||
"@angular/router": "~8.1.0",
|
||||
"rxjs": "~6.4.0",
|
||||
"tslib": "^1.9.0",
|
||||
"zone.js": "~0.9.1"
|
||||
"@angular/animations": "8.1.0",
|
||||
"@angular/common": "8.1.0",
|
||||
"@angular/compiler": "8.1.0",
|
||||
"@angular/core": "8.1.0",
|
||||
"@angular/forms": "8.1.0",
|
||||
"@angular/platform-browser": "8.1.0",
|
||||
"@angular/platform-browser-dynamic": "8.1.0",
|
||||
"@angular/router": "8.1.0",
|
||||
"rxjs": "6.4.0",
|
||||
"tslib": "1.9.0",
|
||||
"zone.js": "0.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.801.0",
|
||||
"@angular/cli": "~8.1.0",
|
||||
"@angular/compiler-cli": "~8.1.0",
|
||||
"@angular/language-service": "~8.1.0",
|
||||
"@types/node": "~8.9.4",
|
||||
"@types/jasmine": "~3.3.8",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"codelyzer": "^5.0.0",
|
||||
"jasmine-core": "~3.4.0",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~4.1.0",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "~2.0.1",
|
||||
"karma-jasmine": "~2.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.4.0",
|
||||
"protractor": "~5.4.0",
|
||||
"ts-node": "~7.0.0",
|
||||
"tslint": "~5.15.0",
|
||||
"typescript": "~3.4.3"
|
||||
"@angular-devkit/build-angular": "0.801.0",
|
||||
"@angular/cli": "8.1.0",
|
||||
"@angular/compiler-cli": "8.1.0",
|
||||
"@angular/language-service": "8.1.0",
|
||||
"@types/node": "8.9.4",
|
||||
"@types/jasmine": "3.3.8",
|
||||
"@types/jasminewd2": "2.0.3",
|
||||
"codelyzer": "5.0.0",
|
||||
"jasmine-core": "3.4.0",
|
||||
"jasmine-spec-reporter": "4.2.1",
|
||||
"karma": "4.1.0",
|
||||
"karma-chrome-launcher": "2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "2.0.1",
|
||||
"karma-jasmine": "2.0.1",
|
||||
"karma-jasmine-html-reporter": "1.4.0",
|
||||
"protractor": "5.4.0",
|
||||
"ts-node": "7.0.0",
|
||||
"tslint": "5.15.0",
|
||||
"typescript": "3.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
7124
packages/now-cli/test/dev/fixtures/02-angular-node/yarn.lock
Normal file
7124
packages/now-cli/test/dev/fixtures/02-angular-node/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -34,3 +34,5 @@ node_modules
|
||||
/public
|
||||
/test/coverage-jest
|
||||
/test/coverage-karma
|
||||
|
||||
!yarn.lock
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
README.md
|
||||
yarn.lock
|
||||
9302
packages/now-cli/test/dev/fixtures/03-aurelia/yarn.lock
Normal file
9302
packages/now-cli/test/dev/fixtures/03-aurelia/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user