mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-24 11:49:13 +00:00
Compare commits
167 Commits
@now/next@
...
@now/next@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
274fdeb49a | ||
|
|
d15c90b42f | ||
|
|
5be2917c47 | ||
|
|
5f08b24546 | ||
|
|
cc3026ffcb | ||
|
|
8cb6e56679 | ||
|
|
7da5ff4b1d | ||
|
|
5d1069d464 | ||
|
|
6e1a72f557 | ||
|
|
6f97a2cc7a | ||
|
|
6e402bffc3 | ||
|
|
e41d0f8e68 | ||
|
|
1a046744f2 | ||
|
|
a9bf011f2c | ||
|
|
60c76b3290 | ||
|
|
c7ff1e7044 | ||
|
|
e2deaef54a | ||
|
|
b2c7386c83 | ||
|
|
3dce84c3cf | ||
|
|
443b1ac158 | ||
|
|
be3dca3d94 | ||
|
|
cb135e4329 | ||
|
|
55c73b68bb | ||
|
|
eb793ba139 | ||
|
|
00908e8ebe | ||
|
|
5ce7ff86a4 | ||
|
|
4b34ee2993 | ||
|
|
646bd288ba | ||
|
|
8aba6f1ff8 | ||
|
|
35e5e328aa | ||
|
|
bdd4953d62 | ||
|
|
e938b298e2 | ||
|
|
b67f324e10 | ||
|
|
1dfa286af5 | ||
|
|
d4391bd4cc | ||
|
|
53eb71f26d | ||
|
|
92ffd654b5 | ||
|
|
36b83f1606 | ||
|
|
7a79f620c0 | ||
|
|
f3b286ecf3 | ||
|
|
adf31c3fcc | ||
|
|
deacdfc47c | ||
|
|
ac9badbe9e | ||
|
|
62beb0f78d | ||
|
|
5f9777f4af | ||
|
|
e00db4437a | ||
|
|
b0e4f2590d | ||
|
|
f0d58eac8c | ||
|
|
dae830d2b6 | ||
|
|
e3071e4e29 | ||
|
|
073d7ece23 | ||
|
|
071258ba33 | ||
|
|
c0e00dc69a | ||
|
|
6e5c136337 | ||
|
|
60428cd4cf | ||
|
|
c6b9d80eec | ||
|
|
953bdc10e5 | ||
|
|
becdbd2136 | ||
|
|
da9bb31259 | ||
|
|
8cbf036921 | ||
|
|
8f66e4a308 | ||
|
|
9627b612f2 | ||
|
|
8a9ded6d61 | ||
|
|
d89c772bd5 | ||
|
|
32137586b9 | ||
|
|
9a3e435175 | ||
|
|
01d5a10ebd | ||
|
|
040658fbfa | ||
|
|
51c00286a4 | ||
|
|
c3e274fc2f | ||
|
|
fe633c528a | ||
|
|
5e306d49f8 | ||
|
|
274259b7dd | ||
|
|
230e96c687 | ||
|
|
8c0c6e546d | ||
|
|
5e8541b936 | ||
|
|
300558f24e | ||
|
|
829f7d8aeb | ||
|
|
340f7db68a | ||
|
|
2fe987b5da | ||
|
|
5b3aa48cd6 | ||
|
|
080d96bfa1 | ||
|
|
7d9bf682b4 | ||
|
|
a913a4f59f | ||
|
|
2042c96d98 | ||
|
|
4ca0ff8426 | ||
|
|
554cc42d83 | ||
|
|
3f9e30d031 | ||
|
|
5a9ca8644c | ||
|
|
3f362d4b50 | ||
|
|
7360886c9a | ||
|
|
5b8a1b47b0 | ||
|
|
0506b262d2 | ||
|
|
17c4569f41 | ||
|
|
888ff83309 | ||
|
|
7191421470 | ||
|
|
e9c9aa5ce1 | ||
|
|
d98f3d8140 | ||
|
|
fbf659e4e1 | ||
|
|
6f7069aadd | ||
|
|
cef2c889f6 | ||
|
|
e42d6c422a | ||
|
|
2e9241b454 | ||
|
|
257a3b07fc | ||
|
|
df9322b392 | ||
|
|
28b5476bf3 | ||
|
|
70a61f045f | ||
|
|
238a8a8593 | ||
|
|
6f2d5c0045 | ||
|
|
fc798da398 | ||
|
|
0804483316 | ||
|
|
4a087f842e | ||
|
|
01c7bf0059 | ||
|
|
6c439516db | ||
|
|
0db4ee69ef | ||
|
|
aec8a7ac9a | ||
|
|
882bc8cb99 | ||
|
|
9aad701dfd | ||
|
|
f224f574be | ||
|
|
e201eaa315 | ||
|
|
b0ed1f2be0 | ||
|
|
4ac33e1c03 | ||
|
|
27ca517666 | ||
|
|
734e9c072d | ||
|
|
8db54f3093 | ||
|
|
886c0718d6 | ||
|
|
e160a2f21f | ||
|
|
e09a418423 | ||
|
|
52d4464368 | ||
|
|
4dc635e5f2 | ||
|
|
510fb7ee7e | ||
|
|
243451e94b | ||
|
|
11bbda977d | ||
|
|
62c050f394 | ||
|
|
bd5a013312 | ||
|
|
1823cf452e | ||
|
|
c426d72ccf | ||
|
|
ddf59c052d | ||
|
|
1dcf6e7fb1 | ||
|
|
d4f4792988 | ||
|
|
7e1f2bd10e | ||
|
|
a80a1d0c1d | ||
|
|
8ff747b4d7 | ||
|
|
aa63b5a581 | ||
|
|
2094ec3c99 | ||
|
|
bf30d10211 | ||
|
|
ccc03c9c6e | ||
|
|
4b7367e2dc | ||
|
|
00aa56a095 | ||
|
|
56ae93a2a5 | ||
|
|
adb32a09d3 | ||
|
|
3358d8e44c | ||
|
|
c3bd2698e8 | ||
|
|
a7baa4761d | ||
|
|
5dd2daa970 | ||
|
|
dd36a489ed | ||
|
|
2e742209e3 | ||
|
|
8d13464cba | ||
|
|
20fdcfa0af | ||
|
|
fac004f83c | ||
|
|
5fee4bbad1 | ||
|
|
18e4b18839 | ||
|
|
b8627fd384 | ||
|
|
4e2db6f8a5 | ||
|
|
b685a3afdd | ||
|
|
aea3f58970 | ||
|
|
b0bb90dc11 |
@@ -1,501 +0,0 @@
|
||||
version: 2
|
||||
jobs:
|
||||
install:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
environment:
|
||||
GOPATH: $HOME/go
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "yarn.lock" }}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- v1-dependencies-
|
||||
- run:
|
||||
name: Updating apt packages
|
||||
command: sudo apt-get update
|
||||
- run:
|
||||
name: Installing the latest version of Go
|
||||
command: sudo apt-get install golang-go
|
||||
- run:
|
||||
name: Installing Dependencies
|
||||
command: yarn install --check-files --frozen-lockfile
|
||||
- save_cache:
|
||||
paths:
|
||||
- node_modules
|
||||
- packages/gatsby-plugin-now/node_modules
|
||||
- packages/now-build-utils/node_modules
|
||||
- packages/now-cgi/node_modules
|
||||
- packages/now-cli/node_modules
|
||||
- packages/now-client/node_modules
|
||||
- packages/now-go/node_modules
|
||||
- packages/now-next/node_modules
|
||||
- packages/now-node/node_modules
|
||||
- packages/now-node-bridge/node_modules
|
||||
- packages/now-python/node_modules
|
||||
- packages/now-routing-utils/node_modules
|
||||
- packages/now-ruby/node_modules
|
||||
- packages/now-static-build/node_modules
|
||||
key: v1-dependencies-{{ checksum "yarn.lock" }}
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- node_modules
|
||||
- packages/gatsby-plugin-now/node_modules
|
||||
- packages/now-build-utils/node_modules
|
||||
- packages/now-cgi/node_modules
|
||||
- packages/now-cli/node_modules
|
||||
- packages/now-client/node_modules
|
||||
- packages/now-go/node_modules
|
||||
- packages/now-next/node_modules
|
||||
- packages/now-node/node_modules
|
||||
- packages/now-node-bridge/node_modules
|
||||
- packages/now-python/node_modules
|
||||
- packages/now-routing-utils/node_modules
|
||||
- packages/now-ruby/node_modules
|
||||
- packages/now-static-build/node_modules
|
||||
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Linking dependencies
|
||||
command: yarn bootstrap
|
||||
- run:
|
||||
name: Building
|
||||
command: yarn build
|
||||
- store_artifacts:
|
||||
path: packages/now-cli/dist
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- packages/gatsby-plugin-now/test/fixtures
|
||||
- packages/now-build-utils/dist
|
||||
- packages/now-cgi/dist
|
||||
- packages/now-cli/dist
|
||||
- packages/now-cli/assets
|
||||
- packages/now-client/dist
|
||||
- packages/now-go/dist
|
||||
- packages/now-next/dist
|
||||
- packages/now-node/dist
|
||||
- packages/now-node/test/fixtures/15-helpers/ts/types.d.ts
|
||||
- packages/now-node/test/fixtures/11-symlinks/symlink
|
||||
- packages/now-node-bridge/index.js
|
||||
- packages/now-node-bridge/bridge.js
|
||||
- packages/now-python/dist
|
||||
- packages/now-routing-utils/dist
|
||||
- packages/now-ruby/dist
|
||||
- packages/now-static-build/dist
|
||||
- packages/now-static-build/test/fixtures/10a-gatsby-redirects/plugins
|
||||
|
||||
test-lint:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Compiling `now dev` HTML error templates
|
||||
command: node packages/now-cli/scripts/compile-templates.js
|
||||
- run:
|
||||
name: Linting Code
|
||||
command: yarn test-lint
|
||||
|
||||
test-integration-macos-node-8:
|
||||
macos:
|
||||
xcode: '9.0.1'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests
|
||||
command: yarn test-integration --clean false
|
||||
|
||||
test-integration-macos-node-10:
|
||||
macos:
|
||||
xcode: '10.0.0'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests
|
||||
command: yarn test-integration --clean false
|
||||
|
||||
test-integration-macos-node-12:
|
||||
macos:
|
||||
xcode: '10.3.0'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests
|
||||
command: yarn test-integration --clean false
|
||||
|
||||
test-integration-linux-node-8:
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests
|
||||
command: yarn test-integration --clean false
|
||||
|
||||
test-integration-linux-node-10:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests
|
||||
command: yarn test-integration --clean false
|
||||
|
||||
test-integration-linux-node-12:
|
||||
docker:
|
||||
- image: circleci/node:12
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests
|
||||
command: yarn test-integration --clean false
|
||||
|
||||
test-integration-macos-now-dev-node-8:
|
||||
macos:
|
||||
xcode: '9.0.1'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Downloading Hugo
|
||||
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run:
|
||||
name: Running Integration Tests for `now dev`
|
||||
command: yarn test-integration-now-dev --clean false
|
||||
|
||||
test-integration-macos-now-dev-node-10:
|
||||
macos:
|
||||
xcode: '10.0.0'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Downloading Hugo
|
||||
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run:
|
||||
name: Running Integration Tests for `now dev`
|
||||
command: yarn test-integration-now-dev --clean false
|
||||
|
||||
test-integration-macos-now-dev-node-12:
|
||||
macos:
|
||||
xcode: '10.3.0'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Downloading Hugo
|
||||
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run:
|
||||
name: Running Integration Tests for `now dev`
|
||||
command: yarn test-integration-now-dev --clean false
|
||||
|
||||
test-integration-linux-now-dev-node-8:
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Downloading Hugo
|
||||
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.55.6/hugo_0.55.6_Linux-64bit.tar.gz && tar -xzf hugo_0.55.6_Linux-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run:
|
||||
name: Running Integration Tests for `now dev`
|
||||
command: yarn test-integration-now-dev --clean false
|
||||
|
||||
test-integration-linux-now-dev-node-10:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Downloading Hugo
|
||||
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.55.6/hugo_0.55.6_Linux-64bit.tar.gz && tar -xzf hugo_0.55.6_Linux-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run:
|
||||
name: Running Integration Tests for `now dev`
|
||||
command: yarn test-integration-now-dev --clean false
|
||||
|
||||
test-integration-linux-now-dev-node-12:
|
||||
docker:
|
||||
- image: circleci/node:12
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Downloading Hugo
|
||||
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.55.6/hugo_0.55.6_Linux-64bit.tar.gz && tar -xzf hugo_0.55.6_Linux-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run:
|
||||
name: Running Integration Tests for `now dev`
|
||||
command: yarn test-integration-now-dev --clean false
|
||||
|
||||
test-integration-once:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests Once
|
||||
command: yarn test-integration-once --clean false
|
||||
|
||||
test-unit:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Compiling `now dev` HTML error templates
|
||||
command: node packages/now-cli/scripts/compile-templates.js
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Unit Tests
|
||||
command: yarn test-unit --clean false
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- packages/now-cli/.nyc_output
|
||||
|
||||
coverage:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Run coverage report
|
||||
command: yarn workspace now run coverage
|
||||
|
||||
source-maps:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Installing Sentry CLI
|
||||
command: npm install -g @sentry/cli
|
||||
- run:
|
||||
name: Creating a New Sentry Release
|
||||
command: sentry-cli releases new now-cli@`git describe --tags`
|
||||
- run:
|
||||
name: Upload Sourcemap Files
|
||||
command: sentry-cli releases files now-cli@`git describe --tags` upload-sourcemaps ./dist
|
||||
- run:
|
||||
name: Finalize Sentry Release
|
||||
command: sentry-cli releases finalize now-cli@`git describe --tags`
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
unscheduled:
|
||||
jobs:
|
||||
- install:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- build:
|
||||
requires:
|
||||
- install
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-lint:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-macos-node-8:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-macos-node-10:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-macos-node-12:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-linux-node-8:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-linux-node-10:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-linux-node-12:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-macos-now-dev-node-8:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-macos-now-dev-node-10:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-macos-now-dev-node-12:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-linux-now-dev-node-8:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-linux-now-dev-node-10:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-linux-now-dev-node-12:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-once:
|
||||
requires:
|
||||
- build
|
||||
- test-unit:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- coverage:
|
||||
requires:
|
||||
- test-integration-macos-node-8
|
||||
- test-integration-macos-node-10
|
||||
- test-integration-macos-node-12
|
||||
- test-integration-linux-node-8
|
||||
- test-integration-linux-node-10
|
||||
- test-integration-linux-node-12
|
||||
- test-integration-macos-now-dev-node-8
|
||||
- test-integration-macos-now-dev-node-10
|
||||
- test-integration-macos-now-dev-node-12
|
||||
- test-integration-linux-now-dev-node-8
|
||||
- test-integration-linux-now-dev-node-10
|
||||
- test-integration-linux-now-dev-node-12
|
||||
- test-integration-once
|
||||
- test-unit
|
||||
- test-lint
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
9
.github/CODEOWNERS
vendored
9
.github/CODEOWNERS
vendored
@@ -2,6 +2,8 @@
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
* @tootallnate @leo
|
||||
/.github/workflows @AndyBitz @styfle
|
||||
/packages/frameworks @AndyBitz
|
||||
/packages/now-cli/src/commands/dev/ @tootallnate @leo @styfle @AndyBitz
|
||||
/packages/now-cli/src/util/dev/ @tootallnate @leo @styfle @AndyBitz
|
||||
/packages/now-cli/src/commands/domains/ @javivelasco @mglagola @anatrajkovska
|
||||
@@ -10,13 +12,16 @@
|
||||
/packages/now-build-utils @styfle @AndyBitz
|
||||
/packages/now-node @styfle @tootallnate @lucleray
|
||||
/packages/now-node-bridge @styfle @tootallnate @lucleray
|
||||
/packages/now-next @Timer
|
||||
/packages/now-next @Timer @ijjk
|
||||
/packages/now-go @styfle @sophearak
|
||||
/packages/now-python @styfle @sophearak
|
||||
/packages/now-ruby @styfle @coetry @nathancahill
|
||||
/packages/now-static-build @styfle @AndyBitz
|
||||
/packages/now-routing-utils @dav-is
|
||||
/packages/now-routing-utils @styfle @dav-is @ijjk
|
||||
|
||||
/examples @msweeneydev @timothyis
|
||||
/examples/create-react-app @Timer
|
||||
/examples/nextjs @timneutkens
|
||||
/examples/hugo @msweeneydev @timothyis @styfle
|
||||
/examples/jekyll @msweeneydev @timothyis @sarupbanskota
|
||||
/examples/zola @msweeneydev @timothyis @styfle
|
||||
|
||||
13
.github/CONTRIBUTING.md
vendored
13
.github/CONTRIBUTING.md
vendored
@@ -97,10 +97,9 @@ Sometimes you want to test changes to a Builder against an existing project, may
|
||||
|
||||
## Add a New Framework
|
||||
|
||||
1. Add the framework to the `@now/frameworks` package.
|
||||
The file is located in `packages/frameworks/frameworks.json`.
|
||||
2. Add an example to the `examples/` directory.
|
||||
The name of the directory should equal the slug of the framework in `@now/frameworks`.
|
||||
The `.github/EXAMPLE_README_TEMPLATE.md` file can be used to create a README for the example.
|
||||
3. Finally, `@now/static-build` must be adjusted.
|
||||
The files `packages/now-static-build/src/frameworks.ts` has to be extended.
|
||||
You can add support for a new Framework by creating a Pull Request for this repository and following the steps below:
|
||||
|
||||
1. Add the Framework to the `@now/frameworks` package: The file is located in `packages/frameworks/frameworks.json`. You can copy the structure of an existing one and adjust the required fields. Note that the `settings` property either contains a `value` or a `placeholder`. The `value` property is used when something is not configurable, the `placeholder` is used when something is configurable and can be changed with configuration. An example would be the Output Directory for Hugo, it's `public` by default but can be changed through its config file, so we use `placeholder` with an explanation of what can be used.
|
||||
2. Add an example to the `examples/` directory: The name of the directory should equal the slug of the framework used in `@now/frameworks`. The `.github/EXAMPLE_README_TEMPLATE.md` file can be used to create a `README.md` file for the example.
|
||||
3. Update the `@now/static-build` package: The file `packages/now-static-build/src/frameworks.ts` has to be extended. You can add default routes that will always be applied to projects that use this Framework or specify some paths that will be cached to speed up the build process.
|
||||
4. After your Pull Request has been merged and released, other users can select the example on the ZEIT Now dashboard and deploy it.
|
||||
|
||||
17
.github/workflows/cancel.yml
vendored
Normal file
17
.github/workflows/cancel.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Cancel
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- '!master'
|
||||
jobs:
|
||||
cancel:
|
||||
name: 'Cancel Previous Runs'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 3
|
||||
steps:
|
||||
- uses: styfle/cancel-workflow-action@master
|
||||
with:
|
||||
workflow_id: 435869
|
||||
access_token: ${{ secrets.GITHUB_WORKFLOW_TOKEN }}
|
||||
|
||||
109
.github/workflows/continuous-integration.yml
vendored
Normal file
109
.github/workflows/continuous-integration.yml
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '!*'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test-unit:
|
||||
name: Unit Tests
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
node: [10, 12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- uses: actions/setup-node@v1
|
||||
- run: yarn install
|
||||
- run: yarn run build
|
||||
- run: yarn run test-lint
|
||||
- run: yarn run test-unit --clean false
|
||||
- name: Upload Artifact
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node == 12 # only run once
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: test-unit-output
|
||||
path: packages/now-cli/.nyc_output
|
||||
|
||||
test-integration:
|
||||
name: Integration Tests
|
||||
timeout-minutes: 120
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- run: yarn install
|
||||
- run: yarn run build
|
||||
- run: yarn test-integration-once --clean false
|
||||
|
||||
test-now-cli:
|
||||
name: Now CLI Tests
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node: [10, 12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- name: Install Hugo
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run: yarn install
|
||||
- run: yarn run build
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: yarn test-integration --clean false
|
||||
|
||||
test-now-dev:
|
||||
name: "`now dev` Tests"
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
node: [10, 12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- name: Install Hugo
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run: yarn install
|
||||
- run: yarn run build
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: yarn test-integration-now-dev --clean false
|
||||
|
||||
coverage:
|
||||
name: Coverage
|
||||
timeout-minutes: 10
|
||||
needs: [test-unit, test-now-cli, test-now-dev, test-integration]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: test-unit-output
|
||||
path: packages/now-cli/.nyc_output
|
||||
- run: yarn install
|
||||
- run: yarn workspace now run coverage
|
||||
15
.github/workflows/publish.yml
vendored
15
.github/workflows/publish.yml
vendored
@@ -4,25 +4,18 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
tags:
|
||||
- '!*'
|
||||
|
||||
jobs:
|
||||
Publish:
|
||||
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Checkout
|
||||
uses: actions/setup-node@v1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
node-version: 10
|
||||
- name: Install
|
||||
run: yarn install --check-files --frozen-lockfile
|
||||
- name: Build
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||

|
||||

|
||||
|
||||
[](https://circleci.com/gh/zeit/workflows/now/tree/master)
|
||||
[](https://github.com/zeit/now/actions?workflow=CI)
|
||||
[](https://spectrum.chat/zeit)
|
||||
|
||||
## Usage
|
||||
@@ -16,7 +16,6 @@ To quickly start a new project, run the following commands:
|
||||
```
|
||||
now init # Pick an example project to clone
|
||||
cd <PROJECT> # Change directory to the newly created project
|
||||
now dev # Run locally during development
|
||||
now # Deploy to the cloud
|
||||
```
|
||||
|
||||
|
||||
@@ -2,17 +2,19 @@ import { NowRequest, NowResponse } from '@now/node';
|
||||
import { withApiHandler } from './_lib/util/with-api-handler';
|
||||
import frameworkList, { Framework } from '../packages/frameworks';
|
||||
|
||||
const frameworks: Framework[] = (frameworkList as Framework[]).map(
|
||||
framework => {
|
||||
delete framework.detectors;
|
||||
const frameworks = (frameworkList as Framework[]).map(frameworkItem => {
|
||||
const framework = {
|
||||
...frameworkItem,
|
||||
hasDetectors: Boolean(frameworkItem.detectors),
|
||||
detectors: undefined,
|
||||
};
|
||||
|
||||
if (framework.logo) {
|
||||
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
||||
}
|
||||
|
||||
return framework;
|
||||
if (framework.logo) {
|
||||
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
||||
}
|
||||
);
|
||||
|
||||
return framework;
|
||||
});
|
||||
|
||||
export default withApiHandler(async function(
|
||||
req: NowRequest,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Angular Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# React Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Docusaurus Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Eleventy Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Ember Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Gatsby Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Gridsome Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Hexo Example
|
||||
|
||||
|
||||
2
examples/hugo/themes/ananke/.gitignore
vendored
2
examples/hugo/themes/ananke/.gitignore
vendored
@@ -28,3 +28,5 @@ npm-debug.log
|
||||
|
||||
/junit.xml
|
||||
partials/structure/stylesheet.html
|
||||
|
||||
!dist
|
||||
|
||||
3
examples/hugo/themes/ananke/static/dist/css/app.d98f2eb6bcd1eaedb7edf166bd16af26.css
vendored
Normal file
3
examples/hugo/themes/ananke/static/dist/css/app.d98f2eb6bcd1eaedb7edf166bd16af26.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
examples/hugo/themes/ananke/static/dist/js/app.3fc0f988d21662902933.js
vendored
Normal file
1
examples/hugo/themes/ananke/static/dist/js/app.3fc0f988d21662902933.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(n){function t(e){if(r[e])return r[e].exports;var o=r[e]={i:e,l:!1,exports:{}};return n[e].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var r={};t.m=n,t.c=r,t.i=function(n){return n},t.d=function(n,r,e){t.o(n,r)||Object.defineProperty(n,r,{configurable:!1,enumerable:!0,get:e})},t.n=function(n){var r=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(r,"a",r),r},t.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},t.p="",t(t.s=1)}([function(n,t){},function(n,t,r){"use strict";var e=r(0);!function(n){n&&n.__esModule}(e)}]);
|
||||
@@ -25,18 +25,3 @@ You can deploy your new Jekyll project with a single command from your terminal
|
||||
```shell
|
||||
$ now
|
||||
```
|
||||
|
||||
### Example Changes
|
||||
|
||||
This example adds a `package.json` file with the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "jekyll build && mv _site public"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This instructs ZEIT Now to build the Jekyll website and move the output to the public directory.
|
||||
|
||||
1
examples/nextjs/.gitignore
vendored
1
examples/nextjs/.gitignore
vendored
@@ -13,7 +13,6 @@
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Next.js Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Polymer Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Preact Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

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

|
||||

|
||||
|
||||
# UmiJS Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Vue.js Example
|
||||
|
||||
|
||||
9
now.json
9
now.json
@@ -1,15 +1,18 @@
|
||||
{
|
||||
"redirects": [
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/",
|
||||
"destination": "https://zeit.co/new",
|
||||
"statusCode": 307
|
||||
"destination": "/api/frameworks"
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
"GITHUB_ACCESS_TOKEN": "@now-api-examples-github-token",
|
||||
"SENTRY_DSN": "@sentry-product-dsn"
|
||||
},
|
||||
"github": {
|
||||
"silent": true,
|
||||
"autoJobCancelation": true
|
||||
},
|
||||
"headers": [
|
||||
{
|
||||
"source": "/api/frameworks",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"publish-stable": "echo 'Run `yarn changelog` for instructions'",
|
||||
"publish-canary": "git pull && lerna version prerelease --preid canary --message 'Publish Canary' --exact",
|
||||
"publish-from-github": "./.circleci/publish.sh",
|
||||
"publish-from-github": "./utils/publish.sh",
|
||||
"changelog": "node utils/changelog.js",
|
||||
"build": "node utils/run.js build all",
|
||||
"test-lint": "node utils/run.js test-lint",
|
||||
@@ -91,6 +91,7 @@
|
||||
"@typescript-eslint/camelcase": 0,
|
||||
"@typescript-eslint/explicit-function-return-type": 0,
|
||||
"@typescript-eslint/no-empty-function": 0,
|
||||
"@typescript-eslint/no-unused-vars": 2,
|
||||
"@typescript-eslint/no-use-before-define": 0
|
||||
},
|
||||
"overrides": [
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
},
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"placeholder": "`next build` or `build` from `package.json`"
|
||||
"placeholder": "`build` from `package.json` or `next build`"
|
||||
},
|
||||
"devCommand": {
|
||||
"value": "next dev --port $PORT"
|
||||
@@ -580,7 +580,7 @@
|
||||
},
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"value": "hugo"
|
||||
"value": "hugo -D --gc"
|
||||
},
|
||||
"devCommand": {
|
||||
"value": "hugo server -D -w -p $PORT"
|
||||
@@ -808,5 +808,22 @@
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/foundation.svg",
|
||||
"tagline": "Foundation is the most advanced responsive front-end framework in the world.",
|
||||
"description": "A Foundation app, created with the Foundation CLI."
|
||||
},
|
||||
{
|
||||
"name": "Other",
|
||||
"slug": null,
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/other.svg",
|
||||
"description": "No framework or a unoptimized framework.",
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"placeholder": "`build` or `now-build` from `package.json` if it exists"
|
||||
},
|
||||
"devCommand": {
|
||||
"placeholder": "None"
|
||||
},
|
||||
"outputDirectory": {
|
||||
"placeholder": "`public` if it exists, or `.`"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
8
packages/frameworks/index.d.ts
vendored
8
packages/frameworks/index.d.ts
vendored
@@ -13,11 +13,11 @@ export type Setting = SettingValue | SettingPlaceholder;
|
||||
|
||||
export interface Framework {
|
||||
name: string;
|
||||
slug: string;
|
||||
slug: string | null;
|
||||
logo: string;
|
||||
demo: string;
|
||||
tagline: string;
|
||||
website: string;
|
||||
demo?: string;
|
||||
tagline?: string;
|
||||
website?: string;
|
||||
description: string;
|
||||
detectors?: {
|
||||
every?: FrameworkDetectionItem[];
|
||||
|
||||
3
packages/frameworks/logos/other.svg
Normal file
3
packages/frameworks/logos/other.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 4L16 15H4L10 4Z" stroke="#666666" stroke-dasharray="2 2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 215 B |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/frameworks",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.10-canary.0",
|
||||
"main": "frameworks.json",
|
||||
"license": "UNLICENSED"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/build-utils",
|
||||
"version": "1.3.6",
|
||||
"version": "2.0.0-canary.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,423 +0,0 @@
|
||||
import { parse as parsePath } from 'path';
|
||||
import { Route, Source } from '@now/routing-utils';
|
||||
import { Builder } from './types';
|
||||
import { getIgnoreApiFilter, sortFiles } from './detect-builders';
|
||||
|
||||
function escapeName(name: string) {
|
||||
const special = '[]^$.|?*+()'.split('');
|
||||
|
||||
for (const char of special) {
|
||||
name = name.replace(new RegExp(`\\${char}`, 'g'), `\\${char}`);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
function joinPath(...segments: string[]) {
|
||||
const joinedPath = segments.join('/');
|
||||
return joinedPath.replace(/\/{2,}/g, '/');
|
||||
}
|
||||
|
||||
function concatArrayOfText(texts: string[]): string {
|
||||
if (texts.length <= 2) {
|
||||
return texts.join(' and ');
|
||||
}
|
||||
|
||||
const last = texts.pop();
|
||||
return `${texts.join(', ')}, and ${last}`;
|
||||
}
|
||||
|
||||
// Takes a filename or foldername, strips the extension
|
||||
// gets the part between the "[]" brackets.
|
||||
// It will return `null` if there are no brackets
|
||||
// and therefore no segment.
|
||||
function getSegmentName(segment: string): string | null {
|
||||
const { name } = parsePath(segment);
|
||||
|
||||
if (name.startsWith('[') && name.endsWith(']')) {
|
||||
return name.slice(1, -1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function createRouteFromPath(
|
||||
filePath: string,
|
||||
featHandleMiss: boolean,
|
||||
cleanUrls: boolean
|
||||
): { route: Source; isDynamic: boolean } {
|
||||
const parts = filePath.split('/');
|
||||
|
||||
let counter = 1;
|
||||
const query: string[] = [];
|
||||
let isDynamic = false;
|
||||
|
||||
const srcParts = parts.map((segment, i): string => {
|
||||
const name = getSegmentName(segment);
|
||||
const isLast = i === parts.length - 1;
|
||||
|
||||
if (name !== null) {
|
||||
// We can't use `URLSearchParams` because `$` would get escaped
|
||||
query.push(`${name}=$${counter++}`);
|
||||
isDynamic = true;
|
||||
return `([^/]+)`;
|
||||
} else if (isLast) {
|
||||
const { name: fileName, ext } = parsePath(segment);
|
||||
const isIndex = fileName === 'index';
|
||||
const prefix = isIndex ? '\\/' : '';
|
||||
|
||||
const names = [
|
||||
isIndex ? prefix : `${fileName}\\/`,
|
||||
prefix + escapeName(fileName),
|
||||
featHandleMiss && cleanUrls
|
||||
? ''
|
||||
: prefix + escapeName(fileName) + escapeName(ext),
|
||||
].filter(Boolean);
|
||||
|
||||
// Either filename with extension, filename without extension
|
||||
// or nothing when the filename is `index`.
|
||||
// When `cleanUrls: true` then do *not* add the filename with extension.
|
||||
return `(${names.join('|')})${isIndex ? '?' : ''}`;
|
||||
}
|
||||
|
||||
return segment;
|
||||
});
|
||||
|
||||
const { name: fileName, ext } = parsePath(filePath);
|
||||
const isIndex = fileName === 'index';
|
||||
const queryString = `${query.length ? '?' : ''}${query.join('&')}`;
|
||||
|
||||
const src = isIndex
|
||||
? `^/${srcParts.slice(0, -1).join('/')}${srcParts.slice(-1)[0]}$`
|
||||
: `^/${srcParts.join('/')}$`;
|
||||
|
||||
let route: Source;
|
||||
if (featHandleMiss) {
|
||||
const extensionless = ext ? filePath.slice(0, -ext.length) : filePath;
|
||||
route = {
|
||||
src,
|
||||
dest: `/${extensionless}${queryString}`,
|
||||
check: true,
|
||||
};
|
||||
} else {
|
||||
route = {
|
||||
src,
|
||||
dest: `/${filePath}${queryString}`,
|
||||
};
|
||||
}
|
||||
return { route, isDynamic };
|
||||
}
|
||||
|
||||
// Check if the path partially matches and has the same
|
||||
// name for the path segment at the same position
|
||||
function partiallyMatches(pathA: string, pathB: string): boolean {
|
||||
const partsA = pathA.split('/');
|
||||
const partsB = pathB.split('/');
|
||||
|
||||
const long = partsA.length > partsB.length ? partsA : partsB;
|
||||
const short = long === partsA ? partsB : partsA;
|
||||
|
||||
let index = 0;
|
||||
|
||||
for (const segmentShort of short) {
|
||||
const segmentLong = long[index];
|
||||
|
||||
const nameLong = getSegmentName(segmentLong);
|
||||
const nameShort = getSegmentName(segmentShort);
|
||||
|
||||
// If there are no segments or the paths differ we
|
||||
// return as they are not matching
|
||||
if (segmentShort !== segmentLong && (!nameLong || !nameShort)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nameLong !== nameShort) {
|
||||
return true;
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Counts how often a path occurs when all placeholders
|
||||
// got resolved, so we can check if they have conflicts
|
||||
function pathOccurrences(filePath: string, files: string[]): string[] {
|
||||
const getAbsolutePath = (unresolvedPath: string): string => {
|
||||
const { dir, name } = parsePath(unresolvedPath);
|
||||
const parts = joinPath(dir, name).split('/');
|
||||
return parts.map(part => part.replace(/\[.*\]/, '1')).join('/');
|
||||
};
|
||||
|
||||
const currentAbsolutePath = getAbsolutePath(filePath);
|
||||
|
||||
return files.reduce((prev: string[], file: string): string[] => {
|
||||
const absolutePath = getAbsolutePath(file);
|
||||
|
||||
if (absolutePath === currentAbsolutePath) {
|
||||
prev.push(file);
|
||||
} else if (partiallyMatches(filePath, file)) {
|
||||
prev.push(file);
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, []);
|
||||
}
|
||||
|
||||
// Checks if a placeholder with the same name is used
|
||||
// multiple times inside the same path
|
||||
function getConflictingSegment(filePath: string): string | null {
|
||||
const segments = new Set<string>();
|
||||
|
||||
for (const segment of filePath.split('/')) {
|
||||
const name = getSegmentName(segment);
|
||||
|
||||
if (name !== null && segments.has(name)) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
segments.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function sortFilesBySegmentCount(fileA: string, fileB: string): number {
|
||||
const lengthA = fileA.split('/').length;
|
||||
const lengthB = fileB.split('/').length;
|
||||
|
||||
if (lengthA > lengthB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (lengthA < lengthB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Paths that have the same segment length but
|
||||
// less placeholders are preferred
|
||||
const countSegments = (prev: number, segment: string) =>
|
||||
getSegmentName(segment) ? prev + 1 : 0;
|
||||
const segmentLengthA = fileA.split('/').reduce(countSegments, 0);
|
||||
const segmentLengthB = fileB.split('/').reduce(countSegments, 0);
|
||||
|
||||
if (segmentLengthA > segmentLengthB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (segmentLengthA < segmentLengthB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
interface ApiRoutesResult {
|
||||
defaultRoutes: Source[] | null;
|
||||
dynamicRoutes: Source[] | null;
|
||||
error: { [key: string]: string } | null;
|
||||
}
|
||||
|
||||
interface RoutesResult {
|
||||
defaultRoutes: Route[] | null;
|
||||
redirectRoutes: Route[] | null;
|
||||
error: { [key: string]: string } | null;
|
||||
}
|
||||
|
||||
async function detectApiRoutes(
|
||||
files: string[],
|
||||
builders: Builder[],
|
||||
featHandleMiss: boolean,
|
||||
cleanUrls: boolean
|
||||
): Promise<ApiRoutesResult> {
|
||||
if (!files || files.length === 0) {
|
||||
return {
|
||||
defaultRoutes: null,
|
||||
dynamicRoutes: null,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
// The deepest routes need to be
|
||||
// the first ones to get handled
|
||||
const sortedFiles = files
|
||||
.filter(getIgnoreApiFilter(builders))
|
||||
.sort(sortFiles)
|
||||
.sort(sortFilesBySegmentCount);
|
||||
|
||||
const defaultRoutes: Source[] = [];
|
||||
const dynamicRoutes: Source[] = [];
|
||||
|
||||
for (const file of sortedFiles) {
|
||||
// We only consider every file in the api directory
|
||||
// as we will strip extensions as well as resolving "[segments]"
|
||||
if (
|
||||
!file.startsWith('api/') &&
|
||||
!builders.some(b => b.src === file && b.config && b.config.functions)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const conflictingSegment = getConflictingSegment(file);
|
||||
|
||||
if (conflictingSegment) {
|
||||
return {
|
||||
defaultRoutes: null,
|
||||
dynamicRoutes: null,
|
||||
error: {
|
||||
code: 'conflicting_path_segment',
|
||||
message:
|
||||
`The segment "${conflictingSegment}" occurs more than ` +
|
||||
`one time in your path "${file}". Please make sure that ` +
|
||||
`every segment in a path is unique`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const occurrences = pathOccurrences(file, sortedFiles).filter(
|
||||
name => name !== file
|
||||
);
|
||||
|
||||
if (occurrences.length > 0) {
|
||||
const messagePaths = concatArrayOfText(
|
||||
occurrences.map(name => `"${name}"`)
|
||||
);
|
||||
|
||||
return {
|
||||
defaultRoutes: null,
|
||||
dynamicRoutes: null,
|
||||
error: {
|
||||
code: 'conflicting_file_path',
|
||||
message:
|
||||
`Two or more files have conflicting paths or names. ` +
|
||||
`Please make sure path segments and filenames, without their extension, are unique. ` +
|
||||
`The path "${file}" has conflicts with ${messagePaths}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const out = createRouteFromPath(file, featHandleMiss, cleanUrls);
|
||||
if (out.isDynamic) {
|
||||
dynamicRoutes.push(out.route);
|
||||
}
|
||||
defaultRoutes.push(out.route);
|
||||
}
|
||||
|
||||
return { defaultRoutes, dynamicRoutes, error: null };
|
||||
}
|
||||
|
||||
function getPublicBuilder(builders: Builder[]): Builder | null {
|
||||
const builder = builders.find(
|
||||
builder =>
|
||||
builder.use === '@now/static' &&
|
||||
/^.*\/\*\*\/\*$/.test(builder.src) &&
|
||||
builder.config &&
|
||||
builder.config.zeroConfig === true
|
||||
);
|
||||
|
||||
return builder || null;
|
||||
}
|
||||
|
||||
export function detectOutputDirectory(builders: Builder[]): string | null {
|
||||
// TODO: We eventually want to save the output directory to
|
||||
// builder.config.outputDirectory so it is only detected once
|
||||
const publicBuilder = getPublicBuilder(builders);
|
||||
return publicBuilder ? publicBuilder.src.replace('/**/*', '') : null;
|
||||
}
|
||||
|
||||
export function detectApiDirectory(builders: Builder[]): string | null {
|
||||
// TODO: We eventually want to save the api directory to
|
||||
// builder.config.apiDirectory so it is only detected once
|
||||
const isZeroConfig = builders.some(b => b.config && b.config.zeroConfig);
|
||||
return isZeroConfig ? 'api' : null;
|
||||
}
|
||||
|
||||
export async function detectRoutes(
|
||||
files: string[],
|
||||
builders: Builder[],
|
||||
featHandleMiss = false,
|
||||
cleanUrls = false,
|
||||
trailingSlash?: boolean
|
||||
): Promise<RoutesResult> {
|
||||
const result = await detectApiRoutes(
|
||||
files,
|
||||
builders,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
);
|
||||
const { dynamicRoutes, defaultRoutes: allRoutes, error } = result;
|
||||
if (error) {
|
||||
return { defaultRoutes: null, redirectRoutes: null, error };
|
||||
}
|
||||
const directory = detectOutputDirectory(builders);
|
||||
const defaultRoutes: Route[] = [];
|
||||
const redirectRoutes: Route[] = [];
|
||||
if (allRoutes && allRoutes.length > 0) {
|
||||
const hasApiRoutes = allRoutes.some(
|
||||
r => r.dest && r.dest.startsWith('/api/')
|
||||
);
|
||||
if (featHandleMiss) {
|
||||
defaultRoutes.push({ handle: 'miss' });
|
||||
const extSet = new Set(
|
||||
builders
|
||||
.filter(b => b.src && b.src.startsWith('api/'))
|
||||
.map(b => parsePath(b.src).ext)
|
||||
.filter(Boolean)
|
||||
);
|
||||
if (extSet.size > 0) {
|
||||
const exts = Array.from(extSet)
|
||||
.map(ext => ext.slice(1))
|
||||
.join('|');
|
||||
const extGroup = `(?:\\.(?:${exts}))`;
|
||||
if (cleanUrls) {
|
||||
redirectRoutes.push({
|
||||
src: `^/(api(?:.+)?)/index${extGroup}?/?$`,
|
||||
headers: { Location: trailingSlash ? '/$1/' : '/$1' },
|
||||
status: 308,
|
||||
});
|
||||
redirectRoutes.push({
|
||||
src: `^/api/(.+)${extGroup}/?$`,
|
||||
headers: { Location: trailingSlash ? '/api/$1/' : '/api/$1' },
|
||||
status: 308,
|
||||
});
|
||||
} else {
|
||||
defaultRoutes.push({
|
||||
src: `^/api/(.+)${extGroup}$`,
|
||||
dest: '/api/$1',
|
||||
check: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (dynamicRoutes) {
|
||||
defaultRoutes.push(...dynamicRoutes);
|
||||
}
|
||||
if (hasApiRoutes) {
|
||||
defaultRoutes.push({
|
||||
src: '^/api(/.*)?$',
|
||||
status: 404,
|
||||
continue: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
defaultRoutes.push(...allRoutes);
|
||||
if (hasApiRoutes) {
|
||||
defaultRoutes.push({
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!featHandleMiss && directory) {
|
||||
defaultRoutes.push({
|
||||
src: '/(.*)',
|
||||
dest: `/${directory}/$1`,
|
||||
});
|
||||
}
|
||||
|
||||
return { defaultRoutes, redirectRoutes, error };
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { promisify } from 'util';
|
||||
import { lstat, Stats } from 'fs-extra';
|
||||
import FileFsRef from '../file-fs-ref';
|
||||
|
||||
type GlobOptions = vanillaGlob_.IOptions;
|
||||
export type GlobOptions = vanillaGlob_.IOptions;
|
||||
|
||||
interface FsFiles {
|
||||
[filePath: string]: FileFsRef;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
|
||||
import { Prerender } from './prerender';
|
||||
import download, { DownloadedFiles, isSymbolicLink } from './fs/download';
|
||||
import getWriteableDirectory from './fs/get-writable-directory';
|
||||
import glob from './fs/glob';
|
||||
import glob, { GlobOptions } from './fs/glob';
|
||||
import rename from './fs/rename';
|
||||
import {
|
||||
execAsync,
|
||||
@@ -40,6 +40,7 @@ export {
|
||||
DownloadedFiles,
|
||||
getWriteableDirectory,
|
||||
glob,
|
||||
GlobOptions,
|
||||
rename,
|
||||
execAsync,
|
||||
spawnAsync,
|
||||
@@ -63,11 +64,11 @@ export {
|
||||
};
|
||||
|
||||
export {
|
||||
detectRoutes,
|
||||
detectBuilders,
|
||||
detectOutputDirectory,
|
||||
detectApiDirectory,
|
||||
} from './detect-routes';
|
||||
export { detectBuilders } from './detect-builders';
|
||||
detectApiExtensions,
|
||||
} from './detect-builders';
|
||||
export { detectFramework } from './detect-framework';
|
||||
export { DetectorFilesystem } from './detectors/filesystem';
|
||||
export { readConfigFile } from './fs/read-config-file';
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface Config {
|
||||
| undefined;
|
||||
maxLambdaSize?: string;
|
||||
includeFiles?: string | string[];
|
||||
excludeFiles?: string | string[];
|
||||
bundle?: boolean;
|
||||
ldsflags?: string;
|
||||
helpers?: boolean;
|
||||
|
||||
@@ -4,7 +4,7 @@ const {
|
||||
packAndDeploy,
|
||||
testDeployment,
|
||||
} = require('../../../test/lib/deployment/test-deployment');
|
||||
const { glob, detectBuilders, detectRoutes } = require('../');
|
||||
const { glob, detectBuilders } = require('../');
|
||||
|
||||
jest.setTimeout(4 * 60 * 1000);
|
||||
|
||||
@@ -110,8 +110,7 @@ it('Test `detectBuilders` and `detectRoutes`', async () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
const { builders, defaultRoutes } = await detectBuilders(files, pkg);
|
||||
|
||||
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
||||
await fs.writeFile(
|
||||
@@ -126,7 +125,7 @@ it('Test `detectBuilders` and `detectRoutes`', async () => {
|
||||
expect(deployment).toBeDefined();
|
||||
});
|
||||
|
||||
it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
|
||||
it('Test `detectBuilders` with `index` files', async () => {
|
||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
||||
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
|
||||
const fileList = await glob('**', fixture);
|
||||
@@ -192,8 +191,7 @@ it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
const { builders, defaultRoutes } = await detectBuilders(files, pkg);
|
||||
|
||||
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
||||
await fs.writeFile(
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Source, Route } from '@now/routing-utils';
|
||||
import { detectBuilders, detectRoutes } from '../src';
|
||||
import { detectOutputDirectory, detectApiDirectory } from '../';
|
||||
import { detectBuilders } from '../src';
|
||||
import {
|
||||
detectOutputDirectory,
|
||||
detectApiDirectory,
|
||||
detectApiExtensions,
|
||||
} from '../';
|
||||
|
||||
describe('Test `detectBuilders`', () => {
|
||||
it('package.json + no build', async () => {
|
||||
@@ -147,6 +151,27 @@ describe('Test `detectBuilders`', () => {
|
||||
expect(builders!.length).toBe(2);
|
||||
});
|
||||
|
||||
it('api go with test files', async () => {
|
||||
const files = [
|
||||
'api/index.go',
|
||||
'api/index_test.go',
|
||||
'api/test.go',
|
||||
'api/testing_another.go',
|
||||
'api/readme.md',
|
||||
'api/config/staging.go',
|
||||
'api/config/staging_test.go',
|
||||
'api/config/production.go',
|
||||
'api/config/production_test.go',
|
||||
'api/src/controllers/health.go',
|
||||
'api/src/controllers/user.module.go',
|
||||
'api/src/controllers/user.module_test.go',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
expect(builders!.length).toBe(7);
|
||||
expect(builders!.some(b => b.src.endsWith('_test.go'))).toBe(false);
|
||||
});
|
||||
|
||||
it('just public', async () => {
|
||||
const files = ['public/index.html', 'public/favicon.ico', 'README.md'];
|
||||
|
||||
@@ -474,7 +499,7 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
expect(errors!.length).toBe(1);
|
||||
expect(errors![0].code).toBe('invalid_function_source');
|
||||
expect(errors![0].code).toBe('unused_function');
|
||||
});
|
||||
|
||||
it('do not allow empty functions', async () => {
|
||||
@@ -623,14 +648,14 @@ describe('Test `detectBuilders`', () => {
|
||||
|
||||
const files = ['dist/index.html', 'dist/style.css'];
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { projectSettings });
|
||||
const { builders, defaultRoutes } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
});
|
||||
|
||||
expect(builders!.length).toBe(1);
|
||||
expect(builders![0].src).toBe('dist/**/*');
|
||||
expect(builders![0].use).toBe('@now/static');
|
||||
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
|
||||
expect(defaultRoutes!.length).toBe(1);
|
||||
expect((defaultRoutes![0] as any).src).toBe('/(.*)');
|
||||
expect((defaultRoutes![0] as any).dest).toBe('/dist/$1');
|
||||
@@ -643,14 +668,14 @@ describe('Test `detectBuilders`', () => {
|
||||
|
||||
const files = ['api/user.ts', 'output/index.html', 'output/style.css'];
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { projectSettings });
|
||||
const { builders, defaultRoutes } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
});
|
||||
|
||||
expect(builders!.length).toBe(2);
|
||||
expect(builders![1].src).toBe('output/**/*');
|
||||
expect(builders![1].use).toBe('@now/static');
|
||||
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
|
||||
expect(defaultRoutes!.length).toBe(3);
|
||||
expect((defaultRoutes![1] as any).status).toBe(404);
|
||||
expect((defaultRoutes![2] as any).src).toBe('/(.*)');
|
||||
@@ -741,14 +766,60 @@ describe('Test `detectBuilders`', () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('All static if `buildCommand` is an empty string', async () => {
|
||||
const files = ['index.html'];
|
||||
const projectSettings = { buildCommand: '' };
|
||||
const { builders, errors } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
});
|
||||
expect(errors).toBe(null);
|
||||
expect(builders).toBe(null);
|
||||
});
|
||||
|
||||
it('All static if `outputDirectory` is an empty string', async () => {
|
||||
const files = ['index.html'];
|
||||
const projectSettings = { outputDirectory: '' };
|
||||
const { builders, errors } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
});
|
||||
expect(errors).toBe(null);
|
||||
expect(builders).toBe(null);
|
||||
});
|
||||
|
||||
it('All static if `buildCommand` is an empty string with an `outputDirectory`', async () => {
|
||||
const files = ['out/index.html'];
|
||||
const projectSettings = { buildCommand: '', outputDirectory: 'out' };
|
||||
const { builders, errors } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
});
|
||||
expect(errors).toBe(null);
|
||||
expect(builders![0]!.use).toBe('@now/static');
|
||||
expect(builders![0]!.src).toBe('out/**/*');
|
||||
});
|
||||
|
||||
it('do not require build script when `buildCommand` is an empty string', async () => {
|
||||
const files = ['index.html', 'about.html', 'package.json'];
|
||||
const projectSettings = { buildCommand: '', outputDirectory: '' };
|
||||
const pkg = {
|
||||
scripts: {
|
||||
build: 'false',
|
||||
},
|
||||
};
|
||||
|
||||
const { builders, errors } = await detectBuilders(files, pkg, {
|
||||
projectSettings,
|
||||
});
|
||||
expect(builders).toBe(null);
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('Test `detectRoutes`', async () => {
|
||||
{
|
||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files);
|
||||
expect(defaultRoutes!.length).toBe(3);
|
||||
expect((defaultRoutes![0] as any).dest).toBe('/api/team.js');
|
||||
expect((defaultRoutes![1] as any).dest).toBe('/api/user.go');
|
||||
@@ -759,41 +830,36 @@ it('Test `detectRoutes`', async () => {
|
||||
{
|
||||
const files = ['api/user.go', 'api/user.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders!);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
const { errors } = await detectBuilders(files);
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[user].go', 'api/[team]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders!);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
const { errors } = await detectBuilders(files);
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[team]/[team].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders!);
|
||||
expect(error!.code).toBe('conflicting_path_segment');
|
||||
const { errors } = await detectBuilders(files);
|
||||
expect(errors![0]!.code).toBe('conflicting_path_segment');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date/index.go'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, error } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes, errors } = await detectBuilders(files);
|
||||
expect(defaultRoutes).toBe(null);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files);
|
||||
expect(defaultRoutes!.length).toBe(3);
|
||||
}
|
||||
|
||||
@@ -804,8 +870,7 @@ it('Test `detectRoutes`', async () => {
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files);
|
||||
expect((defaultRoutes![2] as any).status).toBe(404);
|
||||
expect((defaultRoutes![2] as any).src).toBe('^/api(/.*)?$');
|
||||
expect((defaultRoutes![3] as any).src).toBe('/(.*)');
|
||||
@@ -820,8 +885,7 @@ it('Test `detectRoutes`', async () => {
|
||||
};
|
||||
const files = ['public/index.html', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files, pkg);
|
||||
expect((defaultRoutes![1] as any).status).toBe(404);
|
||||
expect((defaultRoutes![1] as any).src).toBe('^/api(/.*)?$');
|
||||
expect(defaultRoutes!.length).toBe(2);
|
||||
@@ -830,8 +894,7 @@ it('Test `detectRoutes`', async () => {
|
||||
{
|
||||
const files = ['public/index.html'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files);
|
||||
|
||||
expect(defaultRoutes!.length).toBe(1);
|
||||
}
|
||||
@@ -839,8 +902,7 @@ it('Test `detectRoutes`', async () => {
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files);
|
||||
|
||||
expect(defaultRoutes!.length).toBe(3);
|
||||
expect((defaultRoutes![0] as any).src).toBe(
|
||||
@@ -856,8 +918,7 @@ it('Test `detectRoutes`', async () => {
|
||||
{
|
||||
const files = ['api/date.js', 'api/[date]/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files);
|
||||
|
||||
expect(defaultRoutes!.length).toBe(3);
|
||||
expect((defaultRoutes![0] as any).src).toBe(
|
||||
@@ -881,8 +942,7 @@ it('Test `detectRoutes`', async () => {
|
||||
'api/food.ts',
|
||||
'api/ts/gold.ts',
|
||||
];
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { builders, defaultRoutes } = await detectBuilders(files);
|
||||
|
||||
expect(builders!.length).toBe(4);
|
||||
expect(builders![0].use).toBe('@now/node');
|
||||
@@ -897,8 +957,7 @@ it('Test `detectRoutes`', async () => {
|
||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
||||
const files = ['api/user.php'];
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { functions });
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, { functions });
|
||||
|
||||
expect(defaultRoutes!.length).toBe(2);
|
||||
expect((defaultRoutes![0] as any).dest).toBe('/api/user.php');
|
||||
@@ -911,12 +970,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -935,49 +991,40 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
const files = ['api/user.go', 'api/user.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders!, featHandleMiss);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
const { errors } = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[user].go', 'api/[team]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders!, featHandleMiss);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
const { errors } = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[team]/[team].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders!, featHandleMiss);
|
||||
expect(error!.code).toBe('conflicting_path_segment');
|
||||
const { errors } = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(errors![0]!.code).toBe('conflicting_path_segment');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date/index.go'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, error } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes, errors } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toBe(null);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -1010,12 +1057,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -1054,12 +1098,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
|
||||
const files = ['public/index.html', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, pkg, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -1083,24 +1124,18 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
const files = ['public/index.html'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -1119,12 +1154,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
const files = ['api/date.js', 'api/[date]/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -1154,12 +1186,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
'api/food.ts',
|
||||
'api/ts/gold.ts',
|
||||
];
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
@@ -1177,12 +1206,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
||||
const files = ['api/user.php'];
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { functions });
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
functions,
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -1196,8 +1223,11 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
});
|
||||
|
||||
it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async () => {
|
||||
const featHandleMiss = true;
|
||||
const cleanUrls = true;
|
||||
const options = {
|
||||
featHandleMiss: true,
|
||||
cleanUrls: true,
|
||||
};
|
||||
|
||||
const testHeaders = (redirectRoutes: Route[] | null) => {
|
||||
if (!redirectRoutes || redirectRoutes.length === 0) {
|
||||
throw new Error('Expected one redirect but found none');
|
||||
@@ -1209,12 +1239,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1256,65 +1284,43 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
const files = ['api/user.go', 'api/user.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
const { errors } = await detectBuilders(files, null, options);
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[user].go', 'api/[team]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
const { errors } = await detectBuilders(files, null, options);
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[team]/[team].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
);
|
||||
expect(error!.code).toBe('conflicting_path_segment');
|
||||
const { errors } = await detectBuilders(files, null, options);
|
||||
expect(errors![0]!.code).toBe('conflicting_path_segment');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date/index.go'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, error } = await detectRoutes(
|
||||
const { defaultRoutes, errors } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
expect(defaultRoutes).toBe(null);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1344,12 +1350,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1385,12 +1389,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
|
||||
const files = ['public/index.html', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
pkg,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1411,25 +1413,17 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
const files = ['public/index.html'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, options);
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1445,12 +1439,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
const files = ['api/date.js', 'api/[date]/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1477,12 +1469,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
'api/food.ts',
|
||||
'api/ts/gold.ts',
|
||||
];
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1496,12 +1486,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
||||
const files = ['api/user.php'];
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { functions });
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
{ functions, ...options }
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1512,9 +1500,12 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
});
|
||||
|
||||
it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingSlash=true`', async () => {
|
||||
const featHandleMiss = true;
|
||||
const cleanUrls = true;
|
||||
const trailingSlash = true;
|
||||
const options = {
|
||||
featHandleMiss: true,
|
||||
cleanUrls: true,
|
||||
trailingSlash: true,
|
||||
};
|
||||
|
||||
const testHeaders = (redirectRoutes: Route[] | null) => {
|
||||
if (!redirectRoutes || redirectRoutes.length === 0) {
|
||||
throw new Error('Expected one redirect but found none');
|
||||
@@ -1526,13 +1517,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1577,13 +1565,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1613,13 +1598,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1655,13 +1637,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
|
||||
const files = ['public/index.html', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
pkg,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1682,13 +1661,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1704,13 +1680,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
const files = ['api/date.js', 'api/[date]/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1737,13 +1710,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
'api/food.ts',
|
||||
'api/ts/gold.ts',
|
||||
];
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1757,13 +1727,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
||||
const files = ['api/user.php'];
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { functions });
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
{ functions, ...options }
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1877,6 +1844,93 @@ describe('Test `detectApiDirectory`', () => {
|
||||
const result = detectApiDirectory(builders);
|
||||
expect(result).toBe('api');
|
||||
});
|
||||
|
||||
it('should be `null` with zero config but without api directory', async () => {
|
||||
const builders = [
|
||||
{
|
||||
use: '@now/next',
|
||||
src: 'package.json',
|
||||
config: { zeroConfig: true },
|
||||
},
|
||||
];
|
||||
const result = detectApiDirectory(builders);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test `detectApiExtensions`', () => {
|
||||
it('should have correct extensions', async () => {
|
||||
const builders = [
|
||||
{
|
||||
use: '@now/node',
|
||||
src: 'api/**/*.js',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
use: '@now/python',
|
||||
src: 'api/**/*.py',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
use: '@now/go',
|
||||
src: 'api/**/*.go',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
use: '@now/ruby',
|
||||
src: 'api/**/*.rb',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
use: 'now-bash',
|
||||
src: 'api/**/*.sh',
|
||||
// No zero config so it should not be added
|
||||
},
|
||||
{
|
||||
use: 'now-no-extension',
|
||||
src: 'api/executable',
|
||||
// No extension should not be added
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
use: '@now/next',
|
||||
src: 'package.json',
|
||||
// No api directory should not be added
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
use: 'now-rust@1.0.1',
|
||||
src: 'api/user.rs',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
functions: {
|
||||
'api/**/*.rs': {
|
||||
runtime: 'now-rust@1.0.1',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const result = detectApiExtensions(builders);
|
||||
expect(result.size).toBe(5);
|
||||
expect(result.has('.js')).toBe(true);
|
||||
expect(result.has('.py')).toBe(true);
|
||||
expect(result.has('.go')).toBe(true);
|
||||
expect(result.has('.rb')).toBe(true);
|
||||
expect(result.has('.rs')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
11
packages/now-build-utils/test/unit.test.js
vendored
11
packages/now-build-utils/test/unit.test.js
vendored
@@ -11,6 +11,10 @@ const {
|
||||
} = require('../dist');
|
||||
|
||||
it('should re-create symlinks properly', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping test on windows');
|
||||
return;
|
||||
}
|
||||
const files = await glob('**', path.join(__dirname, 'symlinks'));
|
||||
assert.equal(Object.keys(files).length, 2);
|
||||
|
||||
@@ -29,6 +33,10 @@ it('should re-create symlinks properly', async () => {
|
||||
});
|
||||
|
||||
it('should create zip files with symlinks properly', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping test on windows');
|
||||
return;
|
||||
}
|
||||
const files = await glob('**', path.join(__dirname, 'symlinks'));
|
||||
assert.equal(Object.keys(files).length, 2);
|
||||
|
||||
@@ -116,10 +124,7 @@ it('should throw for discontinued versions', async () => {
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => new Date('2020-02-14').getTime();
|
||||
|
||||
expect(getSupportedNodeVersion('', false)).rejects.toThrow();
|
||||
expect(getSupportedNodeVersion('8.10.x', false)).rejects.toThrow();
|
||||
|
||||
expect(getSupportedNodeVersion('', true)).rejects.toThrow();
|
||||
expect(getSupportedNodeVersion('8.10.x', true)).rejects.toThrow();
|
||||
|
||||
expect(getDiscontinuedNodeVersions().length).toBe(1);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/cgi",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
"@zeit/best@0.4.3":
|
||||
version "0.4.3"
|
||||
resolved "http://registry.npmjs.org/@zeit/best/-/best-0.4.3.tgz#eaebdfa8b24121a97b1753501ea8c9330d549b30"
|
||||
resolved "https://registry.npmjs.org/@zeit/best/-/best-0.4.3.tgz#eaebdfa8b24121a97b1753501ea8c9330d549b30"
|
||||
dependencies:
|
||||
arg "1.0.0"
|
||||
chalk "2.3.1"
|
||||
@@ -144,7 +144,7 @@ call-me-maybe@^1.0.1:
|
||||
|
||||
chalk@2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "http://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
|
||||
dependencies:
|
||||
ansi-styles "^3.2.0"
|
||||
escape-string-regexp "^1.0.5"
|
||||
@@ -585,8 +585,8 @@ minimatch@^3.0.4:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
mixin-deep@^1.2.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
|
||||
dependencies:
|
||||
for-in "^1.0.2"
|
||||
is-extendable "^1.0.1"
|
||||
@@ -704,7 +704,7 @@ rmfr@2.0.0:
|
||||
|
||||
safe-regex@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
|
||||
resolved "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
|
||||
dependencies:
|
||||
ret "~0.1.10"
|
||||
|
||||
|
||||
10
packages/now-cli/@types/is-port-reachable/index.d.ts
vendored
Normal file
10
packages/now-cli/@types/is-port-reachable/index.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
declare module 'is-port-reachable' {
|
||||
export interface IsPortReachableOptions {
|
||||
timeout?: number | undefined;
|
||||
host?: string;
|
||||
}
|
||||
export default function(
|
||||
port: number | undefined,
|
||||
options?: IsPortReachableOptions
|
||||
): Promise<boolean>;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
[](https://spectrum.chat/zeit)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now",
|
||||
"version": "17.0.0-canary.14",
|
||||
"version": "17.0.4-canary.3",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Now",
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "node ./scripts/preinstall.js",
|
||||
"test-unit": "nyc ava test/*unit.js --serial --fail-fast --verbose",
|
||||
"test-unit": "nyc ava test/unit.js test/dev-builder.unit.js test/dev-router.unit.js test/dev-server.unit.js --serial --fail-fast --verbose",
|
||||
"test-integration": "ava test/integration.js --serial --fail-fast",
|
||||
"test-integration-v1": "ava test/integration-v1.js --serial --fail-fast",
|
||||
"test-integration-now-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
|
||||
@@ -59,10 +59,11 @@
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
"node": ">= 10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/node": "5.5.0",
|
||||
"@sindresorhus/slugify": "0.10.0",
|
||||
"@types/ansi-escapes": "3.0.0",
|
||||
"@types/ansi-regex": "4.0.0",
|
||||
"@types/async-retry": "1.2.1",
|
||||
@@ -89,10 +90,10 @@
|
||||
"@types/tar-fs": "1.16.1",
|
||||
"@types/text-table": "0.2.0",
|
||||
"@types/universal-analytics": "0.4.2",
|
||||
"@types/which": "1.3.1",
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@zeit/dockerignore": "0.0.5",
|
||||
"@zeit/fun": "0.11.0",
|
||||
"@zeit/fun": "0.11.2",
|
||||
"@zeit/ncc": "0.18.5",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
"ajv": "6.10.2",
|
||||
@@ -108,7 +109,7 @@
|
||||
"chalk": "2.4.2",
|
||||
"chokidar": "2.1.6",
|
||||
"clipboardy": "2.1.0",
|
||||
"codecov": "3.1.0",
|
||||
"codecov": "3.6.5",
|
||||
"cpy": "7.2.0",
|
||||
"credit-card": "3.0.1",
|
||||
"date-fns": "1.29.0",
|
||||
@@ -126,11 +127,13 @@
|
||||
"esm": "3.1.4",
|
||||
"execa": "3.2.0",
|
||||
"fs-extra": "7.0.1",
|
||||
"get-port": "5.1.1",
|
||||
"glob": "7.1.2",
|
||||
"http-proxy": "1.17.0",
|
||||
"ignore": "4.0.6",
|
||||
"ini": "1.3.4",
|
||||
"inquirer": "3.3.0",
|
||||
"inquirer": "7.0.4",
|
||||
"is-port-reachable": "3.0.0",
|
||||
"is-url": "1.2.2",
|
||||
"jaro-winkler": "0.2.8",
|
||||
"jsonlines": "0.1.1",
|
||||
@@ -172,10 +175,10 @@
|
||||
"universal-analytics": "0.4.20",
|
||||
"update-check": "1.5.3",
|
||||
"utility-types": "2.1.0",
|
||||
"which": "1.3.1",
|
||||
"which": "2.0.2",
|
||||
"which-promise": "1.0.0",
|
||||
"write-json-file": "2.2.0",
|
||||
"xdg-app-paths": "5.1.0",
|
||||
"yarn": "1.17.3"
|
||||
"yarn": "1.22.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,21 +50,23 @@ async function createBuildersTarball() {
|
||||
async function main() {
|
||||
const isDev = process.argv[2] === '--dev';
|
||||
|
||||
// Create a tarball from all the `@now` scoped builders which will be bundled
|
||||
// with Now CLI
|
||||
await createBuildersTarball();
|
||||
if (!isDev) {
|
||||
// Create a tarball from all the `@now` scoped builders which will be bundled
|
||||
// with Now CLI
|
||||
await createBuildersTarball();
|
||||
|
||||
// `now dev` uses chokidar to watch the filesystem, but opts-out of the
|
||||
// `fsevents` feature using `useFsEvents: false`, so delete the module here so
|
||||
// that it is not compiled by ncc, which makes the npm package size larger
|
||||
// than necessary.
|
||||
await remove(join(dirRoot, '../../node_modules/fsevents'));
|
||||
// `now dev` uses chokidar to watch the filesystem, but opts-out of the
|
||||
// `fsevents` feature using `useFsEvents: false`, so delete the module here so
|
||||
// that it is not compiled by ncc, which makes the npm package size larger
|
||||
// than necessary.
|
||||
await remove(join(dirRoot, '../../node_modules/fsevents'));
|
||||
|
||||
// Compile the `doT.js` template files for `now dev`
|
||||
console.log();
|
||||
await execa(process.execPath, [join(__dirname, 'compile-templates.js')], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
// Compile the `doT.js` template files for `now dev`
|
||||
console.log();
|
||||
await execa(process.execPath, [join(__dirname, 'compile-templates.js')], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
|
||||
// Do the initial `ncc` build
|
||||
console.log();
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// This should only be used for the integration tests
|
||||
|
||||
const { join } = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const match = process.version.match(/^v(\d+)\.(\d+)/);
|
||||
const major = match && parseInt(match[1], 10);
|
||||
|
||||
// Must be above or equal to 10.10.0
|
||||
// const isVersion = major > 10 || (major === 10 && minor >= 10);
|
||||
|
||||
if (major === 8 && process.platform === 'darwin') {
|
||||
const [node, _, ...args] = process.argv; // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
const script = join(__dirname, '../dist/index.js');
|
||||
|
||||
const { status } = spawnSync(node, ['--no-warnings', script, ...args], {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
process.exit(status);
|
||||
} else {
|
||||
require('../dist/index.js');
|
||||
}
|
||||
require('../dist/index.js');
|
||||
|
||||
@@ -8,12 +8,11 @@ import getAliases from '../../util/alias/get-aliases';
|
||||
import getScope from '../../util/get-scope.ts';
|
||||
import stamp from '../../util/output/stamp.ts';
|
||||
import strlen from '../../util/strlen.ts';
|
||||
import wait from '../../util/output/wait';
|
||||
|
||||
export default async function ls(ctx, opts, args, output) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
config
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
@@ -22,7 +21,7 @@ export default async function ls(ctx, opts, args, output) {
|
||||
apiUrl,
|
||||
token,
|
||||
currentTeam,
|
||||
debug: debugEnabled
|
||||
debug: debugEnabled,
|
||||
});
|
||||
let contextName = null;
|
||||
|
||||
@@ -51,7 +50,7 @@ export default async function ls(ctx, opts, args, output) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
cancelWait = wait(
|
||||
cancelWait = output.spinner(
|
||||
args[0]
|
||||
? `Fetching alias details for "${args[0]}" under ${chalk.bold(
|
||||
contextName
|
||||
@@ -112,13 +111,13 @@ function printAliasTable(aliases) {
|
||||
? a.deployment.url
|
||||
: chalk.gray('–'),
|
||||
a.alias,
|
||||
ms(Date.now() - new Date(a.created))
|
||||
])
|
||||
ms(Date.now() - new Date(a.created)),
|
||||
]),
|
||||
],
|
||||
{
|
||||
align: ['l', 'l', 'r'],
|
||||
hsep: ' '.repeat(4),
|
||||
stringLength: strlen
|
||||
stringLength: strlen,
|
||||
}
|
||||
).replace(/^/gm, ' ')}\n\n`;
|
||||
}
|
||||
@@ -130,13 +129,13 @@ function printPathAliasTable(rules) {
|
||||
rules.map(rule => [
|
||||
rule.pathname ? rule.pathname : chalk.cyan('[fallthrough]'),
|
||||
rule.method ? rule.method : '*',
|
||||
rule.dest
|
||||
rule.dest,
|
||||
])
|
||||
),
|
||||
{
|
||||
align: ['l', 'l', 'l', 'l'],
|
||||
hsep: ' '.repeat(6),
|
||||
stringLength: strlen
|
||||
stringLength: strlen,
|
||||
}
|
||||
).replace(/^(.*)/gm, ' $1')}\n`;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
name: {
|
||||
label: rightPad('Full Name', 12),
|
||||
placeholder: 'John Appleseed',
|
||||
validateValue: data => data.trim().length > 0
|
||||
validateValue: data => data.trim().length > 0,
|
||||
},
|
||||
|
||||
cardNumber: {
|
||||
@@ -36,7 +36,7 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
return false;
|
||||
}
|
||||
return ccValidator.isValidCardNumber(data, type);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
ccv: {
|
||||
@@ -46,7 +46,7 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
validateValue: data => {
|
||||
const brand = state.cardNumber.brand.toLowerCase();
|
||||
return ccValidator.doesCvvMatchType(data, brand);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
expDate: {
|
||||
@@ -54,8 +54,8 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
mask: 'expDate',
|
||||
placeholder: 'mm / yyyy',
|
||||
middleware: expDateMiddleware,
|
||||
validateValue: data => !ccValidator.isExpired(...data.split(' / '))
|
||||
}
|
||||
validateValue: data => !ccValidator.isExpired(...data.split(' / ')),
|
||||
},
|
||||
};
|
||||
|
||||
async function render() {
|
||||
@@ -80,7 +80,7 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
mask: piece.mask,
|
||||
validateKeypress: piece.validateKeypress,
|
||||
validateValue: piece.validateValue,
|
||||
autoComplete: piece.autoComplete
|
||||
autoComplete: piece.autoComplete,
|
||||
});
|
||||
|
||||
piece.value = result;
|
||||
@@ -135,7 +135,7 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
name: state.name.value,
|
||||
cardNumber: state.cardNumber.value,
|
||||
ccv: state.ccv.value,
|
||||
expDate: state.expDate.value
|
||||
expDate: state.expDate.value,
|
||||
});
|
||||
|
||||
stopSpinner();
|
||||
@@ -156,9 +156,9 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
stopSpinner();
|
||||
const linesToClear = state.error ? 15 : 14;
|
||||
process.stdout.write(ansiEscapes.eraseLines(linesToClear));
|
||||
state.error = `${chalk.red(
|
||||
'> Error!'
|
||||
)} ${err.message} Please make sure the info is correct`;
|
||||
state.error = `${chalk.red('> Error!')} ${
|
||||
err.message
|
||||
} Please make sure the info is correct`;
|
||||
await render();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,11 @@ import Now from '../../util';
|
||||
import Client from '../../util/client';
|
||||
import getScope from '../../util/get-scope';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import wait from '../../util/output/wait';
|
||||
import createCertFromFile from '../../util/certs/create-cert-from-file';
|
||||
import createCertForCns from '../../util/certs/create-cert-for-cns';
|
||||
import { NowContext } from '../../types';
|
||||
import { Output } from '../../util/output';
|
||||
|
||||
import { DomainPermissionDenied, InvalidCert } from '../../util/errors-ts';
|
||||
|
||||
interface Options {
|
||||
'--overwrite'?: boolean;
|
||||
'--debug'?: boolean;
|
||||
@@ -88,7 +85,7 @@ async function add(
|
||||
}
|
||||
|
||||
// Create a custom certificate from the given file paths
|
||||
cert = await createCertFromFile(now, keyPath, crtPath, caPath, contextName);
|
||||
cert = await createCertFromFile(now, keyPath, crtPath, caPath);
|
||||
} else {
|
||||
output.warn(
|
||||
`${chalk.cyan(
|
||||
@@ -112,7 +109,7 @@ async function add(
|
||||
(res, item) => res.concat(item.split(',')),
|
||||
[]
|
||||
);
|
||||
const cancelWait = wait(
|
||||
const cancelWait = output.spinner(
|
||||
`Generating a certificate for ${chalk.bold(cns.join(', '))}`
|
||||
);
|
||||
|
||||
|
||||
@@ -87,13 +87,7 @@ export default async function issue(
|
||||
}
|
||||
|
||||
// Create a custom certificate from the given file paths
|
||||
cert = await createCertFromFile(
|
||||
client,
|
||||
keyPath,
|
||||
crtPath,
|
||||
caPath,
|
||||
contextName
|
||||
);
|
||||
cert = await createCertFromFile(client, keyPath, crtPath, caPath);
|
||||
|
||||
if (cert instanceof Error) {
|
||||
output.error(cert.message);
|
||||
|
||||
@@ -38,7 +38,6 @@ export const latestHelp = () => `
|
||||
-h, --help Output usage information
|
||||
-v, --version Output the version number
|
||||
-V, --platform-version Set the platform version to deploy to
|
||||
-n, --name Set the project name of the deployment
|
||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||
'FILE'
|
||||
)} Path to the local ${'`now.json`'} file
|
||||
@@ -66,6 +65,7 @@ export const latestHelp = () => `
|
||||
-S, --scope Set a custom scope
|
||||
--regions Set default regions to enable the deployment on
|
||||
--prod Create a production deployment
|
||||
-c, --confirm Confirm default options and skip questions
|
||||
|
||||
${note(
|
||||
`To view the usage information for Now 1.0, run ${code(
|
||||
@@ -96,7 +96,6 @@ export const latestHelp = () => `
|
||||
`;
|
||||
|
||||
export const latestArgs = {
|
||||
'--name': String,
|
||||
'--force': Boolean,
|
||||
'--public': Boolean,
|
||||
'--no-clipboard': Boolean,
|
||||
@@ -107,10 +106,8 @@ export const latestArgs = {
|
||||
// This is not an array in favor of matching
|
||||
// the config property name.
|
||||
'--regions': String,
|
||||
'--target': String,
|
||||
'--prod': Boolean,
|
||||
'--confirm': Boolean,
|
||||
'-n': '--name',
|
||||
'-f': '--force',
|
||||
'-p': '--public',
|
||||
'-e': '--env',
|
||||
@@ -118,6 +115,11 @@ export const latestArgs = {
|
||||
'-C': '--no-clipboard',
|
||||
'-m': '--meta',
|
||||
'-c': '--confirm',
|
||||
|
||||
// deprecated
|
||||
'--name': String,
|
||||
'-n': '--name',
|
||||
'--target': String,
|
||||
};
|
||||
|
||||
export const legacyArgsMri = {
|
||||
|
||||
@@ -186,8 +186,7 @@ export default async ctx => {
|
||||
contextName,
|
||||
output,
|
||||
stats,
|
||||
localConfig || {},
|
||||
isFile,
|
||||
localConfig,
|
||||
parts.latestArgs
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ms from 'ms';
|
||||
import bytes from 'bytes';
|
||||
import { join } from 'path';
|
||||
import { write as copy } from 'clipboardy';
|
||||
import chalk from 'chalk';
|
||||
import title from 'title';
|
||||
@@ -47,8 +48,12 @@ import {
|
||||
import getProjectName from '../../util/get-project-name';
|
||||
import selectOrg from '../../util/input/select-org';
|
||||
import inputProject from '../../util/input/input-project';
|
||||
import validatePaths from '../../util/validate-paths';
|
||||
import { prependEmoji, emoji } from '../../util/emoji';
|
||||
import { inputRootDirectory } from '../../util/input/input-root-directory';
|
||||
import validatePaths, {
|
||||
validateRootDirectory,
|
||||
} from '../../util/validate-paths';
|
||||
import { readLocalConfig } from '../../util/config/files';
|
||||
|
||||
const addProcessEnv = async (log, env) => {
|
||||
let val;
|
||||
@@ -90,7 +95,7 @@ const printDeploymentStatus = async (
|
||||
},
|
||||
deployStamp,
|
||||
isClipboardEnabled,
|
||||
quiet
|
||||
isFile
|
||||
) => {
|
||||
const isProdDeployment = target === 'production';
|
||||
|
||||
@@ -113,7 +118,7 @@ const printDeploymentStatus = async (
|
||||
// print preview/production url
|
||||
let previewUrl;
|
||||
let isWildcard;
|
||||
if (Array.isArray(aliasList) && aliasList.length > 0) {
|
||||
if (!isFile && Array.isArray(aliasList) && aliasList.length > 0) {
|
||||
// search for a non now.sh/non wildcard domain
|
||||
// but fallback to the first alias in the list
|
||||
const mainAlias =
|
||||
@@ -130,22 +135,22 @@ const printDeploymentStatus = async (
|
||||
}
|
||||
|
||||
// copy to clipboard
|
||||
let isCopiedToClipboard = false;
|
||||
if (isClipboardEnabled && !isWildcard) {
|
||||
await copy(previewUrl).catch(error =>
|
||||
output.debug(`Error copying to clipboard: ${error}`)
|
||||
);
|
||||
}
|
||||
|
||||
// write to stdout
|
||||
if (quiet) {
|
||||
process.stdout.write(`https://${deploymentUrl}`);
|
||||
await copy(previewUrl)
|
||||
.then(() => {
|
||||
isCopiedToClipboard = true;
|
||||
})
|
||||
.catch(error => output.debug(`Error copying to clipboard: ${error}`));
|
||||
}
|
||||
|
||||
output.print(
|
||||
prependEmoji(
|
||||
`${isProdDeployment ? 'Production' : 'Preview'}: ${chalk.bold(
|
||||
previewUrl
|
||||
)} ${deployStamp()}`,
|
||||
)}${
|
||||
isCopiedToClipboard ? chalk.gray(` [copied to clipboard]`) : ''
|
||||
} ${deployStamp()}`,
|
||||
emoji('success')
|
||||
) + `\n`
|
||||
);
|
||||
@@ -201,7 +206,6 @@ export default async function main(
|
||||
output,
|
||||
stats,
|
||||
localConfig,
|
||||
isFile,
|
||||
args
|
||||
) {
|
||||
let argv = null;
|
||||
@@ -224,26 +228,161 @@ export default async function main(
|
||||
// $FlowFixMe
|
||||
const isTTY = process.stdout.isTTY;
|
||||
const quiet = !isTTY;
|
||||
const autoConfirm = argv['--confirm'];
|
||||
|
||||
// check paths
|
||||
const path = await validatePaths(output, paths);
|
||||
if (typeof path === 'number') {
|
||||
return path;
|
||||
const pathValidation = await validatePaths(output, paths);
|
||||
|
||||
if (!pathValidation.valid) {
|
||||
return pathValidation.exitCode;
|
||||
}
|
||||
|
||||
// build `meta`
|
||||
const meta = Object.assign(
|
||||
{},
|
||||
parseMeta(localConfig.meta),
|
||||
parseMeta(argv['--meta'])
|
||||
);
|
||||
const { isFile, path } = pathValidation;
|
||||
const autoConfirm = argv['--confirm'] || isFile;
|
||||
|
||||
// --no-scale
|
||||
if (argv['--no-scale']) {
|
||||
warn(`The option --no-scale is only supported on Now 1.0 deployments`);
|
||||
}
|
||||
|
||||
// deprecate --name
|
||||
if (argv['--name']) {
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`The ${param('--name')} flag is deprecated (https://zeit.ink/1B)`,
|
||||
emoji('warning')
|
||||
)}\n`
|
||||
);
|
||||
}
|
||||
|
||||
const client = new Client({
|
||||
apiUrl: ctx.apiUrl,
|
||||
token: ctx.authConfig.token,
|
||||
debug: debugEnabled,
|
||||
});
|
||||
|
||||
// retrieve `project` and `org` from .now
|
||||
const link = await getLinkedProject(output, client, path);
|
||||
|
||||
if (link.status === 'error') {
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
let { org, project, status } = link;
|
||||
let newProjectName = null;
|
||||
let rootDirectory = project ? project.rootDirectory : null;
|
||||
|
||||
if (status === 'not_linked') {
|
||||
const shouldStartSetup =
|
||||
autoConfirm ||
|
||||
(await confirm(
|
||||
`Set up and deploy ${chalk.cyan(`“${toHumanPath(path)}”`)}?`,
|
||||
true
|
||||
));
|
||||
|
||||
if (!shouldStartSetup) {
|
||||
output.print(`Aborted. Project not set up.\n`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
org = await selectOrg(
|
||||
output,
|
||||
'Which scope do you want to deploy to?',
|
||||
client,
|
||||
ctx.config.currentTeam,
|
||||
autoConfirm
|
||||
);
|
||||
|
||||
// We use `localConfig` here to read the name
|
||||
// even though the `now.json` file can change
|
||||
// afterwards, this is fine since the property
|
||||
// will be deprecated and can be replaced with
|
||||
// user input.
|
||||
const detectedProjectName = getProjectName({
|
||||
argv,
|
||||
nowConfig: localConfig || {},
|
||||
isFile,
|
||||
paths,
|
||||
});
|
||||
|
||||
const projectOrNewProjectName = await inputProject(
|
||||
output,
|
||||
client,
|
||||
org,
|
||||
detectedProjectName,
|
||||
autoConfirm
|
||||
);
|
||||
|
||||
if (typeof projectOrNewProjectName === 'string') {
|
||||
newProjectName = projectOrNewProjectName;
|
||||
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
|
||||
} else {
|
||||
project = projectOrNewProjectName;
|
||||
rootDirectory = project.rootDirectory;
|
||||
|
||||
// we can already link the project
|
||||
await linkFolderToProject(
|
||||
output,
|
||||
path,
|
||||
{
|
||||
projectId: project.id,
|
||||
orgId: org.id,
|
||||
},
|
||||
project.name,
|
||||
org.slug
|
||||
);
|
||||
status = 'linked';
|
||||
}
|
||||
}
|
||||
|
||||
const sourcePath = rootDirectory ? join(path, rootDirectory) : path;
|
||||
|
||||
if (
|
||||
rootDirectory &&
|
||||
(await validateRootDirectory(
|
||||
output,
|
||||
path,
|
||||
sourcePath,
|
||||
project
|
||||
? `To change your project settings, go to https://zeit.co/${org.slug}/${project.name}/settings`
|
||||
: ''
|
||||
)) === false
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If Root Directory is used we'll try to read the config
|
||||
// from there instead and use it if it exists.
|
||||
if (rootDirectory) {
|
||||
const rootDirectoryConfig = readLocalConfig(sourcePath);
|
||||
|
||||
if (rootDirectoryConfig) {
|
||||
debug(`Read local config from root directory (${rootDirectory})`);
|
||||
localConfig = rootDirectoryConfig;
|
||||
} else if (localConfig) {
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`The ${highlight(
|
||||
'now.json'
|
||||
)} file should be inside of the provided root directory.`,
|
||||
emoji('warning')
|
||||
)}\n`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
localConfig = localConfig || {};
|
||||
|
||||
if (localConfig.name) {
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`The ${code('name')} property in ${highlight(
|
||||
'now.json'
|
||||
)} is deprecated (https://zeit.ink/5F)`,
|
||||
emoji('warning')
|
||||
)}\n`
|
||||
);
|
||||
}
|
||||
|
||||
// build `env`
|
||||
const isObject = item =>
|
||||
Object.prototype.toString.call(item) === '[object Object]';
|
||||
@@ -283,6 +422,13 @@ export default async function main(
|
||||
}
|
||||
}
|
||||
|
||||
// build `meta`
|
||||
const meta = Object.assign(
|
||||
{},
|
||||
parseMeta(localConfig.meta),
|
||||
parseMeta(argv['--meta'])
|
||||
);
|
||||
|
||||
// Merge dotenv config, `env` from now.json, and `--env` / `-e` arguments
|
||||
const deploymentEnv = Object.assign(
|
||||
{},
|
||||
@@ -340,81 +486,6 @@ export default async function main(
|
||||
target = 'production';
|
||||
}
|
||||
|
||||
const client = new Client({
|
||||
apiUrl: ctx.apiUrl,
|
||||
token: ctx.authConfig.token,
|
||||
debug: debugEnabled,
|
||||
});
|
||||
|
||||
// retrieve `project` and `org` from .now
|
||||
let [org, project] = await getLinkedProject(client, path);
|
||||
let newProjectName = null;
|
||||
|
||||
if (!org || !project) {
|
||||
const { NOW_PROJECT_ID, NOW_ORG_ID } = process.env;
|
||||
if (NOW_PROJECT_ID && NOW_ORG_ID) {
|
||||
output.print(
|
||||
`${chalk.red('Error!')} Project not found (${JSON.stringify({
|
||||
NOW_PROJECT_ID,
|
||||
NOW_ORG_ID,
|
||||
})})\n`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const shouldStartSetup =
|
||||
autoConfirm ||
|
||||
(await confirm(
|
||||
`Set up and deploy ${chalk.cyan(`“${toHumanPath(path)}”`)}?`,
|
||||
true
|
||||
));
|
||||
|
||||
if (!shouldStartSetup) {
|
||||
output.print(`Aborted. Project not set up.\n`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
org = await selectOrg(
|
||||
'Which scope do you want to deploy to?',
|
||||
client,
|
||||
ctx.config.currentTeam,
|
||||
autoConfirm
|
||||
);
|
||||
|
||||
const detectedProjectName = getProjectName({
|
||||
argv,
|
||||
nowConfig: localConfig,
|
||||
isFile,
|
||||
paths,
|
||||
});
|
||||
|
||||
const projectOrNewProjectName = await inputProject(
|
||||
output,
|
||||
client,
|
||||
org,
|
||||
detectedProjectName,
|
||||
autoConfirm
|
||||
);
|
||||
|
||||
if (typeof projectOrNewProjectName === 'string') {
|
||||
newProjectName = projectOrNewProjectName;
|
||||
} else {
|
||||
project = projectOrNewProjectName;
|
||||
|
||||
// we can already link the project
|
||||
await linkFolderToProject(
|
||||
output,
|
||||
path,
|
||||
{
|
||||
projectId: project.id,
|
||||
orgId: org.id,
|
||||
},
|
||||
project.name,
|
||||
org.slug
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const currentTeam = org.type === 'team' ? org.id : undefined;
|
||||
const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam });
|
||||
let deployStamp = stamp();
|
||||
@@ -442,11 +513,11 @@ export default async function main(
|
||||
output,
|
||||
now,
|
||||
contextName,
|
||||
[path],
|
||||
[sourcePath],
|
||||
createArgs,
|
||||
org,
|
||||
autoConfirm,
|
||||
!!newProjectName
|
||||
!project && !isFile,
|
||||
path
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -455,6 +526,10 @@ export default async function main(
|
||||
) {
|
||||
let { projectSettings, framework } = deployment;
|
||||
|
||||
if (rootDirectory) {
|
||||
projectSettings.rootDirectory = rootDirectory;
|
||||
}
|
||||
|
||||
const settings = await editProjectSettings(
|
||||
output,
|
||||
projectSettings,
|
||||
@@ -470,11 +545,11 @@ export default async function main(
|
||||
output,
|
||||
now,
|
||||
contextName,
|
||||
[path],
|
||||
[sourcePath],
|
||||
createArgs,
|
||||
org,
|
||||
!!newProjectName,
|
||||
false
|
||||
false,
|
||||
path
|
||||
);
|
||||
}
|
||||
|
||||
@@ -483,6 +558,14 @@ export default async function main(
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (deployment instanceof Error) {
|
||||
output.error(
|
||||
`${deployment.message ||
|
||||
'An unexpected error occurred while deploying your project'} (http://zeit.ink/P4)`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const deploymentResponse = await getDeploymentByIdOrHost(
|
||||
now,
|
||||
contextName,
|
||||
@@ -598,7 +681,7 @@ export default async function main(
|
||||
deployment,
|
||||
deployStamp,
|
||||
!argv['--no-clipboard'],
|
||||
quiet
|
||||
isFile
|
||||
);
|
||||
}
|
||||
|
||||
@@ -646,6 +729,11 @@ function handleCreateDeployError(output, error) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (dataPath === '.name') {
|
||||
output.error(message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (keyword === 'type') {
|
||||
const prop = dataPath.substr(1, dataPath.length);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { resolve, basename, join } from 'path';
|
||||
import { resolve, basename } from 'path';
|
||||
import { eraseLines } from 'ansi-escapes';
|
||||
// @ts-ignore
|
||||
import { write as copy } from 'clipboardy';
|
||||
@@ -70,7 +70,6 @@ import {
|
||||
InvalidRegionOrDCForScale,
|
||||
} from '../../util/errors';
|
||||
import { SchemaValidationFailed } from '../../util/errors';
|
||||
import readPackage from '../../util/read-package';
|
||||
|
||||
interface Env {
|
||||
[name: string]: string | null | undefined;
|
||||
@@ -213,43 +212,6 @@ const promptForEnvFields = async (list: string[]) => {
|
||||
return answers;
|
||||
};
|
||||
|
||||
async function canUseZeroConfig(cwd: string): Promise<boolean> {
|
||||
try {
|
||||
const pkg = await readPackage(join(cwd, 'package.json'));
|
||||
|
||||
if (!pkg || pkg instanceof Error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
||||
|
||||
// We can only check for a few frontends,
|
||||
// since can never be sure if APIs will work
|
||||
// with zero-config
|
||||
if (
|
||||
deps.next ||
|
||||
deps.nuxt ||
|
||||
deps.gatsby ||
|
||||
deps.hexo ||
|
||||
deps.gridsome ||
|
||||
deps.umi ||
|
||||
deps.docusaurus ||
|
||||
deps['@docusaurus/core'] ||
|
||||
deps['preact-cli'] ||
|
||||
deps['ember-cli'] ||
|
||||
deps['@vue/cli-service'] ||
|
||||
deps['@angular/cli'] ||
|
||||
deps['polymer-cli'] ||
|
||||
deps['sirv-cli'] ||
|
||||
deps['react-scripts']
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default async function main(
|
||||
ctx: any,
|
||||
contextName: string,
|
||||
@@ -890,7 +852,7 @@ async function sync({
|
||||
const { url } = now;
|
||||
const dcs =
|
||||
deploymentType !== 'static' && deployment.scale
|
||||
? chalk` ({bold ${Object.keys(deployment.scale).join(', ')})`
|
||||
? ` (${ chalk.bold(Object.keys(deployment.scale).join(', ')) })`
|
||||
: '';
|
||||
|
||||
if (isTTY) {
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import pkg from '../../../package.json';
|
||||
import DevServer from '../../util/dev/server';
|
||||
import parseListen from '../../util/dev/parse-listen';
|
||||
import { Output } from '../../util/output';
|
||||
import { NowContext } from '../../types';
|
||||
import Client from '../../util/client';
|
||||
import { getLinkedProject } from '../../util/projects/link';
|
||||
import { getFrameworks } from '../../util/get-frameworks';
|
||||
import { isSettingValue } from '../../util/is-setting-value';
|
||||
import cmd from '../../util/output/cmd';
|
||||
|
||||
type Options = {
|
||||
'--debug'?: boolean;
|
||||
@@ -18,10 +23,62 @@ export default async function dev(
|
||||
output: Output
|
||||
) {
|
||||
const [dir = '.'] = args;
|
||||
const cwd = path.resolve(dir);
|
||||
let cwd = path.resolve(dir);
|
||||
const listen = parseListen(opts['--listen'] || '3000');
|
||||
const debug = opts['--debug'] || false;
|
||||
const devServer = new DevServer(cwd, { output, debug });
|
||||
|
||||
const client = new Client({
|
||||
apiUrl: ctx.apiUrl,
|
||||
token: ctx.authConfig.token,
|
||||
currentTeam: ctx.config.currentTeam,
|
||||
debug,
|
||||
});
|
||||
|
||||
// retrieve dev command
|
||||
const [link, frameworks] = await Promise.all([
|
||||
getLinkedProject(output, client, cwd),
|
||||
getFrameworks(),
|
||||
]);
|
||||
|
||||
if (link.status === 'error') {
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
if (link.status === 'not_linked' && !process.env.__NOW_SKIP_DEV_COMMAND) {
|
||||
output.print(
|
||||
`${chalk.red(
|
||||
'Error!'
|
||||
)} Your codebase isn’t linked to a project on ZEIT Now. Run ${cmd(
|
||||
'now'
|
||||
)} to link it.\n`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
let devCommand: undefined | string;
|
||||
if (link.status === 'linked') {
|
||||
const { project } = link;
|
||||
|
||||
if (project.devCommand) {
|
||||
devCommand = project.devCommand;
|
||||
} else if (project.framework) {
|
||||
const framework = frameworks.find(f => f.slug === project.framework);
|
||||
|
||||
if (framework) {
|
||||
const defaults = framework.settings.devCommand;
|
||||
|
||||
if (isSettingValue(defaults)) {
|
||||
devCommand = defaults.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (project.rootDirectory) {
|
||||
cwd = path.join(cwd, project.rootDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
const devServer = new DevServer(cwd, { output, debug, devCommand });
|
||||
|
||||
process.once('SIGINT', () => devServer.stop());
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import param from '../../util/output/param';
|
||||
import promptBool from '../../util/input/prompt-bool';
|
||||
import purchaseDomain from '../../util/domains/purchase-domain';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import wait from '../../util/output/wait';
|
||||
|
||||
type Options = {
|
||||
'--debug': boolean;
|
||||
@@ -27,7 +26,7 @@ export default async function buy(
|
||||
) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
config
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
@@ -100,7 +99,7 @@ export default async function buy(
|
||||
|
||||
let buyResult;
|
||||
const purchaseStamp = stamp();
|
||||
const stopPurchaseSpinner = wait('Purchasing');
|
||||
const stopPurchaseSpinner = output.spinner('Purchasing');
|
||||
|
||||
try {
|
||||
buyResult = await purchaseDomain(client, domainName, price);
|
||||
|
||||
@@ -9,7 +9,6 @@ import listInput from '../../util/input/list';
|
||||
import listItem from '../../util/output/list-item';
|
||||
import promptBool from '../../util/input/prompt-bool';
|
||||
import toHumanPath from '../../util/humanize-path';
|
||||
import wait from '../../util/output/wait';
|
||||
import { Output } from '../../util/output';
|
||||
import { NowContext } from '../../types';
|
||||
import success from '../../util/output/success';
|
||||
@@ -24,10 +23,10 @@ type Options = {
|
||||
};
|
||||
|
||||
type Example = {
|
||||
name: string,
|
||||
visible: boolean,
|
||||
suggestions: string[]
|
||||
}
|
||||
name: string;
|
||||
visible: boolean;
|
||||
suggestions: string[];
|
||||
};
|
||||
|
||||
const EXAMPLE_API = 'https://now-example-files.zeit.sh';
|
||||
|
||||
@@ -40,7 +39,7 @@ export default async function init(
|
||||
const [name, dir] = args;
|
||||
const force = opts['-f'] || opts['--force'];
|
||||
|
||||
const examples = await fetchExampleList();
|
||||
const examples = await fetchExampleList(output);
|
||||
|
||||
if (!examples) {
|
||||
throw new Error(`Could not fetch example list.`);
|
||||
@@ -56,22 +55,22 @@ export default async function init(
|
||||
return 0;
|
||||
}
|
||||
|
||||
return extractExample(chosen, dir, force);
|
||||
return extractExample(output, chosen, dir, force);
|
||||
}
|
||||
|
||||
if (exampleList.includes(name)) {
|
||||
return extractExample(name, dir, force);
|
||||
return extractExample(output, name, dir, force);
|
||||
}
|
||||
|
||||
const oldExample = examples.find(x => !x.visible && x.name === name);
|
||||
if (oldExample) {
|
||||
return extractExample(name, dir, force, 'v1');
|
||||
return extractExample(output, name, dir, force, 'v1');
|
||||
}
|
||||
|
||||
const found = await guess(exampleList, name);
|
||||
|
||||
if (typeof found === 'string') {
|
||||
return extractExample(found, dir, force);
|
||||
return extractExample(output, found, dir, force);
|
||||
}
|
||||
|
||||
console.log(info('No changes made.'));
|
||||
@@ -81,8 +80,8 @@ export default async function init(
|
||||
/**
|
||||
* Fetch example list json
|
||||
*/
|
||||
async function fetchExampleList() {
|
||||
const stopSpinner = wait('Fetching examples');
|
||||
async function fetchExampleList(output: Output) {
|
||||
const stopSpinner = output.spinner('Fetching examples');
|
||||
const url = `${EXAMPLE_API}/v2/list.json`;
|
||||
|
||||
try {
|
||||
@@ -93,7 +92,7 @@ async function fetchExampleList() {
|
||||
throw new Error(`Failed fetching list.json (${resp.statusText}).`);
|
||||
}
|
||||
|
||||
return await resp.json() as Example[];
|
||||
return (await resp.json()) as Example[];
|
||||
} catch (e) {
|
||||
stopSpinner();
|
||||
}
|
||||
@@ -106,22 +105,28 @@ async function chooseFromDropdown(message: string, exampleList: string[]) {
|
||||
const choices = exampleList.map(name => ({
|
||||
name,
|
||||
value: name,
|
||||
short: name
|
||||
short: name,
|
||||
}));
|
||||
|
||||
return listInput({
|
||||
message,
|
||||
separator: false,
|
||||
choices
|
||||
choices,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract example to directory
|
||||
*/
|
||||
async function extractExample(name: string, dir: string, force?: boolean, ver: string = 'v2') {
|
||||
async function extractExample(
|
||||
output: Output,
|
||||
name: string,
|
||||
dir: string,
|
||||
force?: boolean,
|
||||
ver: string = 'v2'
|
||||
) {
|
||||
const folder = prepareFolder(process.cwd(), dir || name, force);
|
||||
const stopSpinner = wait(`Fetching ${name}`);
|
||||
const stopSpinner = output.spinner(`Fetching ${name}`);
|
||||
|
||||
const url = `${EXAMPLE_API}/${ver}/download/${name}.tar.gz`;
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import createOutput from '../util/output';
|
||||
import Now from '../util';
|
||||
import logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed.ts';
|
||||
import wait from '../util/output/wait';
|
||||
import { handleError } from '../util/error';
|
||||
import strlen from '../util/strlen.ts';
|
||||
import Client from '../util/client.ts';
|
||||
@@ -79,13 +78,16 @@ export default async function main(ctx) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { authConfig: { token }, config } = ctx;
|
||||
const {
|
||||
authConfig: { token },
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const client = new Client({
|
||||
apiUrl,
|
||||
token,
|
||||
currentTeam,
|
||||
debug: debugEnabled
|
||||
debug: debugEnabled,
|
||||
});
|
||||
let contextName = null;
|
||||
|
||||
@@ -104,7 +106,7 @@ export default async function main(ctx) {
|
||||
|
||||
// resolve the deployment, since we might have been given an alias
|
||||
const depFetchStart = Date.now();
|
||||
const cancelWait = wait(
|
||||
const cancelWait = output.spinner(
|
||||
`Fetching deployment "${id}" in ${chalk.bold(contextName)}`
|
||||
);
|
||||
|
||||
@@ -140,7 +142,7 @@ export default async function main(ctx) {
|
||||
limits,
|
||||
version,
|
||||
routes,
|
||||
readyState
|
||||
readyState,
|
||||
} = deployment;
|
||||
|
||||
const isBuilds = version === 2;
|
||||
@@ -159,7 +161,7 @@ export default async function main(ctx) {
|
||||
)}/events?types=event`
|
||||
)
|
||||
),
|
||||
isBuilds ? now.fetch(buildsUrl) : { builds: [] }
|
||||
isBuilds ? now.fetch(buildsUrl) : { builds: [] },
|
||||
]);
|
||||
|
||||
cancelWait();
|
||||
@@ -174,7 +176,9 @@ export default async function main(ctx) {
|
||||
print(` ${chalk.cyan('version')}\t${version}\n`);
|
||||
print(` ${chalk.cyan('id')}\t\t${finalId}\n`);
|
||||
print(` ${chalk.cyan('name')}\t${name}\n`);
|
||||
print(` ${chalk.cyan('readyState')}\t${stateString(state || readyState)}\n`);
|
||||
print(
|
||||
` ${chalk.cyan('readyState')}\t${stateString(state || readyState)}\n`
|
||||
);
|
||||
if (!isBuilds) {
|
||||
print(` ${chalk.cyan('type')}\t${type}\n`);
|
||||
}
|
||||
@@ -255,7 +259,7 @@ export default async function main(ctx) {
|
||||
`${table(t, {
|
||||
align: ['l', 'c', 'c', 'c'],
|
||||
hsep: ' '.repeat(8),
|
||||
stringLength: strlen
|
||||
stringLength: strlen,
|
||||
}).replace(/^(.*)/gm, ' $1')}\n`
|
||||
);
|
||||
print('\n');
|
||||
@@ -269,9 +273,9 @@ export default async function main(ctx) {
|
||||
events.forEach(data => {
|
||||
if (!data.event) return; // keepalive
|
||||
print(
|
||||
` ${chalk.gray(
|
||||
new Date(data.created).toISOString()
|
||||
)} ${data.event} ${getEventMetadata(data)}\n`
|
||||
` ${chalk.gray(new Date(data.created).toISOString())} ${
|
||||
data.event
|
||||
} ${getEventMetadata(data)}\n`
|
||||
);
|
||||
});
|
||||
print('\n');
|
||||
|
||||
@@ -8,10 +8,7 @@ import chalk from 'chalk';
|
||||
import ua from '../util/ua.ts';
|
||||
import getArgs from '../util/get-args';
|
||||
import error from '../util/output/error';
|
||||
import aborted from '../util/output/aborted';
|
||||
import wait from '../util/output/wait';
|
||||
import highlight from '../util/output/highlight';
|
||||
import info from '../util/output/info';
|
||||
import ok from '../util/output/ok';
|
||||
import cmd from '../util/output/cmd.ts';
|
||||
import param from '../util/output/param.ts';
|
||||
@@ -24,7 +21,8 @@ import hp from '../util/humanize-path';
|
||||
import logo from '../util/output/logo';
|
||||
import exit from '../util/exit';
|
||||
import createOutput from '../util/output';
|
||||
import executeLogin from '../util/login/login.ts'
|
||||
import executeLogin from '../util/login/login.ts';
|
||||
import { prependEmoji, emoji } from '../util/emoji';
|
||||
|
||||
const debug = debugFactory('now:sh:login');
|
||||
|
||||
@@ -57,7 +55,7 @@ const help = () => {
|
||||
const verify = async ({ apiUrl, email, verificationToken }) => {
|
||||
const query = {
|
||||
email,
|
||||
token: verificationToken
|
||||
token: verificationToken,
|
||||
};
|
||||
|
||||
debug('GET /now/registration/verify');
|
||||
@@ -68,7 +66,7 @@ const verify = async ({ apiUrl, email, verificationToken }) => {
|
||||
res = await fetch(
|
||||
`${apiUrl}/now/registration/verify?${stringifyQuery(query)}`,
|
||||
{
|
||||
headers: { 'User-Agent': ua }
|
||||
headers: { 'User-Agent': ua },
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -105,12 +103,12 @@ const readEmail = async () => {
|
||||
let email;
|
||||
|
||||
try {
|
||||
email = await promptEmail({ start: info('Enter your email: ') });
|
||||
email = await promptEmail({ start: `Enter your email: ` });
|
||||
} catch (err) {
|
||||
console.log(); // \n
|
||||
|
||||
if (err.message === 'User abort') {
|
||||
throw new Error(aborted('No changes made.'));
|
||||
throw new Error(`${chalk.red('Aborted!')} No changes made`);
|
||||
}
|
||||
|
||||
if (err.message === 'stdin lacks setRawMode support') {
|
||||
@@ -192,7 +190,7 @@ const login = async ctx => {
|
||||
let verificationToken;
|
||||
let securityCode;
|
||||
|
||||
stopSpinner = wait('Sending you an email');
|
||||
stopSpinner = output.spinner('Sending you an email');
|
||||
|
||||
try {
|
||||
const data = await executeLogin(apiUrl, email);
|
||||
@@ -200,7 +198,7 @@ const login = async ctx => {
|
||||
securityCode = data.securityCode;
|
||||
} catch (err) {
|
||||
stopSpinner();
|
||||
console.log(error(err.message))
|
||||
console.log(error(err.message));
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -209,18 +207,15 @@ const login = async ctx => {
|
||||
// Clear up `Sending email` success message
|
||||
process.stdout.write(eraseLines(possibleAddress ? 1 : 2));
|
||||
|
||||
console.log(
|
||||
info(
|
||||
`We sent an email to ${highlight(
|
||||
email
|
||||
)}. Please follow the steps provided`,
|
||||
` inside it and make sure the security code matches ${highlight(
|
||||
securityCode
|
||||
)}.`
|
||||
)
|
||||
output.print(
|
||||
`We sent an email to ${highlight(
|
||||
email
|
||||
)}. Please follow the steps provided inside it and make sure the security code matches ${highlight(
|
||||
securityCode
|
||||
)}.\n`
|
||||
);
|
||||
|
||||
stopSpinner = wait('Waiting for your confirmation');
|
||||
stopSpinner = output.spinner('Waiting for your confirmation');
|
||||
|
||||
let token;
|
||||
|
||||
@@ -256,8 +251,15 @@ const login = async ctx => {
|
||||
output.debug(`Saved credentials in "${hp(getNowDir())}"`);
|
||||
|
||||
console.log(
|
||||
`${chalk.cyan('> Congratulations!')} ` +
|
||||
`You are now logged in. In order to deploy something, run ${cmd('now')}.`
|
||||
`${chalk.cyan('Congratulations!')} ` +
|
||||
`You are now logged in. In order to deploy something, run ${cmd('now')}.`
|
||||
);
|
||||
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`Connect your Git Repositories to deploy every branch push automatically (https://zeit.ink/1X).`,
|
||||
emoji('tip')
|
||||
)}\n`
|
||||
);
|
||||
|
||||
return ctx;
|
||||
|
||||
@@ -6,7 +6,6 @@ import logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed.ts';
|
||||
import { maybeURL, normalizeURL, parseInstanceURL } from '../util/url';
|
||||
import printEvents from '../util/events';
|
||||
import wait from '../util/output/wait';
|
||||
import Client from '../util/client.ts';
|
||||
import getScope from '../util/get-scope.ts';
|
||||
|
||||
@@ -165,7 +164,7 @@ export default async function main(ctx) {
|
||||
const id = deploymentIdOrURL;
|
||||
|
||||
const depFetchStart = Date.now();
|
||||
const cancelWait = wait(
|
||||
const cancelWait = output.spinner(
|
||||
`Fetching deployment "${id}" in ${chalk.bold(contextName)}`
|
||||
);
|
||||
|
||||
@@ -287,12 +286,12 @@ function printLogShort(log) {
|
||||
data = JSON.stringify(obj, null, 2);
|
||||
} else {
|
||||
data = (log.text || '')
|
||||
.replace(/\n$/, '')
|
||||
.replace(/^\n/, '')
|
||||
// eslint-disable-next-line no-control-regex
|
||||
.replace(/\x1b\[1000D/g, '')
|
||||
.replace(/\x1b\[0K/g, '')
|
||||
.replace(/\x1b\[1A/g, '');
|
||||
.replace(/\n$/, '')
|
||||
.replace(/^\n/, '')
|
||||
// eslint-disable-next-line no-control-regex
|
||||
.replace(/\x1b\[1000D/g, '')
|
||||
.replace(/\x1b\[0K/g, '')
|
||||
.replace(/\x1b\[1A/g, '');
|
||||
if (/warning/i.test(data)) {
|
||||
data = chalk.yellow(data);
|
||||
} else if (log.type === 'stderr') {
|
||||
|
||||
@@ -6,7 +6,6 @@ import table from 'text-table';
|
||||
import Now from '../util';
|
||||
import getAliases from '../util/alias/get-aliases';
|
||||
import createOutput from '../util/output';
|
||||
import wait from '../util/output/wait';
|
||||
import logo from '../util/output/logo';
|
||||
import cmd from '../util/output/cmd.ts';
|
||||
import elapsed from '../util/output/elapsed.ts';
|
||||
@@ -132,7 +131,7 @@ export default async function main(ctx) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const cancelWait = wait(
|
||||
const cancelWait = output.spinner(
|
||||
`Fetching deployment(s) ${ids
|
||||
.map(id => `"${id}"`)
|
||||
.join(' ')} in ${chalk.bold(contextName)}`
|
||||
|
||||
@@ -11,6 +11,7 @@ import logo from '../util/output/logo';
|
||||
import Client from '../util/client.ts';
|
||||
import getScope from '../util/get-scope.ts';
|
||||
import createOutput from '../util/output';
|
||||
import confirm from '../util/input/confirm';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -144,7 +145,7 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
const elapsed = ms(new Date() - start);
|
||||
|
||||
console.log(
|
||||
`> ${plural('secret', list.length, true)} found under ${chalk.bold(
|
||||
`${plural('secret', list.length, true)} found under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
@@ -189,22 +190,29 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
const theSecret = list.find(secret => secret.name === args[0]);
|
||||
|
||||
if (theSecret) {
|
||||
const yes = argv.yes || (await readConfirmation(theSecret));
|
||||
const yes =
|
||||
argv.yes || (await readConfirmation(output, theSecret, contextName));
|
||||
if (!yes) {
|
||||
console.error(error('User abort'));
|
||||
output.print(`Aborted. Secret not deleted.\n`);
|
||||
return exit(0);
|
||||
}
|
||||
} else {
|
||||
console.error(error(`No secret found by name "${args[0]}"`));
|
||||
console.error(
|
||||
error(
|
||||
`No secret found by name "${args[0]}" under ${chalk.bold(
|
||||
contextName
|
||||
)}`
|
||||
)
|
||||
);
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
const secret = await secrets.rm(args[0]);
|
||||
const elapsed = ms(new Date() - start);
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
|
||||
`${chalk.cyan('Success!')} Secret ${chalk.bold(
|
||||
secret.name
|
||||
)} removed ${chalk.gray(`[${elapsed}]`)}`
|
||||
)} under ${chalk.bold(contextName)} removed ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
return secrets.close();
|
||||
}
|
||||
@@ -223,9 +231,11 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
const secret = await secrets.rename(args[0], args[1]);
|
||||
const elapsed = ms(new Date() - start);
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
|
||||
`${chalk.cyan('Success!')} Secret ${chalk.bold(
|
||||
secret.oldName
|
||||
)} renamed to ${chalk.bold(args[1])} ${chalk.gray(`[${elapsed}]`)}`
|
||||
)} renamed to ${chalk.bold(args[1])} under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
return secrets.close();
|
||||
}
|
||||
@@ -243,7 +253,7 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
if (args.length > 2) {
|
||||
const example = chalk.cyan(`$ now secret add -- "${args[0]}"`);
|
||||
console.log(
|
||||
`> If your secret has spaces or starts with '-', make sure to terminate command options with double dash and wrap it in quotes. Example: \n ${example} `
|
||||
`If your secret has spaces or starts with '-', make sure to terminate command options with double dash and wrap it in quotes. Example: \n ${example} `
|
||||
);
|
||||
}
|
||||
|
||||
@@ -259,9 +269,9 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
|
||||
`${chalk.cyan('Success!')} Secret ${chalk.bold(
|
||||
name.toLowerCase()
|
||||
)} added (${chalk.bold(contextName)}) ${chalk.gray(`[${elapsed}]`)}`
|
||||
)} added under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
return secrets.close();
|
||||
}
|
||||
@@ -278,33 +288,19 @@ process.on('uncaughtException', err => {
|
||||
exit(1);
|
||||
});
|
||||
|
||||
function readConfirmation(secret) {
|
||||
return new Promise(resolve => {
|
||||
const time = chalk.gray(`${ms(new Date() - new Date(secret.created))} ago`);
|
||||
const tbl = table([[chalk.bold(secret.name), time]], {
|
||||
align: ['r', 'l'],
|
||||
hsep: ' '.repeat(6),
|
||||
});
|
||||
|
||||
process.stdout.write(
|
||||
'> The following secret will be removed permanently\n'
|
||||
);
|
||||
process.stdout.write(` ${tbl}\n`);
|
||||
|
||||
process.stdout.write(
|
||||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`
|
||||
);
|
||||
|
||||
process.stdin
|
||||
.on('data', d => {
|
||||
process.stdin.pause();
|
||||
resolve(
|
||||
d
|
||||
.toString()
|
||||
.trim()
|
||||
.toLowerCase() === 'y'
|
||||
);
|
||||
})
|
||||
.resume();
|
||||
async function readConfirmation(output, secret, contextName) {
|
||||
const time = chalk.gray(`${ms(new Date() - new Date(secret.created))} ago`);
|
||||
const tbl = table([[chalk.bold(secret.name), time]], {
|
||||
align: ['r', 'l'],
|
||||
hsep: ' '.repeat(6),
|
||||
});
|
||||
|
||||
output.print(
|
||||
`The following secret will be removed permanently from ${chalk.bold(
|
||||
contextName
|
||||
)}\n`
|
||||
);
|
||||
output.print(` ${tbl}\n`);
|
||||
|
||||
return confirm(`${chalk.bold.red('Are you sure?')}`, false);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export default async function({ apiUrl, token, teams, config }) {
|
||||
validateKeypress: validateSlugKeypress,
|
||||
initialValue: slug,
|
||||
valid: team,
|
||||
forceLowerCase: true
|
||||
forceLowerCase: true,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.message === 'USER_ABORT') {
|
||||
@@ -95,7 +95,7 @@ export default async function({ apiUrl, token, teams, config }) {
|
||||
try {
|
||||
name = await textInput({
|
||||
label: `- ${teamNamePrefix}`,
|
||||
validateKeypress: validateNameKeypress
|
||||
validateKeypress: validateNameKeypress,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.message === 'USER_ABORT') {
|
||||
@@ -153,7 +153,7 @@ export default async function({ apiUrl, token, teams, config }) {
|
||||
introMsg: 'Invite your teammates! When done, press enter on an empty field',
|
||||
noopMsg: `You can invite teammates later by running ${cmd(
|
||||
'now teams invite'
|
||||
)}`
|
||||
)}`,
|
||||
});
|
||||
|
||||
gracefulExit();
|
||||
|
||||
@@ -30,7 +30,7 @@ const domains = Array.from(
|
||||
'inbox.com',
|
||||
'mail.com',
|
||||
'gmx.com',
|
||||
'icloud.com'
|
||||
'icloud.com',
|
||||
])
|
||||
);
|
||||
|
||||
@@ -56,17 +56,15 @@ const emailAutoComplete = (value, teamSlug) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export default async function(
|
||||
{
|
||||
teams,
|
||||
args,
|
||||
config,
|
||||
introMsg,
|
||||
noopMsg = 'No changes made',
|
||||
apiUrl,
|
||||
token
|
||||
} = {}
|
||||
) {
|
||||
export default async function({
|
||||
teams,
|
||||
args,
|
||||
config,
|
||||
introMsg,
|
||||
noopMsg = 'No changes made',
|
||||
apiUrl,
|
||||
token,
|
||||
} = {}) {
|
||||
const { currentTeam: currentTeamId } = config;
|
||||
|
||||
const stopSpinner = wait('Fetching teams');
|
||||
@@ -86,7 +84,11 @@ export default async function(
|
||||
|
||||
if (!currentTeam) {
|
||||
// We specifically need a team scope here
|
||||
let err = `You can't run this command under ${param(user.username || user.email)}.\nPlease select a team scope using ${cmd('now switch')} or use ${cmd('--scope')}`;
|
||||
let err = `You can't run this command under ${param(
|
||||
user.username || user.email
|
||||
)}.\nPlease select a team scope using ${cmd('now switch')} or use ${cmd(
|
||||
'--scope'
|
||||
)}`;
|
||||
return fatalError(err);
|
||||
}
|
||||
|
||||
@@ -107,7 +109,9 @@ export default async function(
|
||||
userInfo = res.name || res.username;
|
||||
} catch (err) {
|
||||
if (err.code === 'user_not_found') {
|
||||
console.error(error(`No user exists with the email address "${email}".`));
|
||||
console.error(
|
||||
error(`No user exists with the email address "${email}".`)
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -115,7 +119,11 @@ export default async function(
|
||||
}
|
||||
|
||||
stopSpinner();
|
||||
console.log(`${chalk.cyan(chars.tick)} ${email}${userInfo ? ` (${userInfo})` : ''} ${elapsed()}`);
|
||||
console.log(
|
||||
`${chalk.cyan(chars.tick)} ${email}${
|
||||
userInfo ? ` (${userInfo})` : ''
|
||||
} ${elapsed()}`
|
||||
);
|
||||
} else {
|
||||
console.log(`${chalk.red(`✖ ${email}`)} ${chalk.gray('[invalid]')}`);
|
||||
}
|
||||
@@ -135,7 +143,7 @@ export default async function(
|
||||
email = await textInput({
|
||||
label: `- ${inviteUserPrefix}`,
|
||||
validateValue: validateEmail,
|
||||
autoComplete: value => emailAutoComplete(value, currentTeam.slug)
|
||||
autoComplete: value => emailAutoComplete(value, currentTeam.slug),
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.message !== 'USER_ABORT') {
|
||||
@@ -149,7 +157,10 @@ export default async function(
|
||||
stopSpinner = wait(inviteUserPrefix + email);
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { name, username } = await teams.inviteUser({ teamId: currentTeam.id, email });
|
||||
const { name, username } = await teams.inviteUser({
|
||||
teamId: currentTeam.id,
|
||||
email,
|
||||
});
|
||||
stopSpinner();
|
||||
const userInfo = name || username;
|
||||
email = `${email}${userInfo ? ` (${userInfo})` : ''} ${elapsed()}`;
|
||||
|
||||
@@ -27,20 +27,20 @@ export default async function({ teams, config, apiUrl, token }) {
|
||||
|
||||
if (accountIsCurrent) {
|
||||
currentTeam = {
|
||||
slug: user.username || user.email
|
||||
slug: user.username || user.email,
|
||||
};
|
||||
}
|
||||
|
||||
const teamList = list.map(({ slug, name }) => ({
|
||||
name,
|
||||
value: slug,
|
||||
current: slug === currentTeam.slug ? chars.tick : ''
|
||||
current: slug === currentTeam.slug ? chars.tick : '',
|
||||
}));
|
||||
|
||||
teamList.unshift({
|
||||
name: user.email,
|
||||
value: user.username || user.email,
|
||||
current: (accountIsCurrent && chars.tick) || ''
|
||||
current: (accountIsCurrent && chars.tick) || '',
|
||||
});
|
||||
|
||||
// Let's bring the current team to the beginning of the list
|
||||
|
||||
@@ -40,8 +40,8 @@ const main = async ctx => {
|
||||
boolean: ['help', 'debug', 'all'],
|
||||
alias: {
|
||||
help: 'h',
|
||||
debug: 'd'
|
||||
}
|
||||
debug: 'd',
|
||||
},
|
||||
});
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
@@ -52,7 +52,10 @@ const main = async ctx => {
|
||||
}
|
||||
|
||||
const debug = argv['--debug'];
|
||||
const { authConfig: { token }, apiUrl } = ctx;
|
||||
const {
|
||||
authConfig: { token },
|
||||
apiUrl,
|
||||
} = ctx;
|
||||
const output = createOutput({ debug });
|
||||
const client = new Client({ apiUrl, token, debug });
|
||||
let contextName = null;
|
||||
|
||||
@@ -48,6 +48,7 @@ import { NowError } from './util/now-error';
|
||||
import { SENTRY_DSN } from './util/constants.ts';
|
||||
import getUpdateCommand from './util/get-update-command';
|
||||
import { metrics, shouldCollectMetrics } from './util/metrics.ts';
|
||||
import { getLinkedOrg } from './util/projects/link';
|
||||
|
||||
const NOW_DIR = getNowDir();
|
||||
const NOW_CONFIG_PATH = configFiles.getConfigFilePath();
|
||||
@@ -504,9 +505,6 @@ const main = async argv_ => {
|
||||
}
|
||||
}
|
||||
|
||||
const scope = argv['--scope'] || argv['--team'] || localConfig.scope;
|
||||
const targetCommand = commands.get(subcommand);
|
||||
|
||||
if (argv['--team']) {
|
||||
output.warn(
|
||||
`The ${param('--team')} flag is deprecated. Please use ${param(
|
||||
@@ -515,18 +513,35 @@ const main = async argv_ => {
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
authConfig: { token },
|
||||
} = ctx;
|
||||
|
||||
let scope = argv['--scope'] || argv['--team'] || localConfig.scope;
|
||||
|
||||
if (process.env.NOW_ORG_ID || !scope) {
|
||||
const client = new Client({ apiUrl, token });
|
||||
const link = await getLinkedOrg(client, output);
|
||||
|
||||
if (link.status === 'error') {
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
if (link.status === 'linked') {
|
||||
scope = link.org.slug;
|
||||
}
|
||||
}
|
||||
|
||||
const targetCommand = commands.get(subcommand);
|
||||
|
||||
if (
|
||||
typeof scope === 'string' &&
|
||||
targetCommand !== 'login' &&
|
||||
targetCommand !== 'dev' &&
|
||||
!(targetCommand === 'teams' && argv._[3] !== 'invite')
|
||||
) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
} = ctx;
|
||||
const client = new Client({ apiUrl, token });
|
||||
|
||||
let user = null;
|
||||
const client = new Client({ apiUrl, token });
|
||||
|
||||
try {
|
||||
user = await getUser(client);
|
||||
|
||||
@@ -222,6 +222,9 @@ export interface Project {
|
||||
accountId: string;
|
||||
updatedAt: number;
|
||||
createdAt: number;
|
||||
devCommand?: string | null;
|
||||
framework?: string | null;
|
||||
rootDirectory?: string | null;
|
||||
}
|
||||
|
||||
export interface Org {
|
||||
@@ -233,6 +236,4 @@ export interface Org {
|
||||
export interface ProjectLink {
|
||||
projectId: string;
|
||||
orgId: string;
|
||||
orgSlug?: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Output } from '../output';
|
||||
import * as ERRORS from '../errors-ts';
|
||||
import Client from '../client';
|
||||
import createCertForAlias from '../certs/create-cert-for-alias';
|
||||
import wait from '../output/wait';
|
||||
|
||||
export type AliasRecord = {
|
||||
uid: string;
|
||||
@@ -20,7 +19,7 @@ export default async function createAlias(
|
||||
alias: string,
|
||||
externalDomain: boolean
|
||||
) {
|
||||
let cancelMessage = wait(`Creating alias`);
|
||||
let cancelMessage = output.spinner(`Creating alias`);
|
||||
const result = await performCreateAlias(
|
||||
client,
|
||||
contextName,
|
||||
@@ -41,7 +40,7 @@ export default async function createAlias(
|
||||
return cert;
|
||||
}
|
||||
|
||||
let cancelMessage = wait(`Creating alias`);
|
||||
let cancelMessage = output.spinner(`Creating alias`);
|
||||
const secondTry = await performCreateAlias(
|
||||
client,
|
||||
contextName,
|
||||
@@ -66,7 +65,7 @@ async function performCreateAlias(
|
||||
`/now/deployments/${deployment.uid}/aliases`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: { alias }
|
||||
body: { alias },
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -77,7 +76,10 @@ async function performCreateAlias(
|
||||
return { uid: error.uid, alias: error.alias } as AliasRecord;
|
||||
}
|
||||
if (error.code === 'deployment_not_found') {
|
||||
return new ERRORS.DeploymentNotFound({ context: contextName, id: deployment.uid });
|
||||
return new ERRORS.DeploymentNotFound({
|
||||
context: contextName,
|
||||
id: deployment.uid,
|
||||
});
|
||||
}
|
||||
if (error.code === 'gone') {
|
||||
return new ERRORS.DeploymentFailedAliasImpossible();
|
||||
@@ -94,7 +96,7 @@ async function performCreateAlias(
|
||||
}
|
||||
}
|
||||
if (error.status === 400) {
|
||||
return new ERRORS.DeploymentNotReady({url: deployment.url })
|
||||
return new ERRORS.DeploymentNotReady({ url: deployment.url });
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
||||
@@ -3,7 +3,6 @@ import chalk from 'chalk';
|
||||
import getAppLastDeployment from '../deploy/get-app-last-deployment';
|
||||
import getAppName from '../deploy/get-app-name';
|
||||
import fetchDeploymentByIdOrHost from '../deploy/get-deployment-by-id-or-host';
|
||||
import wait from '../output/wait';
|
||||
import Client from '../client';
|
||||
import { Output } from '../output';
|
||||
import { User, Config } from '../../types';
|
||||
@@ -17,7 +16,7 @@ export default async function getDeploymentForAlias(
|
||||
contextName: string,
|
||||
localConfig: Config
|
||||
) {
|
||||
const cancelWait = wait(
|
||||
const cancelWait = output.spinner(
|
||||
`Fetching deployment to alias in ${chalk.bold(contextName)}`
|
||||
);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as ERRORS from '../errors-ts';
|
||||
import Client from '../client';
|
||||
import createCertForAlias from '../certs/create-cert-for-alias';
|
||||
import setupDomain from '../domains/setup-domain';
|
||||
import wait from '../output/wait';
|
||||
|
||||
const NOW_SH_REGEX = /\.now\.sh$/;
|
||||
|
||||
@@ -34,6 +33,7 @@ export default async function upsertPathAlias(
|
||||
}
|
||||
|
||||
const result = await performUpsertPathAlias(
|
||||
output,
|
||||
client,
|
||||
alias,
|
||||
rules,
|
||||
@@ -51,23 +51,26 @@ export default async function upsertPathAlias(
|
||||
return cert;
|
||||
}
|
||||
|
||||
return performUpsertPathAlias(client, alias, rules, contextName);
|
||||
return performUpsertPathAlias(output, client, alias, rules, contextName);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function performUpsertPathAlias(
|
||||
output: Output,
|
||||
client: Client,
|
||||
alias: string,
|
||||
rules: PathRule[],
|
||||
contextName: string
|
||||
) {
|
||||
const cancelMessage = wait(`Updating path alias rules for ${alias}`);
|
||||
const cancelMessage = output.spinner(
|
||||
`Updating path alias rules for ${alias}`
|
||||
);
|
||||
try {
|
||||
const record = await client.fetch<AliasRecord>(`/now/aliases`, {
|
||||
body: { alias, rules },
|
||||
method: 'POST'
|
||||
method: 'POST',
|
||||
});
|
||||
cancelMessage();
|
||||
return record;
|
||||
|
||||
@@ -5,7 +5,6 @@ import createCertForCns from './create-cert-for-cns';
|
||||
import getWildcardCnsForAlias from './get-wildcard-cns-for-alias';
|
||||
import joinWords from '../output/join-words';
|
||||
import stamp from '../output/stamp';
|
||||
import wait from '../output/wait';
|
||||
|
||||
export default async function createCertificateForAlias(
|
||||
output: Output,
|
||||
@@ -15,7 +14,7 @@ export default async function createCertificateForAlias(
|
||||
shouldBeWildcard: boolean
|
||||
) {
|
||||
const cns = shouldBeWildcard ? getWildcardCnsForAlias(alias) : [alias];
|
||||
const cancelMessage = wait(`Generating a certificate...`);
|
||||
const cancelMessage = output.spinner(`Generating a certificate...`);
|
||||
const certStamp = stamp();
|
||||
const cert = await createCertForCns(client, cns, context);
|
||||
if (cert instanceof NowError) {
|
||||
@@ -25,9 +24,9 @@ export default async function createCertificateForAlias(
|
||||
|
||||
cancelMessage();
|
||||
output.log(
|
||||
`Certificate for ${joinWords(
|
||||
cert.cns
|
||||
)} (${cert.uid}) created ${certStamp()}`
|
||||
`Certificate for ${joinWords(cert.cns)} (${
|
||||
cert.uid
|
||||
}) created ${certStamp()}`
|
||||
);
|
||||
return cert;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@ export default async function createCertFromFile(
|
||||
client: Client,
|
||||
keyPath: string,
|
||||
certPath: string,
|
||||
caPath: string,
|
||||
context: string
|
||||
caPath: string
|
||||
) {
|
||||
const cancelWait = wait('Adding your custom certificate');
|
||||
|
||||
|
||||
@@ -11,17 +11,11 @@ export default async function createDeploy(
|
||||
paths,
|
||||
createArgs,
|
||||
org,
|
||||
shouldLinkFolder,
|
||||
isDetectingFramework
|
||||
isSettingUpProject,
|
||||
cwd
|
||||
) {
|
||||
try {
|
||||
return await now.create(
|
||||
paths,
|
||||
createArgs,
|
||||
org,
|
||||
shouldLinkFolder,
|
||||
isDetectingFramework
|
||||
);
|
||||
return await now.create(paths, createArgs, org, isSettingUpProject, cwd);
|
||||
} catch (error) {
|
||||
if (error.code === 'rate_limited') {
|
||||
throw new ERRORS_TS.DeploymentsRateLimited(error.message);
|
||||
@@ -104,8 +98,7 @@ export default async function createDeploy(
|
||||
paths,
|
||||
createArgs,
|
||||
org,
|
||||
shouldLinkFolder,
|
||||
isDetectingFramework
|
||||
isSettingUpProject
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Output } from '../output';
|
||||
import Client from '../client';
|
||||
import createCertForCns from '../certs/create-cert-for-cns';
|
||||
import setupDomain from '../domains/setup-domain';
|
||||
import wait from '../output/wait';
|
||||
import { InvalidDomain } from '../errors-ts';
|
||||
|
||||
export default async function generateCertForDeploy(
|
||||
@@ -23,7 +22,9 @@ export default async function generateCertForDeploy(
|
||||
return new InvalidDomain(deployURL);
|
||||
}
|
||||
|
||||
const cancelSetupWait = wait(`Setting custom suffix domain ${domain}`);
|
||||
const cancelSetupWait = output.spinner(
|
||||
`Setting custom suffix domain ${domain}`
|
||||
);
|
||||
const result = await setupDomain(output, client, domain, contextName);
|
||||
cancelSetupWait();
|
||||
if (result instanceof NowError) {
|
||||
@@ -31,7 +32,7 @@ export default async function generateCertForDeploy(
|
||||
}
|
||||
|
||||
// Generate the certificate with the given parameters
|
||||
const cancelCertWait = wait(
|
||||
const cancelCertWait = output.spinner(
|
||||
`Generating a wildcard certificate for ${domain}`
|
||||
);
|
||||
const cert = await createCertForCns(
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
DeploymentOptions,
|
||||
NowClientOptions,
|
||||
} from 'now-client';
|
||||
import wait from '../output/wait';
|
||||
import { Output } from '../output';
|
||||
// @ts-ignore
|
||||
import Now from '../../util';
|
||||
@@ -50,9 +49,9 @@ function printInspectUrl(
|
||||
export default async function processDeployment({
|
||||
isLegacy,
|
||||
org,
|
||||
cwd,
|
||||
projectName,
|
||||
shouldLinkFolder,
|
||||
isDetectingFramework,
|
||||
isSettingUpProject,
|
||||
skipAutoDetectionConfirmation,
|
||||
...args
|
||||
}: {
|
||||
@@ -69,9 +68,9 @@ export default async function processDeployment({
|
||||
force?: boolean;
|
||||
org: Org;
|
||||
projectName: string;
|
||||
shouldLinkFolder: boolean;
|
||||
isDetectingFramework: boolean;
|
||||
isSettingUpProject: boolean;
|
||||
skipAutoDetectionConfirmation?: boolean;
|
||||
cwd?: string;
|
||||
}) {
|
||||
if (isLegacy) return processLegacyDeployment(args);
|
||||
|
||||
@@ -84,6 +83,7 @@ export default async function processDeployment({
|
||||
deployStamp,
|
||||
force,
|
||||
nowConfig,
|
||||
quiet,
|
||||
} = args;
|
||||
|
||||
const { debug } = output;
|
||||
@@ -106,8 +106,8 @@ export default async function processDeployment({
|
||||
let buildSpinner = null;
|
||||
let deploySpinner = null;
|
||||
|
||||
let deployingSpinner = wait(
|
||||
isDetectingFramework
|
||||
let deployingSpinner = output.spinner(
|
||||
isSettingUpProject
|
||||
? `Setting up project`
|
||||
: `Deploying ${chalk.bold(`${org.slug}/${projectName}`)}`,
|
||||
0
|
||||
@@ -130,7 +130,7 @@ export default async function processDeployment({
|
||||
indications.push(event);
|
||||
}
|
||||
|
||||
if (event.type === 'file_count') {
|
||||
if (event.type === 'file-count') {
|
||||
debug(
|
||||
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
|
||||
);
|
||||
@@ -167,43 +167,42 @@ export default async function processDeployment({
|
||||
|
||||
now._host = event.payload.url;
|
||||
|
||||
if (shouldLinkFolder) {
|
||||
await linkFolderToProject(
|
||||
output,
|
||||
paths[0],
|
||||
{
|
||||
orgId: org.id,
|
||||
projectId: event.payload.projectId,
|
||||
},
|
||||
projectName,
|
||||
org.slug
|
||||
);
|
||||
}
|
||||
await linkFolderToProject(
|
||||
output,
|
||||
cwd || paths[0],
|
||||
{
|
||||
orgId: org.id,
|
||||
projectId: event.payload.projectId,
|
||||
},
|
||||
projectName,
|
||||
org.slug
|
||||
);
|
||||
|
||||
printInspectUrl(output, event.payload.url, deployStamp, org.slug);
|
||||
|
||||
if (quiet) {
|
||||
process.stdout.write(`https://${event.payload.url}`);
|
||||
}
|
||||
|
||||
if (queuedSpinner === null) {
|
||||
queuedSpinner =
|
||||
event.payload.readyState === 'QUEUED'
|
||||
? wait('Queued', 0)
|
||||
: wait('Building', 0);
|
||||
? output.spinner('Queued', 0)
|
||||
: output.spinner('Building', 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
event.type === 'build-state-changed' &&
|
||||
event.payload.readyState === 'BUILDING'
|
||||
) {
|
||||
if (event.type === 'building') {
|
||||
if (queuedSpinner) {
|
||||
queuedSpinner();
|
||||
}
|
||||
|
||||
if (buildSpinner === null) {
|
||||
buildSpinner = wait('Building', 0);
|
||||
buildSpinner = output.spinner('Building', 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === 'all-builds-completed') {
|
||||
if (event.type === 'ready') {
|
||||
if (queuedSpinner) {
|
||||
queuedSpinner();
|
||||
}
|
||||
@@ -211,7 +210,7 @@ export default async function processDeployment({
|
||||
buildSpinner();
|
||||
}
|
||||
|
||||
deploySpinner = wait('Completing', 0);
|
||||
deploySpinner = output.spinner('Completing', 0);
|
||||
}
|
||||
|
||||
// Handle error events
|
||||
|
||||
@@ -71,7 +71,7 @@ export default async function processLegacyDeployment({
|
||||
hashes = event.payload;
|
||||
}
|
||||
|
||||
if (event.type === 'file_count') {
|
||||
if (event.type === 'file-count') {
|
||||
debug(
|
||||
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
|
||||
);
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
import pkg from '../../../package.json';
|
||||
|
||||
import { NoBuilderCacheError } from '../errors-ts';
|
||||
import wait from '../output/wait';
|
||||
import { Output } from '../output';
|
||||
import { getDistTag } from '../get-dist-tag';
|
||||
|
||||
@@ -246,7 +245,7 @@ export async function installBuilders(
|
||||
return;
|
||||
}
|
||||
|
||||
const stopSpinner = wait(
|
||||
const stopSpinner = output.spinner(
|
||||
`Installing ${pluralize(
|
||||
'Runtime',
|
||||
packagesToInstall.length
|
||||
|
||||
@@ -7,9 +7,6 @@ import { delimiter, dirname, join } from 'path';
|
||||
import { fork, ChildProcess } from 'child_process';
|
||||
import { createFunction } from '@zeit/fun';
|
||||
import { Builder, File, Lambda, FileBlob, FileFsRef } from '@now/build-utils';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import chalk from 'chalk';
|
||||
import which from 'which';
|
||||
import plural from 'pluralize';
|
||||
import minimatch from 'minimatch';
|
||||
import _treeKill from 'tree-kill';
|
||||
@@ -46,27 +43,15 @@ interface BuildMessageResult extends BuildMessage {
|
||||
|
||||
const treeKill = promisify(_treeKill);
|
||||
|
||||
let nodeBinPromise: Promise<string>;
|
||||
|
||||
async function getNodeBin(): Promise<string> {
|
||||
return which.sync('node', { nothrow: true }) || process.execPath;
|
||||
}
|
||||
|
||||
async function createBuildProcess(
|
||||
match: BuildMatch,
|
||||
buildEnv: EnvConfig,
|
||||
workPath: string,
|
||||
output: Output,
|
||||
yarnPath?: string,
|
||||
debugEnabled: boolean = false
|
||||
yarnPath?: string
|
||||
): Promise<ChildProcess> {
|
||||
if (!nodeBinPromise) {
|
||||
nodeBinPromise = getNodeBin();
|
||||
}
|
||||
const [execPath, modulePath] = await Promise.all([
|
||||
nodeBinPromise,
|
||||
builderModulePathPromise,
|
||||
]);
|
||||
const { execPath } = process;
|
||||
const modulePath = await builderModulePathPromise;
|
||||
|
||||
// Ensure that `node` is in the builder's `PATH`
|
||||
let PATH = `${dirname(execPath)}${delimiter}${process.env.PATH}`;
|
||||
@@ -123,7 +108,7 @@ export async function executeBuild(
|
||||
const {
|
||||
builderWithPkg: { runInProcess, builder, package: pkg },
|
||||
} = match;
|
||||
const { src: entrypoint } = match;
|
||||
const { entrypoint } = match;
|
||||
const { env, debug, buildEnv, yarnPath, cwd: workPath } = devServer;
|
||||
|
||||
const startTime = Date.now();
|
||||
@@ -149,8 +134,7 @@ export async function executeBuild(
|
||||
buildEnv,
|
||||
workPath,
|
||||
devServer.output,
|
||||
yarnPath,
|
||||
debug
|
||||
yarnPath
|
||||
);
|
||||
}
|
||||
|
||||
@@ -278,20 +262,26 @@ export async function executeBuild(
|
||||
}
|
||||
|
||||
const { output } = result;
|
||||
const { cleanUrls } = nowConfig;
|
||||
|
||||
// Mimic fmeta-util and convert cleanUrls
|
||||
if (nowConfig.cleanUrls) {
|
||||
Object.entries(output)
|
||||
.filter(([name, value]) => name.endsWith('.html'))
|
||||
.forEach(([name, value]) => {
|
||||
const cleanName = name.slice(0, -5);
|
||||
delete output[name];
|
||||
output[cleanName] = value;
|
||||
if (value.type === 'FileBlob' || value.type === 'FileFsRef') {
|
||||
value.contentType = value.contentType || 'text/html; charset=utf-8';
|
||||
}
|
||||
});
|
||||
}
|
||||
// Mimic fmeta-util and perform file renaming
|
||||
Object.entries(output).forEach(([path, value]) => {
|
||||
if (cleanUrls && path.endsWith('.html')) {
|
||||
path = path.slice(0, -5);
|
||||
|
||||
if (value.type === 'FileBlob' || value.type === 'FileFsRef') {
|
||||
value.contentType = value.contentType || 'text/html; charset=utf-8';
|
||||
}
|
||||
}
|
||||
|
||||
const extensionless = devServer.getExtensionlessFile(path);
|
||||
if (extensionless) {
|
||||
path = extensionless;
|
||||
}
|
||||
|
||||
delete output[path];
|
||||
output[path] = value;
|
||||
});
|
||||
|
||||
// Convert the JSON-ified output map back into their corresponding `File`
|
||||
// subclass type instances.
|
||||
@@ -398,6 +388,7 @@ export async function getBuildMatches(
|
||||
cwd: string,
|
||||
yarnDir: string,
|
||||
output: Output,
|
||||
devServer: DevServer,
|
||||
fileList: string[]
|
||||
): Promise<BuildMatch[]> {
|
||||
const matches: BuildMatch[] = [];
|
||||
@@ -429,6 +420,14 @@ export async function getBuildMatches(
|
||||
// try to find a group otherwise
|
||||
src = src.replace(/(\[|\])/g, '[$1]');
|
||||
|
||||
// lambda function files are trimmed of their file extension
|
||||
const mapToEntrypoint = new Map<string, string>();
|
||||
const extensionless = devServer.getExtensionlessFile(src);
|
||||
if (extensionless) {
|
||||
mapToEntrypoint.set(extensionless, src);
|
||||
src = extensionless;
|
||||
}
|
||||
|
||||
const files = fileList
|
||||
.filter(name => name === src || minimatch(name, src))
|
||||
.map(name => join(cwd, name));
|
||||
@@ -443,6 +442,7 @@ export async function getBuildMatches(
|
||||
matches.push({
|
||||
...buildConfig,
|
||||
src,
|
||||
entrypoint: mapToEntrypoint.get(src) || src,
|
||||
builderWithPkg,
|
||||
buildOutput: {},
|
||||
buildResults: new Map(),
|
||||
|
||||
@@ -4,13 +4,8 @@ import PCRE from 'pcre-to-regexp';
|
||||
import isURL from './is-url';
|
||||
import DevServer from './server';
|
||||
|
||||
import {
|
||||
HttpHeadersConfig,
|
||||
RouteConfig,
|
||||
RouteResult,
|
||||
NowConfig,
|
||||
} from './types';
|
||||
import { isHandler } from '@now/routing-utils';
|
||||
import { HttpHeadersConfig, RouteConfig, RouteResult } from './types';
|
||||
import { isHandler, Route, HandleValue } from '@now/routing-utils';
|
||||
|
||||
export function resolveRouteParameters(
|
||||
str: string,
|
||||
@@ -31,27 +26,47 @@ export function resolveRouteParameters(
|
||||
});
|
||||
}
|
||||
|
||||
export default async function(
|
||||
export function getRoutesTypes(routes: Route[] = []) {
|
||||
const handleMap = new Map<HandleValue | null, Route[]>();
|
||||
let prevHandle: HandleValue | null = null;
|
||||
routes.forEach(route => {
|
||||
if (isHandler(route)) {
|
||||
prevHandle = route.handle;
|
||||
} else {
|
||||
const routes = handleMap.get(prevHandle);
|
||||
if (!routes) {
|
||||
handleMap.set(prevHandle, [route]);
|
||||
} else {
|
||||
routes.push(route);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return handleMap;
|
||||
}
|
||||
|
||||
export async function devRouter(
|
||||
reqUrl: string = '/',
|
||||
reqMethod?: string,
|
||||
routes?: RouteConfig[],
|
||||
devServer?: DevServer
|
||||
devServer?: DevServer,
|
||||
previousHeaders?: HttpHeadersConfig,
|
||||
missRoutes?: RouteConfig[],
|
||||
phase?: HandleValue | null
|
||||
): Promise<RouteResult> {
|
||||
let found: RouteResult | undefined;
|
||||
let { query, pathname: reqPathname = '/' } = url.parse(reqUrl, true);
|
||||
const combinedHeaders: HttpHeadersConfig = {};
|
||||
const combinedHeaders: HttpHeadersConfig = { ...previousHeaders };
|
||||
let status: number | undefined;
|
||||
|
||||
// Try route match
|
||||
if (routes) {
|
||||
let idx = -1;
|
||||
for (const routeConfig of routes) {
|
||||
idx++;
|
||||
|
||||
if (isHandler(routeConfig)) {
|
||||
if (routeConfig.handle === 'filesystem' && devServer) {
|
||||
if (await devServer.hasFilesystem(reqPathname)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// We don't expect any Handle, only Source routes
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -74,41 +89,74 @@ export default async function(
|
||||
}
|
||||
|
||||
if (headers) {
|
||||
// Create a clone of the `headers` object to not mutate the original one
|
||||
headers = { ...headers };
|
||||
for (const key of Object.keys(headers)) {
|
||||
headers[key] = resolveRouteParameters(headers[key], match, keys);
|
||||
for (const originalKey of Object.keys(headers)) {
|
||||
const lowerKey = originalKey.toLowerCase();
|
||||
if (
|
||||
previousHeaders &&
|
||||
Object.prototype.hasOwnProperty.call(previousHeaders, lowerKey) &&
|
||||
(phase === 'hit' || phase === 'miss')
|
||||
) {
|
||||
// don't override headers in the hit or miss phase
|
||||
} else {
|
||||
const originalValue = headers[originalKey];
|
||||
const value = resolveRouteParameters(originalValue, match, keys);
|
||||
combinedHeaders[lowerKey] = value;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(combinedHeaders, headers);
|
||||
}
|
||||
|
||||
if (routeConfig.continue) {
|
||||
if (routeConfig.status) {
|
||||
status = routeConfig.status;
|
||||
}
|
||||
reqPathname = destPath;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (routeConfig.check && devServer) {
|
||||
if (routeConfig.check && devServer && phase !== 'hit') {
|
||||
const { pathname = '/' } = url.parse(destPath);
|
||||
const hasDestFile = await devServer.hasFilesystem(pathname);
|
||||
|
||||
if (!hasDestFile) {
|
||||
// If the file is not found, `check: true` will
|
||||
// behave the same as `continue: true`
|
||||
reqPathname = destPath;
|
||||
continue;
|
||||
if (routeConfig.status && phase !== 'miss') {
|
||||
// Equivalent to now-proxy exit_with_status() function
|
||||
} else if (missRoutes && missRoutes.length > 0) {
|
||||
// Trigger a 'miss'
|
||||
const missResult = await devRouter(
|
||||
destPath,
|
||||
reqMethod,
|
||||
missRoutes,
|
||||
devServer,
|
||||
previousHeaders,
|
||||
[],
|
||||
'miss'
|
||||
);
|
||||
if (missResult.found) {
|
||||
return missResult;
|
||||
}
|
||||
} else {
|
||||
if (routeConfig.status && phase === 'miss') {
|
||||
status = routeConfig.status;
|
||||
}
|
||||
reqPathname = destPath;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isURL(destPath)) {
|
||||
const isDestUrl = isURL(destPath);
|
||||
if (isDestUrl) {
|
||||
found = {
|
||||
found: true,
|
||||
dest: destPath,
|
||||
userDest: false,
|
||||
status: routeConfig.status,
|
||||
isDestUrl,
|
||||
status: routeConfig.status || status,
|
||||
headers: combinedHeaders,
|
||||
uri_args: query,
|
||||
matched_route: routeConfig,
|
||||
matched_route_idx: idx,
|
||||
phase,
|
||||
};
|
||||
break;
|
||||
} else {
|
||||
@@ -120,11 +168,13 @@ export default async function(
|
||||
found: true,
|
||||
dest: pathname || '/',
|
||||
userDest: Boolean(routeConfig.dest),
|
||||
status: routeConfig.status,
|
||||
isDestUrl,
|
||||
status: routeConfig.status || status,
|
||||
headers: combinedHeaders,
|
||||
uri_args: query,
|
||||
matched_route: routeConfig,
|
||||
matched_route_idx: idx,
|
||||
phase,
|
||||
};
|
||||
break;
|
||||
}
|
||||
@@ -136,8 +186,11 @@ export default async function(
|
||||
found = {
|
||||
found: false,
|
||||
dest: reqPathname,
|
||||
status,
|
||||
isDestUrl: false,
|
||||
uri_args: query,
|
||||
headers: combinedHeaders,
|
||||
phase,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ms from 'ms';
|
||||
import url from 'url';
|
||||
import http from 'http';
|
||||
import fs from 'fs-extra';
|
||||
@@ -13,15 +12,21 @@ import serveHandler from 'serve-handler';
|
||||
import { watch, FSWatcher } from 'chokidar';
|
||||
import { parse as parseDotenv } from 'dotenv';
|
||||
import { basename, dirname, extname, join } from 'path';
|
||||
import { getTransformedRoutes } from '@now/routing-utils';
|
||||
import { getTransformedRoutes, HandleValue } from '@now/routing-utils';
|
||||
import directoryTemplate from 'serve-handler/src/directory';
|
||||
import getPort from 'get-port';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import isPortReachable from 'is-port-reachable';
|
||||
import which from 'which';
|
||||
|
||||
import {
|
||||
Builder,
|
||||
FileFsRef,
|
||||
PackageJson,
|
||||
detectBuilders,
|
||||
detectRoutes,
|
||||
detectApiDirectory,
|
||||
detectApiExtensions,
|
||||
spawnCommand,
|
||||
} from '@now/build-utils';
|
||||
|
||||
import { once } from '../once';
|
||||
@@ -48,17 +53,12 @@ import {
|
||||
validateNowConfigFunctions,
|
||||
} from './validate';
|
||||
|
||||
import isURL from './is-url';
|
||||
import devRouter from './router';
|
||||
import { devRouter, getRoutesTypes } from './router';
|
||||
import getMimeType from './mime-type';
|
||||
import { getYarnPath } from './yarn-installer';
|
||||
import { executeBuild, getBuildMatches, shutdownBuilder } from './builder';
|
||||
import { generateErrorMessage, generateHttpStatusDescription } from './errors';
|
||||
import {
|
||||
builderDirPromise,
|
||||
installBuilders,
|
||||
updateBuilders,
|
||||
} from './builder-cache';
|
||||
import { installBuilders, updateBuilders } from './builder-cache';
|
||||
|
||||
// HTML templates
|
||||
import errorTemplate from './templates/error';
|
||||
@@ -111,6 +111,8 @@ export default class DevServer {
|
||||
public address: string;
|
||||
|
||||
private cachedNowConfig: NowConfig | null;
|
||||
private apiDir: string | null;
|
||||
private apiExtensions: Set<string>;
|
||||
private server: http.Server;
|
||||
private stopping: boolean;
|
||||
private serverUrlPrinted: boolean;
|
||||
@@ -122,6 +124,9 @@ export default class DevServer {
|
||||
private watchAggregationTimeout: number;
|
||||
private filter: (path: string) => boolean;
|
||||
private podId: string;
|
||||
private devCommand?: string;
|
||||
private devProcess?: ChildProcess;
|
||||
private devProcessPort?: number;
|
||||
|
||||
private getNowConfigPromise: Promise<NowConfig> | null;
|
||||
private blockingBuildsPromise: Promise<void> | null;
|
||||
@@ -135,11 +140,14 @@ export default class DevServer {
|
||||
this.buildEnv = {};
|
||||
this.files = {};
|
||||
this.address = '';
|
||||
this.devCommand = options.devCommand;
|
||||
|
||||
// This gets updated when `start()` is invoked
|
||||
this.yarnPath = '/';
|
||||
|
||||
this.cachedNowConfig = null;
|
||||
this.apiDir = null;
|
||||
this.apiExtensions = new Set<string>();
|
||||
this.server = http.createServer(this.devServerHandler);
|
||||
this.server.timeout = 0; // Disable timeout
|
||||
this.serverUrlPrinted = false;
|
||||
@@ -286,6 +294,10 @@ export default class DevServer {
|
||||
const name = relative(this.cwd, fsPath);
|
||||
try {
|
||||
this.files[name] = await FileFsRef.fromFsPath({ fsPath });
|
||||
const extensionless = this.getExtensionlessFile(name);
|
||||
if (extensionless) {
|
||||
this.files[extensionless] = await FileFsRef.fromFsPath({ fsPath });
|
||||
}
|
||||
fileChanged(name, changed, removed);
|
||||
this.output.debug(`File created: ${name}`);
|
||||
} catch (err) {
|
||||
@@ -306,6 +318,11 @@ export default class DevServer {
|
||||
const name = relative(this.cwd, fsPath);
|
||||
this.output.debug(`File deleted: ${name}`);
|
||||
fileRemoved(name, this.files, changed, removed);
|
||||
const extensionless = this.getExtensionlessFile(name);
|
||||
if (extensionless) {
|
||||
this.output.debug(`File deleted: ${extensionless}`);
|
||||
fileRemoved(extensionless, this.files, changed, removed);
|
||||
}
|
||||
}
|
||||
|
||||
async handleFileModified(
|
||||
@@ -338,6 +355,7 @@ export default class DevServer {
|
||||
this.cwd,
|
||||
this.yarnPath,
|
||||
this.output,
|
||||
this,
|
||||
fileList
|
||||
);
|
||||
const sources = matches.map(m => m.src);
|
||||
@@ -526,12 +544,22 @@ export default class DevServer {
|
||||
|
||||
// no builds -> zero config
|
||||
if (!config.builds || config.builds.length === 0) {
|
||||
const { projectSettings } = config;
|
||||
const featHandleMiss = true; // enable for zero config
|
||||
const { projectSettings, cleanUrls, trailingSlash } = config;
|
||||
|
||||
const { builders, warnings, errors } = await detectBuilders(files, pkg, {
|
||||
let {
|
||||
builders,
|
||||
warnings,
|
||||
errors,
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
} = await detectBuilders(files, pkg, {
|
||||
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest',
|
||||
functions: config.functions,
|
||||
...(projectSettings ? { projectSettings } : {}),
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash,
|
||||
});
|
||||
|
||||
if (errors) {
|
||||
@@ -544,34 +572,31 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
if (builders) {
|
||||
const {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
error: routesError,
|
||||
} = await detectRoutes(files, builders);
|
||||
if (this.devCommand) {
|
||||
builders = builders.filter(filterFrontendBuilds);
|
||||
}
|
||||
|
||||
config.builds = config.builds || [];
|
||||
config.builds.push(...builders);
|
||||
|
||||
if (routesError) {
|
||||
this.output.error(routesError.message);
|
||||
await this.exit();
|
||||
} else {
|
||||
const routes: RouteConfig[] = [];
|
||||
const { routes: nowConfigRoutes } = config;
|
||||
routes.push(...(redirectRoutes || []));
|
||||
routes.push(...(nowConfigRoutes || []));
|
||||
routes.push(...(defaultRoutes || []));
|
||||
config.routes = routes;
|
||||
}
|
||||
const routes: RouteConfig[] = [];
|
||||
const { routes: nowConfigRoutes } = config;
|
||||
routes.push(...(redirectRoutes || []));
|
||||
routes.push(...(nowConfigRoutes || []));
|
||||
routes.push(...(defaultRoutes || []));
|
||||
config.routes = routes;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.builds || config.builds.length === 0) {
|
||||
if (isInitialLoad) {
|
||||
if (isInitialLoad && !this.devCommand) {
|
||||
this.output.note(`Serving all files as static`);
|
||||
}
|
||||
} else if (Array.isArray(config.builds)) {
|
||||
if (this.devCommand) {
|
||||
config.builds = config.builds.filter(filterFrontendBuilds);
|
||||
}
|
||||
|
||||
// `@now/static-build` needs to be the last builder
|
||||
// since it might catch all other requests
|
||||
config.builds.sort(sortBuilders);
|
||||
@@ -580,6 +605,8 @@ export default class DevServer {
|
||||
await this.validateNowConfig(config);
|
||||
|
||||
this.cachedNowConfig = config;
|
||||
this.apiDir = detectApiDirectory(config.builds || []);
|
||||
this.apiExtensions = detectApiExtensions(config.builds || []);
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -719,18 +746,21 @@ export default class DevServer {
|
||||
|
||||
const opts = { output: this.output, isBuilds: true };
|
||||
const files = await getFiles(this.cwd, nowConfig, opts);
|
||||
const results: { [filePath: string]: FileFsRef } = {};
|
||||
this.files = {};
|
||||
for (const fsPath of files) {
|
||||
const path = relative(this.cwd, fsPath);
|
||||
let path = relative(this.cwd, fsPath);
|
||||
const { mode } = await fs.stat(fsPath);
|
||||
results[path] = new FileFsRef({ mode, fsPath });
|
||||
this.files[path] = new FileFsRef({ mode, fsPath });
|
||||
const extensionless = this.getExtensionlessFile(path);
|
||||
if (extensionless) {
|
||||
this.files[extensionless] = new FileFsRef({ mode, fsPath });
|
||||
}
|
||||
}
|
||||
this.files = results;
|
||||
|
||||
const builders: Set<string> = new Set(
|
||||
const builders = new Set<string>(
|
||||
(nowConfig.builds || [])
|
||||
.filter((b: Builder) => b.use)
|
||||
.map((b: Builder) => b.use as string)
|
||||
.map((b: Builder) => b.use)
|
||||
);
|
||||
|
||||
await installBuilders(builders, this.yarnPath, this.output);
|
||||
@@ -795,6 +825,8 @@ export default class DevServer {
|
||||
// Wait for "ready" event of the watcher
|
||||
await once(this.watcher, 'ready');
|
||||
|
||||
const devCommandPromise = this.runDevCommand();
|
||||
|
||||
let address: string | null = null;
|
||||
while (typeof address !== 'string') {
|
||||
try {
|
||||
@@ -826,6 +858,8 @@ export default class DevServer {
|
||||
.replace('[::]', 'localhost')
|
||||
.replace('127.0.0.1', 'localhost');
|
||||
|
||||
await devCommandPromise;
|
||||
|
||||
this.output.ready(`Available at ${link(this.address)}`);
|
||||
this.serverUrlPrinted = true;
|
||||
}
|
||||
@@ -850,6 +884,18 @@ export default class DevServer {
|
||||
ops.push(shutdownBuilder(match, this.output));
|
||||
}
|
||||
|
||||
ops.push(
|
||||
new Promise(resolve => {
|
||||
if (!this.devProcess) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.devProcess.on('exit', () => resolve());
|
||||
process.kill(this.devProcess.pid, exitCode);
|
||||
})
|
||||
);
|
||||
|
||||
ops.push(close(this.server));
|
||||
|
||||
if (this.watcher) {
|
||||
@@ -1041,7 +1087,9 @@ export default class DevServer {
|
||||
// If the requested asset wasn't found in the match's
|
||||
// outputs then trigger a build
|
||||
const buildKey =
|
||||
requestPath === null ? match.src : `${match.src}-${requestPath}`;
|
||||
requestPath === null
|
||||
? match.entrypoint
|
||||
: `${match.entrypoint}-${requestPath}`;
|
||||
let buildPromise = this.inProgressBuilds.get(buildKey);
|
||||
if (buildPromise) {
|
||||
// A build for `buildKey` is already in progress, so don't trigger
|
||||
@@ -1083,6 +1131,19 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
|
||||
getExtensionlessFile = (path: string) => {
|
||||
const ext = extname(path);
|
||||
if (
|
||||
this.apiDir &&
|
||||
path.startsWith(this.apiDir + '/') &&
|
||||
this.apiExtensions.has(ext)
|
||||
) {
|
||||
// lambda function files are trimmed of their file extension
|
||||
return path.slice(0, -ext.length);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* DevServer HTTP handler
|
||||
*/
|
||||
@@ -1155,55 +1216,145 @@ export default class DevServer {
|
||||
await this.blockingBuildsPromise;
|
||||
}
|
||||
|
||||
const { dest, status, headers, uri_args } = await devRouter(
|
||||
req.url,
|
||||
req.method,
|
||||
routes,
|
||||
this
|
||||
);
|
||||
const handleMap = getRoutesTypes(routes);
|
||||
const missRoutes = handleMap.get('miss') || [];
|
||||
const hitRoutes = handleMap.get('hit') || [];
|
||||
handleMap.delete('miss');
|
||||
handleMap.delete('hit');
|
||||
const phases: (HandleValue | null)[] = [null, 'filesystem'];
|
||||
|
||||
let routeResult: RouteResult | null = null;
|
||||
let match: BuildMatch | null = null;
|
||||
let statusCode: number | undefined;
|
||||
|
||||
for (const phase of phases) {
|
||||
statusCode = undefined;
|
||||
const phaseRoutes = handleMap.get(phase) || [];
|
||||
routeResult = await devRouter(
|
||||
req.url,
|
||||
req.method,
|
||||
phaseRoutes,
|
||||
this,
|
||||
undefined,
|
||||
missRoutes,
|
||||
phase
|
||||
);
|
||||
|
||||
if (routeResult.isDestUrl) {
|
||||
// Mix the `routes` result dest query params into the req path
|
||||
const destParsed = url.parse(routeResult.dest, true);
|
||||
delete destParsed.search;
|
||||
Object.assign(destParsed.query, routeResult.uri_args);
|
||||
const destUrl = url.format(destParsed);
|
||||
|
||||
this.output.debug(`ProxyPass: ${destUrl}`);
|
||||
this.setResponseHeaders(res, nowRequestId);
|
||||
return proxyPass(req, res, destUrl, this.output);
|
||||
}
|
||||
|
||||
match = await findBuildMatch(
|
||||
this.buildMatches,
|
||||
this.files,
|
||||
routeResult.dest,
|
||||
this
|
||||
);
|
||||
|
||||
if (!match && missRoutes.length > 0) {
|
||||
// Since there was no build match, enter the miss phase
|
||||
routeResult = await devRouter(
|
||||
routeResult.dest || req.url,
|
||||
req.method,
|
||||
missRoutes,
|
||||
this,
|
||||
routeResult.headers,
|
||||
[],
|
||||
'miss'
|
||||
);
|
||||
|
||||
match = await findBuildMatch(
|
||||
this.buildMatches,
|
||||
this.files,
|
||||
routeResult.dest,
|
||||
this
|
||||
);
|
||||
} else if (match && hitRoutes.length > 0) {
|
||||
// Since there was a build match, enter the hit phase.
|
||||
// The hit phase must not set status code.
|
||||
const prevStatus = routeResult.status;
|
||||
routeResult = await devRouter(
|
||||
routeResult.dest || req.url,
|
||||
req.method,
|
||||
hitRoutes,
|
||||
this,
|
||||
routeResult.headers,
|
||||
[],
|
||||
'hit'
|
||||
);
|
||||
routeResult.status = prevStatus;
|
||||
}
|
||||
|
||||
statusCode = routeResult.status;
|
||||
|
||||
if (match && statusCode === 404 && routeResult.phase === 'miss') {
|
||||
statusCode = undefined;
|
||||
}
|
||||
|
||||
const location = routeResult.headers['location'] || routeResult.dest;
|
||||
|
||||
if (statusCode && location && (300 <= statusCode && statusCode <= 399)) {
|
||||
// Equivalent to now-proxy exit_with_status() function
|
||||
this.output.debug(
|
||||
`Route found with redirect status code ${statusCode}`
|
||||
);
|
||||
await this.sendRedirect(req, res, nowRequestId, location, statusCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!match && statusCode && routeResult.phase !== 'miss') {
|
||||
// Equivalent to now-proxy exit_with_status() function
|
||||
this.output.debug(`Route found with with status code ${statusCode}`);
|
||||
await this.sendError(req, res, nowRequestId, '', statusCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (match) {
|
||||
// end the phase
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!routeResult) {
|
||||
throw new Error('Expected Route Result but none was found.');
|
||||
}
|
||||
|
||||
const { dest, headers, uri_args } = routeResult;
|
||||
|
||||
// Set any headers defined in the matched `route` config
|
||||
Object.entries(headers).forEach(([name, value]) => {
|
||||
res.setHeader(name, value);
|
||||
});
|
||||
|
||||
if (isURL(dest)) {
|
||||
// Mix the `routes` result dest query params into the req path
|
||||
const destParsed = url.parse(dest, true);
|
||||
delete destParsed.search;
|
||||
Object.assign(destParsed.query, uri_args);
|
||||
const destUrl = url.format(destParsed);
|
||||
|
||||
this.output.debug(`ProxyPass: ${destUrl}`);
|
||||
this.setResponseHeaders(res, nowRequestId);
|
||||
return proxyPass(req, res, destUrl, this.output);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
res.statusCode = status;
|
||||
if (300 <= status && status <= 399) {
|
||||
await this.sendRedirect(
|
||||
req,
|
||||
res,
|
||||
nowRequestId,
|
||||
res.getHeader('location') as string,
|
||||
status
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (statusCode) {
|
||||
res.statusCode = statusCode;
|
||||
}
|
||||
|
||||
const requestPath = dest.replace(/^\//, '');
|
||||
const match = await findBuildMatch(
|
||||
this.buildMatches,
|
||||
this.files,
|
||||
requestPath,
|
||||
this
|
||||
);
|
||||
|
||||
if (!match) {
|
||||
// if the dev command is started, proxy to it
|
||||
if (this.devProcessPort) {
|
||||
this.output.debug('Proxy to dev command server');
|
||||
return proxyPass(
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${this.devProcessPort}`,
|
||||
this.output,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
status === 404 ||
|
||||
(statusCode === 404 && routeResult.phase === 'miss') ||
|
||||
!this.renderDirectoryListing(req, res, requestPath, nowRequestId)
|
||||
) {
|
||||
await this.send404(req, res, nowRequestId);
|
||||
@@ -1259,6 +1410,18 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
if (!foundAsset) {
|
||||
// if the dev command is started, proxy to it
|
||||
if (this.devProcessPort) {
|
||||
this.output.debug('Proxy to dev command server');
|
||||
return proxyPass(
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${this.devProcessPort}`,
|
||||
this.output,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
await this.send404(req, res, nowRequestId);
|
||||
return;
|
||||
}
|
||||
@@ -1349,7 +1512,7 @@ export default class DevServer {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!status) {
|
||||
if (!statusCode) {
|
||||
res.statusCode = result.statusCode;
|
||||
}
|
||||
this.setResponseHeaders(res, nowRequestId, result.headers);
|
||||
@@ -1453,20 +1616,75 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
async hasFilesystem(dest: string): Promise<boolean> {
|
||||
const requestPath = dest.replace(/^\//, '');
|
||||
if (
|
||||
await findBuildMatch(
|
||||
this.buildMatches,
|
||||
this.files,
|
||||
requestPath,
|
||||
this,
|
||||
true
|
||||
)
|
||||
) {
|
||||
if (await findBuildMatch(this.buildMatches, this.files, dest, this, true)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async runDevCommand() {
|
||||
const { devCommand, cwd } = this;
|
||||
|
||||
if (!devCommand) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.output.log(
|
||||
`Running Dev Command ${chalk.cyan.bold(`“${devCommand}”`)}`
|
||||
);
|
||||
|
||||
const port = await getPort();
|
||||
|
||||
const env: EnvConfig = {
|
||||
...process.env,
|
||||
...this.buildEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
PORT: `${port}`,
|
||||
};
|
||||
|
||||
// This is necesary so that the dev command in the Project
|
||||
// will work cross-platform (especially Windows).
|
||||
let command = devCommand
|
||||
.replace(/\$PORT/g, `${port}`)
|
||||
.replace(/%PORT%/g, `${port}`);
|
||||
|
||||
this.output.debug(
|
||||
`Starting dev command with parameters : ${JSON.stringify({
|
||||
cwd,
|
||||
command,
|
||||
port,
|
||||
})}`
|
||||
);
|
||||
|
||||
const isNpxAvailable = await which('npx')
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (isNpxAvailable) {
|
||||
command = `npx --no-install ${command}`;
|
||||
} else {
|
||||
const isYarnAvailable = await which('yarn')
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (isYarnAvailable) {
|
||||
command = `yarn run --silent ${command}`;
|
||||
}
|
||||
}
|
||||
|
||||
this.output.debug(`Spawning dev command: ${command}`);
|
||||
|
||||
const p = spawnCommand(command, { stdio: 'inherit', cwd, env });
|
||||
|
||||
p.on('exit', () => {
|
||||
this.devProcessPort = undefined;
|
||||
});
|
||||
|
||||
await checkForPort(port, 1000 * 60 * 5);
|
||||
|
||||
this.devProcessPort = port;
|
||||
this.devProcess = p;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1476,13 +1694,14 @@ function proxyPass(
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
dest: string,
|
||||
output: Output
|
||||
output: Output,
|
||||
ignorePath: boolean = true
|
||||
): void {
|
||||
const proxy = httpProxy.createProxyServer({
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
xfwd: true,
|
||||
ignorePath: true,
|
||||
ignorePath,
|
||||
target: dest,
|
||||
});
|
||||
|
||||
@@ -1552,6 +1771,7 @@ async function findBuildMatch(
|
||||
devServer: DevServer,
|
||||
isFilesystem?: boolean
|
||||
): Promise<BuildMatch | null> {
|
||||
requestPath = requestPath.replace(/^\//, '');
|
||||
for (const match of matches.values()) {
|
||||
if (await shouldServe(match, files, requestPath, devServer, isFilesystem)) {
|
||||
return match;
|
||||
@@ -1720,3 +1940,27 @@ function needsBlockingBuild(buildMatch: BuildMatch): boolean {
|
||||
const { builder } = buildMatch.builderWithPkg;
|
||||
return typeof builder.shouldServe !== 'function';
|
||||
}
|
||||
|
||||
async function sleep(n: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, n));
|
||||
}
|
||||
|
||||
async function checkForPort(
|
||||
port: number | undefined,
|
||||
timeout: number
|
||||
): Promise<void> {
|
||||
const start = Date.now();
|
||||
while (!(await isPortReachable(port))) {
|
||||
if (Date.now() - start > timeout) {
|
||||
throw new Error(`Detecting port ${port} timed out after ${timeout}ms`);
|
||||
}
|
||||
await sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
function filterFrontendBuilds(build: Builder) {
|
||||
return (
|
||||
!build.use.startsWith('@now/static-build') &&
|
||||
!build.use.startsWith('@now/next')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,22 @@ import { BuilderParams, BuildResult, ShouldServeParams } from './types';
|
||||
|
||||
export const version = 2;
|
||||
|
||||
export function build({ files, entrypoint }: BuilderParams): BuildResult {
|
||||
export function build({
|
||||
files,
|
||||
entrypoint,
|
||||
config,
|
||||
}: BuilderParams): BuildResult {
|
||||
let path = entrypoint;
|
||||
const outputDir = config.zeroConfig ? config.outputDirectory : '';
|
||||
const outputMatch = outputDir + '/';
|
||||
if (outputDir && path.startsWith(outputMatch)) {
|
||||
// static output files are moved to the root directory
|
||||
path = path.slice(outputMatch.length);
|
||||
}
|
||||
const output = {
|
||||
[entrypoint]: files[entrypoint],
|
||||
[path]: files[entrypoint],
|
||||
};
|
||||
const watch = [entrypoint];
|
||||
const watch = [path];
|
||||
|
||||
return { output, routes: [], watch };
|
||||
}
|
||||
@@ -16,14 +27,25 @@ export function shouldServe({
|
||||
entrypoint,
|
||||
files,
|
||||
requestPath,
|
||||
config = {},
|
||||
}: ShouldServeParams) {
|
||||
let outputPrefix = '';
|
||||
const outputDir = config.zeroConfig ? config.outputDirectory : '';
|
||||
const outputMatch = outputDir + '/';
|
||||
if (outputDir && entrypoint.startsWith(outputMatch)) {
|
||||
// static output files are moved to the root directory
|
||||
entrypoint = entrypoint.slice(outputMatch.length);
|
||||
outputPrefix = outputMatch;
|
||||
}
|
||||
const isMatch = (f: string) => entrypoint === f && outputPrefix + f in files;
|
||||
|
||||
if (isIndex(entrypoint)) {
|
||||
const indexPath = join(requestPath, basename(entrypoint));
|
||||
if (entrypoint === indexPath && indexPath in files) {
|
||||
if (isMatch(indexPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return entrypoint === requestPath && requestPath in files;
|
||||
return isMatch(requestPath);
|
||||
}
|
||||
|
||||
function isIndex(path: string): boolean {
|
||||
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
FileFsRef,
|
||||
Lambda,
|
||||
PackageJson,
|
||||
BuilderFunctions,
|
||||
Config,
|
||||
} from '@now/build-utils';
|
||||
import { NowConfig } from 'now-client';
|
||||
import { NowRedirect, NowRewrite, NowHeader, Route } from '@now/routing-utils';
|
||||
import { HandleValue, Route } from '@now/routing-utils';
|
||||
import { Output } from '../output';
|
||||
|
||||
export { NowConfig };
|
||||
@@ -18,6 +18,7 @@ export { NowConfig };
|
||||
export interface DevServerOptions {
|
||||
output: Output;
|
||||
debug: boolean;
|
||||
devCommand?: string;
|
||||
}
|
||||
|
||||
export interface EnvConfig {
|
||||
@@ -25,6 +26,7 @@ export interface EnvConfig {
|
||||
}
|
||||
|
||||
export interface BuildMatch extends BuildConfig {
|
||||
entrypoint: string;
|
||||
builderWithPkg: BuilderWithPackage;
|
||||
buildOutput: BuilderOutputs;
|
||||
buildResults: Map<string | null, BuildResult>;
|
||||
@@ -61,7 +63,7 @@ export interface CacheOutputs {
|
||||
export interface BuilderParamsBase {
|
||||
files: BuilderInputs;
|
||||
entrypoint: string;
|
||||
config: object;
|
||||
config: Config;
|
||||
meta?: {
|
||||
isDev?: boolean;
|
||||
requestPath?: string | null;
|
||||
@@ -124,7 +126,7 @@ export interface BuildResultV4 {
|
||||
export interface ShouldServeParams {
|
||||
files: BuilderInputs;
|
||||
entrypoint: string;
|
||||
config?: object;
|
||||
config?: Config;
|
||||
requestPath: string;
|
||||
workPath: string;
|
||||
}
|
||||
@@ -156,6 +158,10 @@ export interface RouteResult {
|
||||
matched_route_idx?: number;
|
||||
// "userDest": <boolean in case the destination was user defined>
|
||||
userDest?: boolean;
|
||||
// url as destination should end routing
|
||||
isDestUrl: boolean;
|
||||
// the phase that this route is defined in
|
||||
phase?: HandleValue | null;
|
||||
}
|
||||
|
||||
export interface InvokePayload {
|
||||
|
||||
@@ -9,7 +9,6 @@ import getDomainStatus from './get-domain-status';
|
||||
import promptBool from '../input/prompt-bool';
|
||||
import purchaseDomain from './purchase-domain';
|
||||
import stamp from '../output/stamp';
|
||||
import wait from '../output/wait';
|
||||
import * as ERRORS from '../errors-ts';
|
||||
|
||||
const isTTY = process.stdout.isTTY;
|
||||
@@ -20,7 +19,7 @@ export default async function purchaseDomainIfAvailable(
|
||||
domain: string,
|
||||
contextName: string
|
||||
) {
|
||||
const cancelWait = wait(`Checking status of ${chalk.bold(domain)}`);
|
||||
const cancelWait = output.spinner(`Checking status of ${chalk.bold(domain)}`);
|
||||
const buyDomainStamp = stamp();
|
||||
const { available } = await getDomainStatus(client, domain);
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
export function emoji(label: string): string | undefined {
|
||||
export type EmojiLabel =
|
||||
| 'notice'
|
||||
| 'tip'
|
||||
| 'warning'
|
||||
| 'link'
|
||||
| 'inspect'
|
||||
| 'success';
|
||||
|
||||
export function emoji(label: EmojiLabel): string | undefined {
|
||||
switch (label) {
|
||||
case 'notice':
|
||||
return '📝';
|
||||
|
||||
@@ -1213,7 +1213,7 @@ export class BuildError extends NowError<'BUILD_ERROR', {}> {
|
||||
meta,
|
||||
}: {
|
||||
message: string;
|
||||
meta: { entrypoint: string };
|
||||
meta: { entrypoint?: string };
|
||||
}) {
|
||||
super({
|
||||
code: 'BUILD_ERROR',
|
||||
|
||||
14
packages/now-cli/src/util/get-frameworks.ts
Normal file
14
packages/now-cli/src/util/get-frameworks.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import fetch from 'node-fetch';
|
||||
import { Framework } from '@now/frameworks';
|
||||
|
||||
export async function getFrameworks(): Promise<Framework[]> {
|
||||
const res = await fetch('https://api-frameworks.zeit.sh/api/frameworks');
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error('Could not retrieve frameworks');
|
||||
}
|
||||
|
||||
const json: Framework[] = await res.json();
|
||||
|
||||
return json;
|
||||
}
|
||||
@@ -1,15 +1,24 @@
|
||||
import Client from './client';
|
||||
import { APIError, InvalidToken } from './errors-ts';
|
||||
import { Team } from '../types';
|
||||
// @ts-ignore
|
||||
import NowTeams from './teams.js';
|
||||
|
||||
type Response = {
|
||||
teams: Team[];
|
||||
};
|
||||
let teams: Team[] | undefined;
|
||||
|
||||
export default async function getTeams(client: Client) {
|
||||
if (teams) return teams;
|
||||
|
||||
try {
|
||||
const { teams } = await client.fetch<Response>(`/teams`);
|
||||
return teams;
|
||||
// we're using NowTeams because `client.fetch` hangs on windows
|
||||
const teamClient = new NowTeams({
|
||||
apiUrl: client._apiUrl,
|
||||
token: client._token,
|
||||
debug: client._debug,
|
||||
});
|
||||
|
||||
const teams = (await teamClient.ls()).teams;
|
||||
return teams as Team[];
|
||||
} catch (error) {
|
||||
if (error instanceof APIError && error.status === 403) {
|
||||
throw new InvalidToken();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Stats } from 'fs';
|
||||
import {sep, dirname, join, resolve } from 'path';
|
||||
import { sep, dirname, join, resolve } from 'path';
|
||||
import { readJSON, lstat, readlink, readFile, realpath } from 'fs-extra';
|
||||
|
||||
import { version } from '../../package.json';
|
||||
import { isCanary } from './is-canary';
|
||||
|
||||
// `npm` tacks a bunch of extra properties on the `package.json` file,
|
||||
// so check for one of them to determine yarn vs. npm.
|
||||
@@ -20,7 +19,7 @@ async function isYarn(): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
const pkgPath = join(dirname(binPath), '..', 'package.json');
|
||||
const pkg = await readJSON(pkgPath);
|
||||
const pkg = await readJSON(pkgPath).catch(() => ({}));
|
||||
return !('_id' in pkg);
|
||||
}
|
||||
|
||||
@@ -28,7 +27,7 @@ async function getConfigPrefix() {
|
||||
const paths = [
|
||||
process.env.npm_config_userconfig || process.env.NPM_CONFIG_USERCONFIG,
|
||||
join(process.env.HOME || '/', '.npmrc'),
|
||||
process.env.npm_config_globalconfig || process.env.NPM_CONFIG_GLOBALCONFIG
|
||||
process.env.npm_config_globalconfig || process.env.NPM_CONFIG_GLOBALCONFIG,
|
||||
].filter(Boolean);
|
||||
|
||||
for (const configPath of paths) {
|
||||
@@ -64,21 +63,22 @@ async function isGlobal() {
|
||||
}
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
const defaultPath = isWindows ? process.env.APPDATA : '/usr/local/lib'
|
||||
const defaultPath = isWindows ? process.env.APPDATA : '/usr/local/lib';
|
||||
|
||||
const installPath = await realpath(resolve(__dirname));
|
||||
|
||||
if (installPath.includes(['', 'yarn', 'global', 'node_modules', ''].join(sep))) {
|
||||
if (
|
||||
installPath.includes(['', 'yarn', 'global', 'node_modules', ''].join(sep))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const prefixPath = (
|
||||
const prefixPath =
|
||||
process.env.PREFIX ||
|
||||
process.env.npm_config_prefix ||
|
||||
process.env.NPM_CONFIG_PREFIX ||
|
||||
await getConfigPrefix() ||
|
||||
defaultPath
|
||||
);
|
||||
(await getConfigPrefix()) ||
|
||||
defaultPath;
|
||||
|
||||
if (!prefixPath) {
|
||||
return true;
|
||||
@@ -92,7 +92,7 @@ async function isGlobal() {
|
||||
}
|
||||
|
||||
export default async function getUpdateCommand(): Promise<string> {
|
||||
const tag = version.includes('canary') ? 'canary' : 'latest';
|
||||
const tag = isCanary() ? 'canary' : 'latest';
|
||||
|
||||
if (await isGlobal()) {
|
||||
return (await isYarn())
|
||||
@@ -100,7 +100,5 @@ export default async function getUpdateCommand(): Promise<string> {
|
||||
: `npm i -g now@${tag}`;
|
||||
}
|
||||
|
||||
return (await isYarn())
|
||||
? `yarn add now@${tag}`
|
||||
: `npm i now@${tag}`;
|
||||
return (await isYarn()) ? `yarn add now@${tag}` : `npm i now@${tag}`;
|
||||
}
|
||||
|
||||
@@ -2,23 +2,27 @@ import Client from './client';
|
||||
import { User } from '../types';
|
||||
import { APIError, InvalidToken, MissingUser } from './errors-ts';
|
||||
|
||||
let user: User | undefined;
|
||||
|
||||
export default async function getUser(client: Client) {
|
||||
let user;
|
||||
if (user) return user;
|
||||
|
||||
try {
|
||||
({ user } = await client.fetch<{ user: User }>('/www/user', {
|
||||
useCurrentTeam: false
|
||||
}));
|
||||
const res = await client.fetch<{ user: User }>('/www/user', {
|
||||
useCurrentTeam: false,
|
||||
});
|
||||
|
||||
if (!res.user) {
|
||||
throw new MissingUser();
|
||||
}
|
||||
|
||||
user = res.user;
|
||||
return user;
|
||||
} catch (error) {
|
||||
if (error instanceof APIError && error.status === 403) {
|
||||
throw new InvalidToken();
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
throw new MissingUser();
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
@@ -70,11 +70,11 @@ export default class Now extends EventEmitter {
|
||||
skipAutoDetectionConfirmation,
|
||||
},
|
||||
org,
|
||||
shouldLinkFolder,
|
||||
isDetectingFramework
|
||||
isSettingUpProject,
|
||||
cwd
|
||||
) {
|
||||
const opts = { output: this._output, hasNowJson };
|
||||
const { log, warn, debug } = this._output;
|
||||
const { log, warn } = this._output;
|
||||
const isLegacy = type !== null;
|
||||
|
||||
let files = [];
|
||||
@@ -182,9 +182,9 @@ export default class Now extends EventEmitter {
|
||||
force: forceNew,
|
||||
org,
|
||||
projectName: name,
|
||||
shouldLinkFolder,
|
||||
isDetectingFramework,
|
||||
isSettingUpProject,
|
||||
skipAutoDetectionConfirmation,
|
||||
cwd,
|
||||
});
|
||||
|
||||
// We report about files whose sizes are too big
|
||||
@@ -319,6 +319,13 @@ export default class Now extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
if (error.errorCode && error.errorCode === 'BUILD_FAILED') {
|
||||
return new BuildError({
|
||||
message: error.errorMessage,
|
||||
meta: {},
|
||||
});
|
||||
}
|
||||
|
||||
return new Error(error.message);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import inquirer from 'inquirer';
|
||||
import confirm from './confirm';
|
||||
import chalk from 'chalk';
|
||||
import { Output } from '../output';
|
||||
import { Framework, SettingValue } from '@now/frameworks';
|
||||
import { Framework } from '@now/frameworks';
|
||||
import { isSettingValue } from '../is-setting-value';
|
||||
|
||||
export interface ProjectSettings {
|
||||
buildCommand: string | null;
|
||||
@@ -10,37 +11,45 @@ export interface ProjectSettings {
|
||||
devCommand: string | null;
|
||||
}
|
||||
|
||||
export interface ProjectSettingsWithFramework extends ProjectSettings {
|
||||
framework: string | null;
|
||||
}
|
||||
|
||||
const fields: { name: string; value: keyof ProjectSettings }[] = [
|
||||
{ name: 'Build Command', value: 'buildCommand' },
|
||||
{ name: 'Output Directory', value: 'outputDirectory' },
|
||||
{ name: 'Development Command', value: 'devCommand' },
|
||||
];
|
||||
|
||||
function isSettingValue(setting: any): setting is SettingValue {
|
||||
return setting && typeof setting.value === 'string';
|
||||
}
|
||||
|
||||
export default async function editProjectSettings(
|
||||
output: Output,
|
||||
projectSettings: ProjectSettings | null,
|
||||
framework: Framework | null
|
||||
) {
|
||||
// create new settings object filled with "null" values
|
||||
const settings: Partial<ProjectSettings> = {};
|
||||
// create new settings object, missing values will be filled with `null`
|
||||
const settings: Partial<ProjectSettingsWithFramework> = {
|
||||
...projectSettings,
|
||||
};
|
||||
|
||||
for (let field of fields) {
|
||||
settings[field.value] =
|
||||
(projectSettings && projectSettings[field.value]) || null;
|
||||
}
|
||||
|
||||
// skip editing project settings if no framework is detected
|
||||
if (!framework) {
|
||||
settings.framework = null;
|
||||
return settings;
|
||||
}
|
||||
|
||||
output.print(
|
||||
`Auto-detected project settings (${chalk.bold(framework.name)}):\n`
|
||||
!framework.slug
|
||||
? `No framework detected. Default project settings:\n`
|
||||
: `Auto-detected project settings (${chalk.bold(framework.name)}):\n`
|
||||
);
|
||||
|
||||
settings.framework = framework.slug;
|
||||
|
||||
for (let field of fields) {
|
||||
const defaults = framework.settings[field.value];
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import chalk from 'chalk';
|
||||
import { ProjectNotFound } from '../../util/errors-ts';
|
||||
import { Output } from '../output';
|
||||
import { Project, Org } from '../../types';
|
||||
import wait from '../output/wait';
|
||||
import slugify from '@sindresorhus/slugify';
|
||||
|
||||
export default async function inputProject(
|
||||
output: Output,
|
||||
@@ -19,16 +19,26 @@ export default async function inputProject(
|
||||
return detectedProjectName;
|
||||
}
|
||||
|
||||
const slugifiedName = slugify(detectedProjectName);
|
||||
|
||||
// attempt to auto-detect a project to link
|
||||
let detectedProject = null;
|
||||
const existingProjectSpinner = wait('Searching for existing projects…', 1000);
|
||||
const existingProjectSpinner = output.spinner(
|
||||
'Searching for existing projects…',
|
||||
1000
|
||||
);
|
||||
try {
|
||||
const project = await getProjectByIdOrName(
|
||||
client,
|
||||
detectedProjectName,
|
||||
org.id
|
||||
);
|
||||
detectedProject = project instanceof ProjectNotFound ? null : project;
|
||||
const [project, slugifiedProject] = await Promise.all([
|
||||
getProjectByIdOrName(client, detectedProjectName, org.id),
|
||||
slugifiedName !== detectedProjectName
|
||||
? getProjectByIdOrName(client, slugifiedName, org.id)
|
||||
: null,
|
||||
]);
|
||||
detectedProject = !(project instanceof ProjectNotFound)
|
||||
? project
|
||||
: !(slugifiedProject instanceof ProjectNotFound)
|
||||
? slugifiedProject
|
||||
: null;
|
||||
} catch (error) {}
|
||||
existingProjectSpinner();
|
||||
|
||||
@@ -42,7 +52,7 @@ export default async function inputProject(
|
||||
if (
|
||||
await confirm(
|
||||
`Found project ${chalk.cyan(
|
||||
`“${org.slug}/${detectedProjectName}”`
|
||||
`“${org.slug}/${detectedProject.name}”`
|
||||
)}. Link to it?`,
|
||||
true
|
||||
)
|
||||
@@ -74,7 +84,7 @@ export default async function inputProject(
|
||||
continue;
|
||||
}
|
||||
|
||||
const spinner = wait('Verifying project name…', 1000);
|
||||
const spinner = output.spinner('Verifying project name…', 1000);
|
||||
try {
|
||||
project = await getProjectByIdOrName(client, projectName, org.id);
|
||||
} finally {
|
||||
@@ -97,7 +107,7 @@ export default async function inputProject(
|
||||
type: 'input',
|
||||
name: 'newProjectName',
|
||||
message: `What’s your project’s name?`,
|
||||
default: !detectedProject ? detectedProjectName : undefined,
|
||||
default: !detectedProject ? slugifiedName : undefined,
|
||||
});
|
||||
newProjectName = answers.newProjectName as string;
|
||||
|
||||
@@ -106,7 +116,7 @@ export default async function inputProject(
|
||||
continue;
|
||||
}
|
||||
|
||||
const spinner = wait('Verifying project name…', 1000);
|
||||
const spinner = output.spinner('Verifying project name…', 1000);
|
||||
let existingProject: Project | ProjectNotFound;
|
||||
try {
|
||||
existingProject = await getProjectByIdOrName(
|
||||
|
||||
54
packages/now-cli/src/util/input/input-root-directory.ts
Normal file
54
packages/now-cli/src/util/input/input-root-directory.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import { Output } from '../output';
|
||||
import { validateRootDirectory } from '../validate-paths';
|
||||
|
||||
export async function inputRootDirectory(
|
||||
cwd: string,
|
||||
output: Output,
|
||||
autoConfirm: boolean
|
||||
) {
|
||||
if (autoConfirm) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const basename = path.basename(cwd);
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const { rootDirectory } = await inquirer.prompt({
|
||||
type: 'input',
|
||||
name: 'rootDirectory',
|
||||
message: `In which directory is your code located?`,
|
||||
transformer: (input: string) => {
|
||||
return `${chalk.dim(`${basename}/`)}${input}`;
|
||||
},
|
||||
});
|
||||
|
||||
if (!rootDirectory) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normal = path.normalize(rootDirectory);
|
||||
|
||||
if (normal === '.' || normal === './') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fullPath = path.join(cwd, normal);
|
||||
|
||||
if (
|
||||
(await validateRootDirectory(
|
||||
output,
|
||||
cwd,
|
||||
fullPath,
|
||||
'Please choose a different one.'
|
||||
)) === false
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return normal;
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,12 @@ import inquirer from 'inquirer';
|
||||
import getUser from '../get-user';
|
||||
import getTeams from '../get-teams';
|
||||
import { User, Team, Org } from '../../types';
|
||||
import wait from '../output/wait';
|
||||
import { Output } from '../output';
|
||||
|
||||
type Choice = { name: string; value: Org };
|
||||
|
||||
export default async function selectProject(
|
||||
export default async function selectOrg(
|
||||
output: Output,
|
||||
question: string,
|
||||
client: Client,
|
||||
currentTeam?: string,
|
||||
@@ -15,7 +16,7 @@ export default async function selectProject(
|
||||
): Promise<Org> {
|
||||
require('./patch-inquirer');
|
||||
|
||||
const spinner = wait('Loading scopes…', 1000);
|
||||
const spinner = output.spinner('Loading scopes…', 1000);
|
||||
let user: User;
|
||||
let teams: Team[];
|
||||
try {
|
||||
|
||||
5
packages/now-cli/src/util/is-canary.ts
Normal file
5
packages/now-cli/src/util/is-canary.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import pkg from '../../package.json';
|
||||
|
||||
export function isCanary() {
|
||||
return pkg.version.includes('canary');
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user