mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 12:57:46 +00:00
Compare commits
49 Commits
@vercel/bu
...
create-inp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d1afb8f63 | ||
|
|
4e8da2c60c | ||
|
|
8706d857a9 | ||
|
|
8b903da18c | ||
|
|
7c693edef5 | ||
|
|
fa467e3613 | ||
|
|
75d85e0f4a | ||
|
|
909d36fef3 | ||
|
|
fa425339d0 | ||
|
|
d2396b0cf9 | ||
|
|
7a8101e3bd | ||
|
|
7023ea0d64 | ||
|
|
b7cb5c6ae3 | ||
|
|
c87bd35e69 | ||
|
|
f8a3b43bed | ||
|
|
bdf338b790 | ||
|
|
6487d091df | ||
|
|
705453202e | ||
|
|
988f7b75a2 | ||
|
|
9481572c6d | ||
|
|
7a28972897 | ||
|
|
3a05469454 | ||
|
|
f1c67d05e3 | ||
|
|
6f46b1fc70 | ||
|
|
34e961e1c6 | ||
|
|
75346d45cc | ||
|
|
f9b4fae51b | ||
|
|
8619992def | ||
|
|
a67ad4b5a1 | ||
|
|
11218a1798 | ||
|
|
64b97bf4b5 | ||
|
|
5c3d2e7411 | ||
|
|
4bca0c6d0b | ||
|
|
fab5fca939 | ||
|
|
2ad44999dd | ||
|
|
eee57262f0 | ||
|
|
fd762d8800 | ||
|
|
8ea93839cc | ||
|
|
b9f3438c2d | ||
|
|
bf587f77b7 | ||
|
|
58ef91bfe8 | ||
|
|
4111fbaa89 | ||
|
|
c3251e3775 | ||
|
|
33202dec1f | ||
|
|
a53d1b0d38 | ||
|
|
5e3656ec1b | ||
|
|
908e7837d5 | ||
|
|
dfa2f07c45 | ||
|
|
33181274bb |
2
.changeset/curly-eyes-travel.md
Normal file
2
.changeset/curly-eyes-travel.md
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
---
|
||||
2
.changeset/eleven-pants-attack.md
Normal file
2
.changeset/eleven-pants-attack.md
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
---
|
||||
2
.changeset/neat-tigers-pay.md
Normal file
2
.changeset/neat-tigers-pay.md
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
---
|
||||
3
.changeset/next-14-1-4.md
Normal file
3
.changeset/next-14-1-4.md
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
---
|
||||
|
||||
5
.changeset/silent-books-decide.md
Normal file
5
.changeset/silent-books-decide.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'vercel': patch
|
||||
---
|
||||
|
||||
Replace `inquirer` with `@inquirer/prompts`
|
||||
5
.changeset/slimy-nails-rescue.md
Normal file
5
.changeset/slimy-nails-rescue.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@vercel/remix-builder": patch
|
||||
---
|
||||
|
||||
Add `mjs` and `mts` extensions to vite detection
|
||||
2
.changeset/sour-experts-dress.md
Normal file
2
.changeset/sour-experts-dress.md
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
---
|
||||
4
.changeset/spicy-icons-serve.md
Normal file
4
.changeset/spicy-icons-serve.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
---
|
||||
|
||||
Update CODEOWNERS
|
||||
2
.changeset/tasty-bikes-push.md
Normal file
2
.changeset/tasty-bikes-push.md
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
---
|
||||
@@ -10,6 +10,7 @@ packages/*/test/fixtures
|
||||
packages/cli/@types
|
||||
packages/cli/download
|
||||
packages/cli/dist
|
||||
packages/cli/test/fixtures
|
||||
packages/cli/test/dev/fixtures
|
||||
packages/cli/bin
|
||||
packages/cli/link
|
||||
|
||||
22
.github/CODEOWNERS
vendored
22
.github/CODEOWNERS
vendored
@@ -2,17 +2,17 @@
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
# Restricted Paths
|
||||
* @TooTallNate @EndangeredMassa @trek
|
||||
/.github/workflows @TooTallNate @EndangeredMassa @trek @ijjk
|
||||
/packages/fs-detectors @TooTallNate @EndangeredMassa @trek @agadzik @chloetedder
|
||||
/packages/next @TooTallNate @EndangeredMassa @Ethan-Arrowood @trek @ijjk @ztanner
|
||||
/packages/routing-utils @TooTallNate @EndangeredMassa @trek @ijjk
|
||||
/packages/static-build @TooTallNate @EndangeredMassa @trek
|
||||
/packages/edge @vercel/compute @TooTallNate @EndangeredMassa @trek
|
||||
/examples @leerob
|
||||
/examples/create-react-app @Timer
|
||||
/examples/nextjs @timneutkens @ijjk @ztanner @huozhi
|
||||
/packages/node @TooTallNate @EndangeredMassa @trek @Kikobeats
|
||||
* @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55
|
||||
/.github/workflows @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @ijjk
|
||||
/packages/fs-detectors @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @agadzik @chloetedder
|
||||
/packages/next @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @timneutkens @ijjk @ztanner @huozhi @Ethan-Arrowood @styfle
|
||||
/packages/routing-utils @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @ijjk
|
||||
/packages/static-build @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55
|
||||
/packages/edge @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @vercel/compute
|
||||
/examples @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @leerob
|
||||
/examples/create-react-app @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @Timer
|
||||
/examples/nextjs @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @timneutkens @ijjk @ztanner @huozhi @Ethan-Arrowood @styfle
|
||||
/packages/node @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @Kikobeats
|
||||
|
||||
# Unrestricted Paths
|
||||
.changeset/
|
||||
|
||||
99
.github/dependabot.yml
vendored
99
.github/dependabot.yml
vendored
@@ -1,7 +1,8 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- schedule:
|
||||
- directory: /packages/static-build/test/fixtures/angular-v17
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
open-pull-requests-limit: 1
|
||||
reviewers:
|
||||
@@ -11,7 +12,6 @@ updates:
|
||||
commit-message:
|
||||
prefix: '[framework-fixtures]'
|
||||
package-ecosystem: 'npm'
|
||||
directory: /packages/static-build/test/fixtures/angular-v17
|
||||
allow:
|
||||
- dependency-name: '@angular*'
|
||||
ignore:
|
||||
@@ -25,8 +25,9 @@ updates:
|
||||
update-types:
|
||||
- 'minor'
|
||||
|
||||
- schedule:
|
||||
interval: 'daily'
|
||||
- directory: /packages/static-build/test/fixtures/astro-v4
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
open-pull-requests-limit: 1
|
||||
reviewers:
|
||||
- 'trek'
|
||||
@@ -35,7 +36,6 @@ updates:
|
||||
commit-message:
|
||||
prefix: '[framework-fixtures]'
|
||||
package-ecosystem: 'npm'
|
||||
directory: /packages/static-build/test/fixtures/astro-v4
|
||||
allow:
|
||||
- dependency-name: 'astro*'
|
||||
ignore:
|
||||
@@ -49,7 +49,8 @@ updates:
|
||||
update-types:
|
||||
- 'minor'
|
||||
|
||||
- schedule:
|
||||
- directory: /packages/static-build/test/fixtures/hydrogen-v2023
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
open-pull-requests-limit: 1
|
||||
reviewers:
|
||||
@@ -59,7 +60,6 @@ updates:
|
||||
commit-message:
|
||||
prefix: '[framework-fixtures]'
|
||||
package-ecosystem: 'npm'
|
||||
directory: /packages/static-build/test/fixtures/hydrogen-v2023
|
||||
allow:
|
||||
- dependency-name: '@remix-run*'
|
||||
- dependency-name: '@shopify*'
|
||||
@@ -77,3 +77,88 @@ updates:
|
||||
- '@shopify*'
|
||||
update-types:
|
||||
- 'minor'
|
||||
|
||||
- directory: /packages/static-build/test/fixtures/ionic-angular-v7
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
open-pull-requests-limit: 1
|
||||
reviewers:
|
||||
- 'trek'
|
||||
- 'TooTallNate'
|
||||
- 'EndangeredMassa'
|
||||
commit-message:
|
||||
prefix: '[framework-fixtures]'
|
||||
package-ecosystem: 'npm'
|
||||
allow:
|
||||
- dependency-name: '@angular*'
|
||||
- dependency-name: '@ionic*'
|
||||
groups:
|
||||
core:
|
||||
patterns:
|
||||
- '@angular*'
|
||||
- '@ionic*'
|
||||
update-types:
|
||||
- 'minor'
|
||||
|
||||
- directory: /packages/static-build/test/fixtures/nuxt-v3
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
open-pull-requests-limit: 1
|
||||
reviewers:
|
||||
- 'trek'
|
||||
- 'TooTallNate'
|
||||
- 'EndangeredMassa'
|
||||
commit-message:
|
||||
prefix: '[framework-fixtures]'
|
||||
package-ecosystem: 'npm'
|
||||
allow:
|
||||
- dependency-name: 'nuxt'
|
||||
- dependency-name: 'vue'
|
||||
- dependency-name: 'vue-router'
|
||||
- dependency-name: '@nuxt-devtools'
|
||||
ignore:
|
||||
- dependency-name: 'nuxt'
|
||||
update-types:
|
||||
['version-update:semver-major', 'version-update:semver-patch']
|
||||
- dependency-name: 'vue'
|
||||
update-types:
|
||||
['version-update:semver-major', 'version-update:semver-patch']
|
||||
- dependency-name: 'vue-router'
|
||||
update-types:
|
||||
['version-update:semver-major', 'version-update:semver-patch']
|
||||
- dependency-name: '@nuxt-devtools'
|
||||
update-types:
|
||||
['version-update:semver-major', 'version-update:semver-patch']
|
||||
groups:
|
||||
core:
|
||||
patterns:
|
||||
- 'nuxt'
|
||||
- 'vue'
|
||||
- 'vue-router'
|
||||
- '@nuxt-devtools'
|
||||
update-types:
|
||||
- 'minor'
|
||||
|
||||
- directory: /packages/static-build/test/fixtures/stencil-v4
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
open-pull-requests-limit: 1
|
||||
reviewers:
|
||||
- 'trek'
|
||||
- 'TooTallNate'
|
||||
- 'EndangeredMassa'
|
||||
commit-message:
|
||||
prefix: '[framework-fixtures]'
|
||||
package-ecosystem: 'npm'
|
||||
allow:
|
||||
- dependency-name: '@stencil/core'
|
||||
ignore:
|
||||
- dependency-name: '@stencil/core'
|
||||
update-types:
|
||||
['version-update:semver-major', 'version-update:semver-patch']
|
||||
groups:
|
||||
core:
|
||||
patterns:
|
||||
- '@stencil/core'
|
||||
update-types:
|
||||
- 'minor'
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<a href="https://vercel.com/docs"><strong>Documentation</strong></a> ·
|
||||
<a href="https://vercel.com/changelog"><strong>Changelog</strong></a> ·
|
||||
<a href="https://vercel.com/templates"><strong>Templates</strong></a> ·
|
||||
<a href="https://vercel.com/cli"><strong>CLI</strong></a>
|
||||
<a href="https://vercel.com/docs/cli"><strong>CLI</strong></a>
|
||||
</p>
|
||||
<br/>
|
||||
|
||||
@@ -23,7 +23,7 @@ Vercel’s Frontend Cloud provides the developer experience and infrastructure t
|
||||
|
||||
## Deploy
|
||||
|
||||
Get started by [importing a project](https://vercel.com/new) or using the [Vercel CLI](https://vercel.com/cli). Then, `git push` to deploy.
|
||||
Get started by [importing a project](https://vercel.com/new) or using the [Vercel CLI](https://vercel.com/docs/cli). Then, `git push` to deploy.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
2
examples/README.md
vendored
2
examples/README.md
vendored
@@ -1,6 +1,6 @@
|
||||
# Vercel Examples
|
||||
|
||||
To get started using any of these examples as your own project, [install Vercel](https://vercel.com/cli) and use either of the following commands in your terminal:
|
||||
To get started using any of these examples as your own project, [install Vercel](https://vercel.com/docs/cli) and use either of the following commands in your terminal:
|
||||
|
||||
```sh
|
||||
vercel init # Pick an example in the CLI
|
||||
|
||||
5
examples/hugo/package.json
Normal file
5
examples/hugo/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"engines": {
|
||||
"node": "18.x"
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Hydrogen v2](https://shopify.dev/custom-
|
||||
|
||||
_Live Example: https://hydrogen-v2-template.vercel.app_
|
||||
|
||||
You can also deploy using the [Vercel CLI](https://vercel.com/cli):
|
||||
You can also deploy using the [Vercel CLI](https://vercel.com/docs/cli):
|
||||
|
||||
```sh
|
||||
npm i -g vercel
|
||||
|
||||
5
examples/jekyll/package.json
Normal file
5
examples/jekyll/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"engines": {
|
||||
"node": "18.x"
|
||||
}
|
||||
}
|
||||
5
examples/middleman/package.json
Normal file
5
examples/middleman/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"engines": {
|
||||
"node": "18.x"
|
||||
}
|
||||
}
|
||||
569
examples/nextjs/package-lock.json
generated
569
examples/nextjs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"next": "14.1.2"
|
||||
"next": "14.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
@@ -22,6 +22,6 @@
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.1.2"
|
||||
"eslint-config-next": "14.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ npx create-remix@latest --template vercel/vercel/examples/remix
|
||||
|
||||
_Live Example: https://remix-run-template.vercel.app_
|
||||
|
||||
You can also deploy using the [Vercel CLI](https://vercel.com/cli):
|
||||
You can also deploy using the [Vercel CLI](https://vercel.com/docs/cli):
|
||||
|
||||
```sh
|
||||
npm i -g vercel
|
||||
@@ -35,4 +35,4 @@ Afterwards, start the Remix development server like so:
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open up [http://localhost:3000](http://localhost:3000) and you should be ready to go!
|
||||
Open up [http://localhost:5173](http://localhost:5173) and you should be ready to go!
|
||||
|
||||
@@ -18,7 +18,7 @@ Install dependencies:
|
||||
npx @sanity/cli install
|
||||
```
|
||||
|
||||
Pull down environment variables from your Vercel project (requires the [Vercel CLI](https://vercel.com/cli)):
|
||||
Pull down environment variables from your Vercel project (requires the [Vercel CLI](https://vercel.com/docs/cli)):
|
||||
|
||||
```sh
|
||||
vercel env pull
|
||||
|
||||
5
examples/zola/package.json
Normal file
5
examples/zola/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"engines": {
|
||||
"node": "18.x"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,26 @@
|
||||
# @vercel-internals/types
|
||||
|
||||
## 1.0.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`11218a179`](https://github.com/vercel/vercel/commit/11218a179870a5420c5a6ff720cd4aec4f7e1c5e)]:
|
||||
- @vercel/build-utils@7.9.1
|
||||
|
||||
## 1.0.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`8ea93839c`](https://github.com/vercel/vercel/commit/8ea93839ccc70816f3ece9d7cfdb857aa7a4b015)]:
|
||||
- @vercel/build-utils@7.9.0
|
||||
|
||||
## 1.0.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`908e7837d`](https://github.com/vercel/vercel/commit/908e7837d55bc02e708f402c700e00208415e954), [`5e3656ec1`](https://github.com/vercel/vercel/commit/5e3656ec1b3f0561091636582715ba09ddd8cb2d)]:
|
||||
- @vercel/build-utils@7.8.0
|
||||
|
||||
## 1.0.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "@vercel-internals/types",
|
||||
"version": "1.0.24",
|
||||
"version": "1.0.27",
|
||||
"types": "index.d.ts",
|
||||
"main": "index.d.ts",
|
||||
"files": [
|
||||
@@ -10,7 +10,7 @@
|
||||
"dependencies": {
|
||||
"@types/node": "14.14.31",
|
||||
"@vercel-internals/constants": "1.0.4",
|
||||
"@vercel/build-utils": "7.7.1",
|
||||
"@vercel/build-utils": "7.9.1",
|
||||
"@vercel/routing-utils": "3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"source-map-support": "0.5.12",
|
||||
"ts-eager": "2.0.2",
|
||||
"ts-jest": "29.1.0",
|
||||
"turbo": "1.12.4",
|
||||
"turbo": "1.12.5",
|
||||
"typescript": "4.9.5"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# @vercel/build-utils
|
||||
|
||||
## 7.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Export `getSupportedNodeVersion` ([#11277](https://github.com/vercel/vercel/pull/11277))
|
||||
|
||||
## 7.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add `base` parameter to `scanParentDirs()` ([#11261](https://github.com/vercel/vercel/pull/11261))
|
||||
|
||||
## 7.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Remove `VERCEL_ENABLE_NPM_DEFAULT` env var check ([#11242](https://github.com/vercel/vercel/pull/11242))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Rename variants to flags and remove legacy flags ([#11121](https://github.com/vercel/vercel/pull/11121))
|
||||
|
||||
## 7.7.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "7.7.1",
|
||||
"version": "7.9.1",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
@@ -276,12 +276,13 @@ export async function getNodeVersion(
|
||||
|
||||
export async function scanParentDirs(
|
||||
destPath: string,
|
||||
readPackageJson = false
|
||||
readPackageJson = false,
|
||||
base = '/'
|
||||
): Promise<ScanParentDirsResult> {
|
||||
assert(path.isAbsolute(destPath));
|
||||
|
||||
const pkgJsonPath = await walkParentDirs({
|
||||
base: '/',
|
||||
base,
|
||||
start: destPath,
|
||||
filename: 'package.json',
|
||||
});
|
||||
@@ -291,7 +292,7 @@ export async function scanParentDirs(
|
||||
: undefined;
|
||||
const [yarnLockPath, npmLockPath, pnpmLockPath, bunLockPath] =
|
||||
await walkParentDirsMulti({
|
||||
base: '/',
|
||||
base,
|
||||
start: destPath,
|
||||
filenames: [
|
||||
'yarn.lock',
|
||||
@@ -339,11 +340,7 @@ export async function scanParentDirs(
|
||||
// TODO: read "bun-lockfile-format-v0"
|
||||
lockfileVersion = 0;
|
||||
} else {
|
||||
if (process.env.VERCEL_ENABLE_NPM_DEFAULT === '1') {
|
||||
cliType = 'npm';
|
||||
} else {
|
||||
cliType = 'yarn';
|
||||
}
|
||||
cliType = 'npm';
|
||||
}
|
||||
|
||||
const packageJsonPath = pkgJsonPath || undefined;
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
import {
|
||||
getLatestNodeVersion,
|
||||
getDiscontinuedNodeVersions,
|
||||
getSupportedNodeVersion,
|
||||
} from './fs/node-version';
|
||||
import streamToBuffer from './fs/stream-to-buffer';
|
||||
import debug from './debug';
|
||||
@@ -72,6 +73,7 @@ export {
|
||||
walkParentDirs,
|
||||
getNodeBinPath,
|
||||
getNodeBinPaths,
|
||||
getSupportedNodeVersion,
|
||||
runNpmInstall,
|
||||
runBundleInstall,
|
||||
runPipInstall,
|
||||
|
||||
@@ -440,15 +440,6 @@ export interface Cron {
|
||||
schedule: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Replaced by Variants. Remove once fully replaced.
|
||||
*/
|
||||
export interface Flag {
|
||||
key: string;
|
||||
defaultValue?: unknown;
|
||||
metadata: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** The framework which created the function */
|
||||
export interface FunctionFramework {
|
||||
slug: string;
|
||||
@@ -473,9 +464,7 @@ export interface BuildResultV2Typical {
|
||||
framework?: {
|
||||
version: string;
|
||||
};
|
||||
/** @deprecated Replaced by Variants. Remove once fully replaced. */
|
||||
flags?: Flag[];
|
||||
variants?: Record<string, VariantDefinition>;
|
||||
flags?: { definitions: FlagDefinitions };
|
||||
}
|
||||
|
||||
export type BuildResultV2 = BuildResultV2Typical | BuildResultBuildOutput;
|
||||
@@ -500,23 +489,25 @@ export type StartDevServer = (
|
||||
* TODO: The following types will eventually be exported by a more
|
||||
* relevant package.
|
||||
*/
|
||||
type VariantJSONArray = ReadonlyArray<VariantJSONValue>;
|
||||
type FlagJSONArray = ReadonlyArray<FlagJSONValue>;
|
||||
|
||||
type VariantJSONValue =
|
||||
type FlagJSONValue =
|
||||
| string
|
||||
| boolean
|
||||
| number
|
||||
| null
|
||||
| VariantJSONArray
|
||||
| { [key: string]: VariantJSONValue };
|
||||
| FlagJSONArray
|
||||
| { [key: string]: FlagJSONValue };
|
||||
|
||||
type VariantOption = {
|
||||
value: VariantJSONValue;
|
||||
type FlagOption = {
|
||||
value: FlagJSONValue;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export interface VariantDefinition {
|
||||
options?: VariantOption[];
|
||||
export interface FlagDefinition {
|
||||
options?: FlagOption[];
|
||||
origin?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export type FlagDefinitions = Record<string, FlagDefinition>;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"build": {
|
||||
"env": {
|
||||
"VERCEL_ENABLE_NPM_DEFAULT": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
26
packages/build-utils/test/unit.test.ts
vendored
26
packages/build-utils/test/unit.test.ts
vendored
@@ -576,7 +576,7 @@ it(
|
||||
ms('1m')
|
||||
);
|
||||
|
||||
it('should return cliType npm when no lockfile is present and VERCEL_ENABLE_NPM_DEFAULT is set', async () => {
|
||||
it('should return cliType "npm" when no lockfile is present', async () => {
|
||||
const originalRepoLockfilePath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
@@ -587,36 +587,12 @@ it('should return cliType npm when no lockfile is present and VERCEL_ENABLE_NPM_
|
||||
const originalRepoLockfileData = await fs.readFile(originalRepoLockfilePath);
|
||||
await fs.remove(originalRepoLockfilePath);
|
||||
try {
|
||||
process.env.VERCEL_ENABLE_NPM_DEFAULT = '1';
|
||||
const fixture = path.join(__dirname, 'fixtures', '40-no-lockfile');
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('npm');
|
||||
expect(result.lockfileVersion).toEqual(undefined);
|
||||
expect(result.lockfilePath).toEqual(undefined);
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
} finally {
|
||||
delete process.env.VERCEL_ENABLE_NPM_DEFAULT;
|
||||
await fs.writeFile(originalRepoLockfilePath, originalRepoLockfileData);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return cliType yarn when no lockfile is present and VERCEL_ENABLE_NPM_DEFAULT is not set', async () => {
|
||||
const originalRepoLockfilePath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'pnpm-lock.yaml'
|
||||
);
|
||||
const originalRepoLockfileData = await fs.readFile(originalRepoLockfilePath);
|
||||
await fs.remove(originalRepoLockfilePath);
|
||||
try {
|
||||
const fixture = path.join(__dirname, 'fixtures', '40-no-lockfile');
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('yarn');
|
||||
expect(result.lockfileVersion).toEqual(undefined);
|
||||
expect(result.lockfilePath).toEqual(undefined);
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
} finally {
|
||||
await fs.writeFile(originalRepoLockfilePath, originalRepoLockfileData);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,51 @@
|
||||
# vercel
|
||||
|
||||
## 33.6.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Don't send `projectSettings.nodeVersion` for unsupported versions ([#11277](https://github.com/vercel/vercel/pull/11277))
|
||||
|
||||
- Updated dependencies [[`4bca0c6d0`](https://github.com/vercel/vercel/commit/4bca0c6d0bc25052b95bd02b12a0b891c86c4b49), [`a67ad4b5a`](https://github.com/vercel/vercel/commit/a67ad4b5a130bf0e56e18111b3f9ddad69cec0e1), [`11218a179`](https://github.com/vercel/vercel/commit/11218a179870a5420c5a6ff720cd4aec4f7e1c5e), [`64b97bf4b`](https://github.com/vercel/vercel/commit/64b97bf4b5203ecf9a95f63ce26a5c3360208966)]:
|
||||
- @vercel/next@4.1.6
|
||||
- @vercel/remix-builder@2.1.4
|
||||
- @vercel/build-utils@7.9.1
|
||||
- @vercel/static-build@2.4.4
|
||||
- @vercel/node@3.0.24
|
||||
|
||||
## 33.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Set `projectSettings.nodeVersion` in `vc deploy` based on "engines.node" field ([#11261](https://github.com/vercel/vercel/pull/11261))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Stops warning about legacy Speed Insights for Next.js apps ([#11268](https://github.com/vercel/vercel/pull/11268))
|
||||
|
||||
- Fix framework version detection in monorepos ([#11212](https://github.com/vercel/vercel/pull/11212))
|
||||
|
||||
- Updated dependencies [[`8ea93839c`](https://github.com/vercel/vercel/commit/8ea93839ccc70816f3ece9d7cfdb857aa7a4b015), [`58ef91bfe`](https://github.com/vercel/vercel/commit/58ef91bfe8c2e7176e8783cc4eb91ee8580c70dc)]:
|
||||
- @vercel/build-utils@7.9.0
|
||||
- @vercel/remix-builder@2.1.3
|
||||
- @vercel/node@3.0.23
|
||||
- @vercel/static-build@2.4.3
|
||||
|
||||
## 33.5.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Rename variants to flags and remove legacy flags ([#11121](https://github.com/vercel/vercel/pull/11121))
|
||||
|
||||
- fix vc with root dir issues ([#11243](https://github.com/vercel/vercel/pull/11243))
|
||||
|
||||
- Updated dependencies [[`908e7837d`](https://github.com/vercel/vercel/commit/908e7837d55bc02e708f402c700e00208415e954), [`5e3656ec1`](https://github.com/vercel/vercel/commit/5e3656ec1b3f0561091636582715ba09ddd8cb2d), [`a53d1b0d3`](https://github.com/vercel/vercel/commit/a53d1b0d38efa9637f8b8f81a70898add87530e3)]:
|
||||
- @vercel/build-utils@7.8.0
|
||||
- @vercel/next@4.1.5
|
||||
- @vercel/remix-builder@2.1.2
|
||||
- @vercel/node@3.0.22
|
||||
- @vercel/static-build@2.4.2
|
||||
|
||||
## 33.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -7,6 +7,7 @@ module.exports = {
|
||||
{
|
||||
diagnostics: true,
|
||||
isolatedModules: true,
|
||||
tsconfig: 'test/tsconfig.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "33.5.4",
|
||||
"version": "33.6.1",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -31,22 +31,25 @@
|
||||
"node": ">= 16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "7.7.1",
|
||||
"@vercel/build-utils": "7.9.1",
|
||||
"@vercel/fun": "1.1.0",
|
||||
"@vercel/go": "3.0.5",
|
||||
"@vercel/hydrogen": "1.0.2",
|
||||
"@vercel/next": "4.1.4",
|
||||
"@vercel/node": "3.0.21",
|
||||
"@vercel/next": "4.1.6",
|
||||
"@vercel/node": "3.0.24",
|
||||
"@vercel/python": "4.1.1",
|
||||
"@vercel/redwood": "2.0.8",
|
||||
"@vercel/remix-builder": "2.1.1",
|
||||
"@vercel/remix-builder": "2.1.4",
|
||||
"@vercel/ruby": "2.0.5",
|
||||
"@vercel/static-build": "2.4.1",
|
||||
"@vercel/static-build": "2.4.4",
|
||||
"chokidar": "3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@alex_neo/jest-expect-message": "1.0.5",
|
||||
"@edge-runtime/node-utils": "2.3.0",
|
||||
"@inquirer/core": "7.1.0",
|
||||
"@inquirer/prompts": "4.3.0",
|
||||
"@inquirer/type": "1.2.1",
|
||||
"@next/env": "11.1.2",
|
||||
"@sentry/node": "5.5.0",
|
||||
"@sindresorhus/slugify": "0.11.0",
|
||||
@@ -88,8 +91,8 @@
|
||||
"@types/yauzl-promise": "2.1.0",
|
||||
"@vercel-internals/constants": "1.0.4",
|
||||
"@vercel-internals/get-package-json": "1.0.0",
|
||||
"@vercel-internals/types": "1.0.24",
|
||||
"@vercel/client": "13.1.4",
|
||||
"@vercel-internals/types": "1.0.27",
|
||||
"@vercel/client": "13.1.7",
|
||||
"@vercel/error-utils": "2.0.2",
|
||||
"@vercel/frameworks": "3.0.0",
|
||||
"@vercel/fs-detectors": "5.2.1",
|
||||
@@ -126,7 +129,6 @@
|
||||
"glob": "7.1.2",
|
||||
"http-proxy": "1.18.1",
|
||||
"ini": "3.0.0",
|
||||
"inquirer": "7.0.4",
|
||||
"is-docker": "2.2.1",
|
||||
"is-port-reachable": "3.1.0",
|
||||
"is-url": "1.2.2",
|
||||
|
||||
@@ -278,9 +278,7 @@ export default async function bisect(client: Client): Promise<number> {
|
||||
if (openEnabled) {
|
||||
await open(testUrl);
|
||||
}
|
||||
const answer = await client.prompt({
|
||||
type: 'expand',
|
||||
name: 'action',
|
||||
action = await client.input.expand({
|
||||
message: 'Select an action:',
|
||||
choices: [
|
||||
{ key: 'g', name: 'Good', value: 'good' },
|
||||
@@ -288,7 +286,6 @@ export default async function bisect(client: Client): Promise<number> {
|
||||
{ key: 's', name: 'Skip', value: 'skip' },
|
||||
],
|
||||
});
|
||||
action = answer.action;
|
||||
}
|
||||
|
||||
if (action === 'good') {
|
||||
@@ -342,11 +339,7 @@ function getCommit(deployment: Deployment) {
|
||||
async function prompt(client: Client, message: string): Promise<string> {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const { val } = await client.prompt({
|
||||
type: 'input',
|
||||
name: 'val',
|
||||
message,
|
||||
});
|
||||
const val = await client.input.text({ message });
|
||||
if (val) {
|
||||
return val;
|
||||
} else {
|
||||
|
||||
@@ -21,8 +21,7 @@ import {
|
||||
NowBuildError,
|
||||
Cron,
|
||||
validateNpmrc,
|
||||
Flag,
|
||||
VariantDefinition,
|
||||
type FlagDefinitions,
|
||||
} from '@vercel/build-utils';
|
||||
import {
|
||||
detectBuilders,
|
||||
@@ -96,9 +95,6 @@ interface BuildOutputConfig {
|
||||
version: string;
|
||||
};
|
||||
crons?: Cron[];
|
||||
/** @deprecated Replaced by Variants. Remove once fully replaced. */
|
||||
flags?: Flag[];
|
||||
variants?: Record<string, VariantDefinition>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,9 +257,6 @@ export default async function main(client: Client): Promise<number> {
|
||||
if (project.settings.analyticsId) {
|
||||
envToUnset.add('VERCEL_ANALYTICS_ID');
|
||||
process.env.VERCEL_ANALYTICS_ID = project.settings.analyticsId;
|
||||
output.warn(
|
||||
'Vercel Speed Insights auto-injection is deprecated in favor of @vercel/speed-insights package. Learn more: https://vercel.link/upgrate-to-speed-insights-package'
|
||||
);
|
||||
}
|
||||
|
||||
// Some build processes use these env vars to platform detect Vercel
|
||||
@@ -667,9 +660,8 @@ async function doBuild(
|
||||
const mergedWildcard = mergeWildcard(buildResults.values());
|
||||
const mergedOverrides: Record<string, PathOverride> =
|
||||
overrides.length > 0 ? Object.assign({}, ...overrides) : undefined;
|
||||
const mergedFlags = mergeFlags(buildResults.values());
|
||||
|
||||
const framework = await getFramework(cwd, buildResults);
|
||||
const framework = await getFramework(workPath, buildResults);
|
||||
|
||||
// Write out the final `config.json` file based on the
|
||||
// user configuration and Builder build results
|
||||
@@ -681,12 +673,10 @@ async function doBuild(
|
||||
overrides: mergedOverrides,
|
||||
framework,
|
||||
crons: mergedCrons,
|
||||
/** @deprecated Replaced by Variants. Remove once fully replaced. */
|
||||
flags: mergedFlags,
|
||||
};
|
||||
await fs.writeJSON(join(outputDir, 'config.json'), config, { spaces: 2 });
|
||||
|
||||
await writeVariantsJson(client, buildResults.values(), outputDir);
|
||||
await writeFlagsJSON(client, buildResults.values(), outputDir);
|
||||
|
||||
const relOutputDir = relative(cwd, outputDir);
|
||||
output.print(
|
||||
@@ -820,60 +810,51 @@ function mergeWildcard(
|
||||
return wildcard;
|
||||
}
|
||||
|
||||
function mergeFlags(
|
||||
buildResults: Iterable<BuildResult | BuildOutputConfig>
|
||||
): BuildResultV2Typical['flags'] {
|
||||
return Array.from(buildResults).flatMap(result => {
|
||||
if ('flags' in result) {
|
||||
return result.flags ?? [];
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the build output and writes all the variants into the `variants.json`
|
||||
* file. It'll skip variants that already exist.
|
||||
* Takes the build output and writes all the flags into the `flags.json`
|
||||
* file. It'll skip flags that already exist.
|
||||
*/
|
||||
async function writeVariantsJson(
|
||||
async function writeFlagsJSON(
|
||||
{ output }: Client,
|
||||
buildResults: Iterable<BuildResult | BuildOutputConfig>,
|
||||
outputDir: string
|
||||
): Promise<void> {
|
||||
const variantsFilePath = join(outputDir, 'variants.json');
|
||||
const flagsFilePath = join(outputDir, 'flags.json');
|
||||
|
||||
let hasVariants = true;
|
||||
let hasFlags = true;
|
||||
|
||||
const variants = (await fs.readJSON(variantsFilePath).catch(error => {
|
||||
const flags = (await fs.readJSON(flagsFilePath).catch(error => {
|
||||
if (error.code === 'ENOENT') {
|
||||
hasVariants = false;
|
||||
hasFlags = false;
|
||||
return { definitions: {} };
|
||||
}
|
||||
|
||||
throw error;
|
||||
})) as { definitions: Record<string, VariantDefinition> };
|
||||
})) as { definitions: FlagDefinitions };
|
||||
|
||||
for (const result of buildResults) {
|
||||
if (!('variants' in result) || !result.variants) continue;
|
||||
if (!('flags' in result) || !result.flags || !result.flags.definitions)
|
||||
continue;
|
||||
|
||||
for (const [key, defintion] of Object.entries(result.variants)) {
|
||||
if (result.variants[key]) {
|
||||
for (const [key, definition] of Object.entries(result.flags.definitions)) {
|
||||
if (result.flags.definitions[key]) {
|
||||
output.warn(
|
||||
`The variant "${key}" was found multiple times. Only its first occurrence will be considered.`
|
||||
`The flag "${key}" was found multiple times. Only its first occurrence will be considered.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
hasVariants = true;
|
||||
variants.definitions[key] = defintion;
|
||||
hasFlags = true;
|
||||
flags.definitions[key] = definition;
|
||||
}
|
||||
}
|
||||
|
||||
// Only create the file when there are variants to write,
|
||||
// Only create the file when there are flags to write,
|
||||
// or when the file already exists.
|
||||
if (hasVariants) {
|
||||
await fs.writeJSON(variantsFilePath, variants, { spaces: 2 });
|
||||
// Checking `definitions` alone won't be enough in case there
|
||||
// are other properties set.
|
||||
if (hasFlags) {
|
||||
await fs.writeJSON(flagsFilePath, flags, { spaces: 2 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ function readConfirmation(output: Output, msg: string, certs: Cert[]) {
|
||||
}).replace(/^(.*)/gm, ' $1')}\n`
|
||||
);
|
||||
output.print(
|
||||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`
|
||||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('(y/N) ')}`
|
||||
);
|
||||
process.stdin
|
||||
.on('data', d => {
|
||||
|
||||
@@ -1,73 +1,77 @@
|
||||
import ms from 'ms';
|
||||
import fs from 'fs-extra';
|
||||
import bytes from 'bytes';
|
||||
import chalk from 'chalk';
|
||||
import { join, resolve } from 'path';
|
||||
import {
|
||||
getPrettyError,
|
||||
getSupportedNodeVersion,
|
||||
scanParentDirs,
|
||||
} from '@vercel/build-utils';
|
||||
import {
|
||||
fileNameSymbol,
|
||||
VALID_ARCHIVE_FORMATS,
|
||||
VercelConfig,
|
||||
} from '@vercel/client';
|
||||
import code from '../../util/output/code';
|
||||
import highlight from '../../util/output/highlight';
|
||||
import { readLocalConfig } from '../../util/config/files';
|
||||
import getArgs from '../../util/get-args';
|
||||
import { handleError } from '../../util/error';
|
||||
import Client from '../../util/client';
|
||||
import { getPrettyError } from '@vercel/build-utils';
|
||||
import toHumanPath from '../../util/humanize-path';
|
||||
import { errorToString, isErrnoException, isError } from '@vercel/error-utils';
|
||||
import bytes from 'bytes';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import ms from 'ms';
|
||||
import { join, resolve } from 'path';
|
||||
import Now, { CreateOptions } from '../../util';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import Client from '../../util/client';
|
||||
import { readLocalConfig } from '../../util/config/files';
|
||||
import { createGitMeta } from '../../util/create-git-meta';
|
||||
import createDeploy from '../../util/deploy/create-deploy';
|
||||
import getDeployment from '../../util/get-deployment';
|
||||
import parseMeta from '../../util/parse-meta';
|
||||
import param from '../../util/output/param';
|
||||
import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
|
||||
import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
|
||||
import parseTarget from '../../util/deploy/parse-target';
|
||||
import { printDeploymentStatus } from '../../util/deploy/print-deployment-status';
|
||||
import { isValidArchive } from '../../util/deploy/validate-archive-format';
|
||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||
import { emoji, prependEmoji } from '../../util/emoji';
|
||||
import { handleError } from '../../util/error';
|
||||
import { SchemaValidationFailed } from '../../util/errors';
|
||||
import {
|
||||
AliasDomainConfigured,
|
||||
BuildError,
|
||||
BuildsRateLimited,
|
||||
ConflictingFilePath,
|
||||
ConflictingPathSegment,
|
||||
DeploymentNotFound,
|
||||
DeploymentsRateLimited,
|
||||
DomainNotFound,
|
||||
DomainNotVerified,
|
||||
DomainPermissionDenied,
|
||||
DomainVerificationFailed,
|
||||
InvalidDomain,
|
||||
isAPIError,
|
||||
MissingBuildScript,
|
||||
NotDomainOwner,
|
||||
TooManyRequests,
|
||||
UserAborted,
|
||||
DeploymentsRateLimited,
|
||||
AliasDomainConfigured,
|
||||
MissingBuildScript,
|
||||
ConflictingFilePath,
|
||||
ConflictingPathSegment,
|
||||
BuildError,
|
||||
NotDomainOwner,
|
||||
isAPIError,
|
||||
} from '../../util/errors-ts';
|
||||
import { SchemaValidationFailed } from '../../util/errors';
|
||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||
import getArgs from '../../util/get-args';
|
||||
import getDeployment from '../../util/get-deployment';
|
||||
import getProjectName from '../../util/get-project-name';
|
||||
import toHumanPath from '../../util/humanize-path';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import editProjectSettings from '../../util/input/edit-project-settings';
|
||||
import inputProject from '../../util/input/input-project';
|
||||
import { inputRootDirectory } from '../../util/input/input-root-directory';
|
||||
import selectOrg from '../../util/input/select-org';
|
||||
import { Output } from '../../util/output';
|
||||
import code from '../../util/output/code';
|
||||
import highlight from '../../util/output/highlight';
|
||||
import param from '../../util/output/param';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import { parseEnv } from '../../util/parse-env';
|
||||
import parseMeta from '../../util/parse-meta';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import {
|
||||
getLinkedProject,
|
||||
linkFolderToProject,
|
||||
} from '../../util/projects/link';
|
||||
import getProjectName from '../../util/get-project-name';
|
||||
import selectOrg from '../../util/input/select-org';
|
||||
import inputProject from '../../util/input/input-project';
|
||||
import { prependEmoji, emoji } from '../../util/emoji';
|
||||
import { inputRootDirectory } from '../../util/input/input-root-directory';
|
||||
import { pickOverrides } from '../../util/projects/project-settings';
|
||||
import validatePaths, {
|
||||
validateRootDirectory,
|
||||
} from '../../util/validate-paths';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { Output } from '../../util/output';
|
||||
import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
|
||||
import parseTarget from '../../util/deploy/parse-target';
|
||||
import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
|
||||
import { createGitMeta } from '../../util/create-git-meta';
|
||||
import { isValidArchive } from '../../util/deploy/validate-archive-format';
|
||||
import { parseEnv } from '../../util/parse-env';
|
||||
import { errorToString, isErrnoException, isError } from '@vercel/error-utils';
|
||||
import { pickOverrides } from '../../util/projects/project-settings';
|
||||
import { printDeploymentStatus } from '../../util/deploy/print-deployment-status';
|
||||
import { help } from '../help';
|
||||
import { deployCommand } from './command';
|
||||
|
||||
@@ -513,6 +517,23 @@ export default async (client: Client): Promise<number> => {
|
||||
);
|
||||
}
|
||||
|
||||
const { packageJson } = await scanParentDirs(
|
||||
join(cwd, project?.rootDirectory ?? ''),
|
||||
true,
|
||||
cwd
|
||||
);
|
||||
let nodeVersion: string | undefined;
|
||||
if (packageJson?.engines?.node) {
|
||||
try {
|
||||
const { range } = await getSupportedNodeVersion(packageJson.engines.node);
|
||||
nodeVersion = range;
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
output.warn(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// if this flag is not set, use `undefined` to allow the project setting to be used
|
||||
const autoAssignCustomDomains = argv['--skip-domain'] ? false : undefined;
|
||||
@@ -545,12 +566,18 @@ export default async (client: Client): Promise<number> => {
|
||||
|
||||
if (!localConfig.builds || localConfig.builds.length === 0) {
|
||||
// Only add projectSettings for zero config deployments
|
||||
createArgs.projectSettings =
|
||||
status === 'not_linked'
|
||||
? {
|
||||
sourceFilesOutsideRootDirectory,
|
||||
}
|
||||
: { ...localConfigurationOverrides, sourceFilesOutsideRootDirectory };
|
||||
createArgs.projectSettings = {
|
||||
sourceFilesOutsideRootDirectory,
|
||||
rootDirectory,
|
||||
nodeVersion,
|
||||
};
|
||||
|
||||
if (status !== 'not_linked') {
|
||||
createArgs.projectSettings = {
|
||||
...createArgs.projectSettings,
|
||||
...localConfigurationOverrides,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
deployment = await createDeploy(
|
||||
|
||||
@@ -75,7 +75,7 @@ function readConfirmation(
|
||||
}).replace(/^(.*)/gm, ' $1')}\n`
|
||||
);
|
||||
output.print(
|
||||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`
|
||||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('(y/N) ')}`
|
||||
);
|
||||
process.stdin
|
||||
.on('data', d => {
|
||||
|
||||
27
packages/cli/src/commands/env/add.ts
vendored
27
packages/cli/src/commands/env/add.ts
vendored
@@ -29,9 +29,6 @@ export default async function add(
|
||||
args: string[],
|
||||
output: Output
|
||||
) {
|
||||
// improve the way we show inquirer prompts
|
||||
require('../../util/input/patch-inquirer');
|
||||
|
||||
const stdInput = await readStandardInput(client.stdin);
|
||||
let [envName, envTargetArg, envGitBranch] = args;
|
||||
|
||||
@@ -67,15 +64,11 @@ export default async function add(
|
||||
}
|
||||
|
||||
while (!envName) {
|
||||
const { inputName } = await client.prompt({
|
||||
type: 'input',
|
||||
name: 'inputName',
|
||||
envName = await client.input({
|
||||
message: `What’s the name of the variable?`,
|
||||
});
|
||||
|
||||
envName = inputName;
|
||||
|
||||
if (!inputName) {
|
||||
if (!envName) {
|
||||
output.error('Name cannot be empty');
|
||||
}
|
||||
}
|
||||
@@ -107,9 +100,7 @@ export default async function add(
|
||||
if (stdInput) {
|
||||
envValue = stdInput;
|
||||
} else {
|
||||
const { inputValue } = await client.prompt({
|
||||
type: 'input',
|
||||
name: 'inputValue',
|
||||
const inputValue = await client.input.text({
|
||||
message: `What’s the value of ${envName}?`,
|
||||
});
|
||||
|
||||
@@ -117,16 +108,12 @@ export default async function add(
|
||||
}
|
||||
|
||||
while (envTargets.length === 0) {
|
||||
const { inputTargets } = await client.prompt({
|
||||
name: 'inputTargets',
|
||||
type: 'checkbox',
|
||||
envTargets = await client.input.checkbox({
|
||||
message: `Add ${envName} to which Environments (select multiple)?`,
|
||||
choices,
|
||||
});
|
||||
|
||||
envTargets = inputTargets;
|
||||
|
||||
if (inputTargets.length === 0) {
|
||||
if (envTargets.length === 0) {
|
||||
output.error('Please select at least one Environment');
|
||||
}
|
||||
}
|
||||
@@ -137,9 +124,7 @@ export default async function add(
|
||||
envTargets.length === 1 &&
|
||||
envTargets[0] === 'preview'
|
||||
) {
|
||||
const { inputValue } = await client.prompt({
|
||||
type: 'input',
|
||||
name: 'inputValue',
|
||||
const inputValue = await client.input.text({
|
||||
message: `Add ${envName} to which Git branch? (leave empty for all Preview branches)?`,
|
||||
});
|
||||
envGitBranch = inputValue || '';
|
||||
|
||||
11
packages/cli/src/commands/env/rm.ts
vendored
11
packages/cli/src/commands/env/rm.ts
vendored
@@ -29,9 +29,6 @@ export default async function rm(
|
||||
args: string[],
|
||||
output: Output
|
||||
) {
|
||||
// improve the way we show inquirer prompts
|
||||
require('../../util/input/patch-inquirer');
|
||||
|
||||
if (args.length > 3) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${getCommandName(
|
||||
@@ -44,9 +41,7 @@ export default async function rm(
|
||||
let [envName, envTarget, envGitBranch] = args;
|
||||
|
||||
while (!envName) {
|
||||
const { inputName } = await client.prompt({
|
||||
type: 'input',
|
||||
name: 'inputName',
|
||||
const inputName = await client.input({
|
||||
message: `What’s the name of the variable?`,
|
||||
});
|
||||
|
||||
@@ -86,9 +81,7 @@ export default async function rm(
|
||||
}
|
||||
|
||||
while (envs.length > 1) {
|
||||
const { id } = await client.prompt({
|
||||
name: 'id',
|
||||
type: 'list',
|
||||
const id = await client.select({
|
||||
message: `Remove ${envName} from which Environments?`,
|
||||
choices: envs.map(env => ({ value: env.id, name: formatEnvTarget(env) })),
|
||||
});
|
||||
|
||||
@@ -277,7 +277,7 @@ function readConfirmation(
|
||||
}
|
||||
|
||||
output.print(
|
||||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`
|
||||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('(y/N) ')}`
|
||||
);
|
||||
|
||||
process.stdin
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { bold } from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import input from './custom-input/input';
|
||||
import select from './custom-input/select';
|
||||
import checkbox from './custom-input/checkbox';
|
||||
import expand from './custom-input/expand';
|
||||
import confirm from './custom-input/confirm';
|
||||
import { EventEmitter } from 'events';
|
||||
import { URL } from 'url';
|
||||
import { VercelConfig } from '@vercel/client';
|
||||
@@ -66,8 +70,8 @@ export default class Client extends EventEmitter implements Stdio {
|
||||
agent?: Agent;
|
||||
localConfig?: VercelConfig;
|
||||
localConfigPath?: string;
|
||||
prompt!: inquirer.PromptModule;
|
||||
requestIdCounter: number;
|
||||
input;
|
||||
|
||||
constructor(opts: ClientOptions) {
|
||||
super();
|
||||
@@ -83,7 +87,18 @@ export default class Client extends EventEmitter implements Stdio {
|
||||
this.localConfig = opts.localConfig;
|
||||
this.localConfigPath = opts.localConfigPath;
|
||||
this.requestIdCounter = 1;
|
||||
this._createPromptModule();
|
||||
this.input = {
|
||||
text: (opts: Parameters<typeof input>[0]) =>
|
||||
input(opts, { input: this.stdin, output: this.stderr }),
|
||||
checkbox: <T>(opts: Parameters<typeof checkbox<T>>[0]) =>
|
||||
checkbox<T>(opts, { input: this.stdin, output: this.stderr }),
|
||||
expand: (opts: Parameters<typeof expand>[0]) =>
|
||||
expand(opts, { input: this.stdin, output: this.stderr }),
|
||||
confirm: (opts: Parameters<typeof confirm>[0]) =>
|
||||
confirm(opts, { input: this.stdin, output: this.stderr }),
|
||||
select: <T>(opts: Parameters<typeof select<T>>[0]) =>
|
||||
select<T>(opts, { input: this.stdin, output: this.stderr }),
|
||||
};
|
||||
}
|
||||
|
||||
retry<T>(fn: RetryFunction<T>, { retries = 3, maxTimeout = Infinity } = {}) {
|
||||
@@ -229,13 +244,6 @@ export default class Client extends EventEmitter implements Stdio {
|
||||
this.output.debug(`Retrying: ${error}\n${error.stack}`);
|
||||
};
|
||||
|
||||
_createPromptModule() {
|
||||
this.prompt = inquirer.createPromptModule({
|
||||
input: this.stdin as NodeJS.ReadStream,
|
||||
output: this.stderr as NodeJS.WriteStream,
|
||||
});
|
||||
}
|
||||
|
||||
get cwd(): string {
|
||||
return process.cwd();
|
||||
}
|
||||
|
||||
272
packages/cli/src/util/custom-input/checkbox.ts
Normal file
272
packages/cli/src/util/custom-input/checkbox.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import {
|
||||
createPrompt,
|
||||
useState,
|
||||
useKeypress,
|
||||
usePrefix,
|
||||
usePagination,
|
||||
useMemo,
|
||||
makeTheme,
|
||||
isUpKey,
|
||||
isDownKey,
|
||||
isSpaceKey,
|
||||
isNumberKey,
|
||||
isEnterKey,
|
||||
ValidationError,
|
||||
Separator,
|
||||
type Theme,
|
||||
} from '@inquirer/core';
|
||||
import type { PartialDeep } from '@inquirer/type';
|
||||
import chalk from 'chalk';
|
||||
import figures from './util/figures';
|
||||
import ansiEscapes from 'ansi-escapes';
|
||||
|
||||
import isUnicodeSupported from './util/is-unicode-supported';
|
||||
|
||||
const unicode = isUnicodeSupported();
|
||||
const s = (c: string, fallback: string) => (unicode ? c : fallback);
|
||||
const S_STEP_ACTIVE = s('◆', '*');
|
||||
const S_STEP_SUBMIT = s('◇', 'o');
|
||||
|
||||
const S_BAR = s('│', '|');
|
||||
|
||||
const S_CHECKBOX_SELECTED = s('◼', '[+]');
|
||||
const S_CHECKBOX_INACTIVE = s('◻', '[ ]');
|
||||
|
||||
type Status = 'pending' | 'done';
|
||||
|
||||
const symbol = (state: Status) => {
|
||||
switch (state) {
|
||||
case 'pending':
|
||||
return chalk.cyan(S_STEP_ACTIVE);
|
||||
case 'done':
|
||||
return chalk.green(S_STEP_SUBMIT);
|
||||
}
|
||||
};
|
||||
|
||||
type CheckboxTheme = {
|
||||
icon: {
|
||||
checked: string;
|
||||
unchecked: string;
|
||||
cursor: string;
|
||||
};
|
||||
style: {
|
||||
disabledChoice: (text: string) => string;
|
||||
renderSelectedChoices: <T>(
|
||||
selectedChoices: ReadonlyArray<Choice<T>>,
|
||||
allChoices: ReadonlyArray<Choice<T> | Separator>
|
||||
) => string;
|
||||
};
|
||||
};
|
||||
|
||||
const checkboxTheme: CheckboxTheme = {
|
||||
icon: {
|
||||
checked: chalk.green(figures.circleFilled),
|
||||
unchecked: figures.circle,
|
||||
cursor: figures.pointer,
|
||||
},
|
||||
style: {
|
||||
disabledChoice: (text: string) => chalk.dim(`- ${text}`),
|
||||
renderSelectedChoices: selectedChoices =>
|
||||
selectedChoices.map(choice => choice.name || choice.value).join(', '),
|
||||
},
|
||||
};
|
||||
|
||||
type Choice<Value> = {
|
||||
name?: string;
|
||||
value: Value;
|
||||
disabled?: boolean | string;
|
||||
checked?: boolean;
|
||||
type?: never;
|
||||
};
|
||||
|
||||
type Config<Value> = {
|
||||
message: string;
|
||||
prefix?: string;
|
||||
pageSize?: number;
|
||||
instructions?: string | boolean;
|
||||
choices: ReadonlyArray<Choice<Value> | Separator>;
|
||||
loop?: boolean;
|
||||
required?: boolean;
|
||||
validate?: (
|
||||
items: ReadonlyArray<Item<Value>>
|
||||
) => boolean | string | Promise<string | boolean>;
|
||||
theme?: PartialDeep<Theme<CheckboxTheme>>;
|
||||
};
|
||||
|
||||
type Item<Value> = Separator | Choice<Value>;
|
||||
|
||||
function isSelectable<Value>(item: Item<Value>): item is Choice<Value> {
|
||||
return !Separator.isSeparator(item) && !item.disabled;
|
||||
}
|
||||
|
||||
function isChecked<Value>(item: Item<Value>): item is Choice<Value> {
|
||||
return isSelectable(item) && Boolean(item.checked);
|
||||
}
|
||||
|
||||
function toggle<Value>(item: Item<Value>): Item<Value> {
|
||||
return isSelectable(item) ? { ...item, checked: !item.checked } : item;
|
||||
}
|
||||
|
||||
function check(checked: boolean) {
|
||||
return function <Value>(item: Item<Value>): Item<Value> {
|
||||
return isSelectable(item) ? { ...item, checked } : item;
|
||||
};
|
||||
}
|
||||
|
||||
export default createPrompt(
|
||||
<Value>(config: Config<Value>, done: (value: Array<Value>) => void) => {
|
||||
const {
|
||||
instructions,
|
||||
pageSize = 7,
|
||||
loop = true,
|
||||
choices,
|
||||
required,
|
||||
validate = () => true,
|
||||
} = config;
|
||||
const theme = makeTheme<CheckboxTheme>(checkboxTheme, config.theme);
|
||||
const prefix = usePrefix({ theme });
|
||||
const [status, setStatus] = useState<Status>('pending');
|
||||
const [items, setItems] = useState<ReadonlyArray<Item<Value>>>(
|
||||
choices.map(choice => ({ ...choice }))
|
||||
);
|
||||
|
||||
const bounds = useMemo(() => {
|
||||
const first = items.findIndex(isSelectable);
|
||||
// TODO: Replace with `findLastIndex` when it's available.
|
||||
const last =
|
||||
items.length - 1 - [...items].reverse().findIndex(isSelectable);
|
||||
|
||||
if (first < 0) {
|
||||
throw new ValidationError(
|
||||
'[checkbox prompt] No selectable choices. All choices are disabled.'
|
||||
);
|
||||
}
|
||||
|
||||
return { first, last };
|
||||
}, [items]);
|
||||
|
||||
const [active, setActive] = useState(bounds.first);
|
||||
const [showHelpTip, setShowHelpTip] = useState(true);
|
||||
const [errorMsg, setError] = useState<string | undefined>(undefined);
|
||||
|
||||
useKeypress(async key => {
|
||||
if (isEnterKey(key)) {
|
||||
const selection = items.filter(isChecked);
|
||||
const isValid = await validate([...selection]);
|
||||
if (required && !items.some(isChecked)) {
|
||||
setError('At least one choice must be selected');
|
||||
} else if (isValid === true) {
|
||||
setStatus('done');
|
||||
done(selection.map(choice => choice.value));
|
||||
} else {
|
||||
setError(isValid || 'You must select a valid value');
|
||||
}
|
||||
} else if (isUpKey(key) || isDownKey(key)) {
|
||||
if (
|
||||
loop ||
|
||||
(isUpKey(key) && active !== bounds.first) ||
|
||||
(isDownKey(key) && active !== bounds.last)
|
||||
) {
|
||||
const offset = isUpKey(key) ? -1 : 1;
|
||||
let next = active;
|
||||
do {
|
||||
next = (next + offset + items.length) % items.length;
|
||||
} while (!isSelectable(items[next]!));
|
||||
setActive(next);
|
||||
}
|
||||
} else if (isSpaceKey(key)) {
|
||||
setError(undefined);
|
||||
setShowHelpTip(false);
|
||||
setItems(
|
||||
items.map((choice, i) => (i === active ? toggle(choice) : choice))
|
||||
);
|
||||
} else if (key.name === 'a') {
|
||||
const selectAll = Boolean(
|
||||
items.find(choice => isSelectable(choice) && !choice.checked)
|
||||
);
|
||||
setItems(items.map(check(selectAll)));
|
||||
} else if (key.name === 'i') {
|
||||
setItems(items.map(toggle));
|
||||
} else if (isNumberKey(key)) {
|
||||
// Adjust index to start at 1
|
||||
const position = Number(key.name) - 1;
|
||||
const item = items[position];
|
||||
if (item != null && isSelectable(item)) {
|
||||
setActive(position);
|
||||
setItems(
|
||||
items.map((choice, i) => (i === position ? toggle(choice) : choice))
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const message = theme.style.message(config.message);
|
||||
|
||||
const page = usePagination<Item<Value>>({
|
||||
items,
|
||||
active,
|
||||
renderItem({ item, isActive }: { item: Item<Value>; isActive: boolean }) {
|
||||
if (Separator.isSeparator(item)) {
|
||||
return `${chalk.cyan(S_BAR)} ${item.separator}`;
|
||||
}
|
||||
|
||||
const line = item.name || item.value;
|
||||
if (item.disabled) {
|
||||
const disabledLabel =
|
||||
typeof item.disabled === 'string' ? item.disabled : '(disabled)';
|
||||
return `${chalk.cyan(S_BAR)} ${theme.style.disabledChoice(
|
||||
`${line} ${disabledLabel}`
|
||||
)}`;
|
||||
}
|
||||
|
||||
const checkbox = item.checked
|
||||
? S_CHECKBOX_SELECTED
|
||||
: S_CHECKBOX_INACTIVE;
|
||||
const color = isActive ? theme.style.highlight : (x: string) => x;
|
||||
// const cursor = isActive ? " " : " ";
|
||||
return `${chalk.cyan(S_BAR)} ${color(`${checkbox} ${line}`)}`;
|
||||
},
|
||||
pageSize,
|
||||
loop,
|
||||
theme,
|
||||
});
|
||||
|
||||
let helpTip = '';
|
||||
if (showHelpTip && (instructions === undefined || instructions)) {
|
||||
if (typeof instructions === 'string') {
|
||||
helpTip = instructions;
|
||||
} else {
|
||||
const keys = [
|
||||
`${theme.style.key('space')} to select`,
|
||||
`${theme.style.key('a')} to toggle all`,
|
||||
`${theme.style.key('i')} to invert selection`,
|
||||
`and ${theme.style.key('enter')} to proceed`,
|
||||
];
|
||||
helpTip = ` (Press ${keys.join(', ')})`;
|
||||
}
|
||||
}
|
||||
const title_basic = `${chalk.gray(S_BAR)}\n${symbol(
|
||||
status
|
||||
)} ${prefix} ${message}`;
|
||||
const title_with_help = `${title_basic}${helpTip}\n`;
|
||||
const title_without_help = `${title_basic}\n`;
|
||||
|
||||
if (status === 'done') {
|
||||
const selection = items.filter(isChecked);
|
||||
const answer = theme.style.answer(
|
||||
theme.style.renderSelectedChoices(selection, items)
|
||||
);
|
||||
|
||||
return `${title_without_help}${chalk.gray(S_BAR)} ${answer}`;
|
||||
}
|
||||
|
||||
let error = '';
|
||||
if (errorMsg) {
|
||||
error = theme.style.error(errorMsg);
|
||||
}
|
||||
|
||||
return `${title_with_help}${page}\n${error}${ansiEscapes.cursorHide}`;
|
||||
}
|
||||
);
|
||||
|
||||
export { Separator };
|
||||
128
packages/cli/src/util/custom-input/confirm.ts
Normal file
128
packages/cli/src/util/custom-input/confirm.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import {
|
||||
createPrompt,
|
||||
useState,
|
||||
useKeypress,
|
||||
isEnterKey,
|
||||
makeTheme,
|
||||
isUpKey,
|
||||
isDownKey,
|
||||
type Theme,
|
||||
type KeypressEvent,
|
||||
} from '@inquirer/core';
|
||||
import type { PartialDeep } from '@inquirer/type';
|
||||
|
||||
import isUnicodeSupported from './util/is-unicode-supported';
|
||||
import chalk from 'chalk';
|
||||
import ansiEscapes from 'ansi-escapes';
|
||||
|
||||
const unicode = isUnicodeSupported();
|
||||
const s = (c: string, fallback: string) => (unicode ? c : fallback);
|
||||
const S_STEP_ACTIVE = s('◆', '*');
|
||||
const S_STEP_SUBMIT = s('◇', 'o');
|
||||
|
||||
const S_BAR = s('│', '|');
|
||||
const S_BAR_END = s('└', '—');
|
||||
|
||||
const S_RADIO_ACTIVE = s('●', '>');
|
||||
const S_RADIO_INACTIVE = s('○', ' ');
|
||||
|
||||
type Status = 'pending' | 'done';
|
||||
|
||||
const symbol = (state: Status) => {
|
||||
switch (state) {
|
||||
case 'pending':
|
||||
return chalk.cyan(S_STEP_ACTIVE);
|
||||
case 'done':
|
||||
return chalk.green(S_STEP_SUBMIT);
|
||||
}
|
||||
};
|
||||
|
||||
type ConfirmConfig = {
|
||||
message: string;
|
||||
default?: CursorState;
|
||||
active?: string;
|
||||
inactive?: string;
|
||||
transformer?: (value: boolean) => string;
|
||||
theme?: PartialDeep<Theme>;
|
||||
};
|
||||
|
||||
const isLeftKey = (key: KeypressEvent): boolean =>
|
||||
// The left key
|
||||
key.name === 'left' ||
|
||||
// Vim keybinding
|
||||
key.name === 'h' ||
|
||||
// Emacs keybinding
|
||||
(key.ctrl && key.name === 'b');
|
||||
|
||||
const isRightKey = (key: KeypressEvent): boolean =>
|
||||
// The right key
|
||||
key.name === 'right' ||
|
||||
// Vim keybinding
|
||||
key.name === 'l' ||
|
||||
// Emacs keybinding
|
||||
(key.ctrl && key.name === 'f');
|
||||
|
||||
export type CursorState = 'yes' | 'no';
|
||||
|
||||
export default createPrompt<boolean, ConfirmConfig>((config, done) => {
|
||||
const { transformer = answer => (answer ? 'yes' : 'no') } = config;
|
||||
const [status, setStatus] = useState<Status>('pending');
|
||||
const [value, setValue] = useState('');
|
||||
const [cursorStatus, setCursorStatus] = useState<CursorState>(
|
||||
(config.default ? 'yes' : 'no') ?? 'yes'
|
||||
);
|
||||
const theme = makeTheme(config.theme);
|
||||
|
||||
const active = config.active ?? 'Yes';
|
||||
const inactive = config.active ?? 'No';
|
||||
|
||||
const toggle = (cursor_state: CursorState): CursorState => {
|
||||
if (cursor_state === 'yes') {
|
||||
return 'no';
|
||||
} else {
|
||||
return 'yes';
|
||||
}
|
||||
};
|
||||
|
||||
useKeypress(key => {
|
||||
if (isEnterKey(key)) {
|
||||
let answer = cursorStatus === 'yes';
|
||||
|
||||
setValue(transformer(answer));
|
||||
setStatus('done');
|
||||
done(answer);
|
||||
} else if (
|
||||
isUpKey(key) ||
|
||||
isDownKey(key) ||
|
||||
isLeftKey(key) ||
|
||||
isRightKey(key)
|
||||
) {
|
||||
setCursorStatus(toggle(cursorStatus));
|
||||
// setValue(rl.line);
|
||||
}
|
||||
});
|
||||
|
||||
const message = theme.style.message(config.message);
|
||||
const title = `${chalk.gray(S_BAR)}\n${symbol(status)} ${message}\n`;
|
||||
|
||||
const output = (status: Status) => {
|
||||
switch (status) {
|
||||
case 'done':
|
||||
return `${title}${chalk.gray(S_BAR)} ${chalk.dim(
|
||||
value === 'yes' ? active : inactive
|
||||
)}`;
|
||||
default:
|
||||
return `${title}${chalk.cyan(S_BAR)} ${
|
||||
cursorStatus === 'yes'
|
||||
? `${chalk.green(S_RADIO_ACTIVE)} ${active}`
|
||||
: `${chalk.dim(S_RADIO_INACTIVE)} ${chalk.dim(active)}`
|
||||
} ${chalk.dim('/')} ${
|
||||
cursorStatus === 'no'
|
||||
? `${chalk.green(S_RADIO_ACTIVE)} ${inactive}`
|
||||
: `${chalk.dim(S_RADIO_INACTIVE)} ${chalk.dim(inactive)}`
|
||||
} \n${chalk.cyan(S_BAR_END)} \n${ansiEscapes.cursorHide} `;
|
||||
}
|
||||
};
|
||||
|
||||
return output(status);
|
||||
});
|
||||
161
packages/cli/src/util/custom-input/expand.ts
Normal file
161
packages/cli/src/util/custom-input/expand.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import {
|
||||
createPrompt,
|
||||
useState,
|
||||
useKeypress,
|
||||
usePrefix,
|
||||
isEnterKey,
|
||||
makeTheme,
|
||||
type Theme,
|
||||
} from '@inquirer/core';
|
||||
import type { PartialDeep } from '@inquirer/type';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import isUnicodeSupported from './util/is-unicode-supported';
|
||||
|
||||
const unicode = isUnicodeSupported();
|
||||
const s = (c: string, fallback: string) => (unicode ? c : fallback);
|
||||
const S_STEP_ACTIVE = s('◆', '*');
|
||||
const S_STEP_SUBMIT = s('◇', 'o');
|
||||
|
||||
const S_BAR = s('│', '|');
|
||||
const S_BAR_END = s('└', '—');
|
||||
|
||||
type Status = 'pending' | 'done';
|
||||
|
||||
const symbol = (state: Status) => {
|
||||
switch (state) {
|
||||
case 'pending':
|
||||
return chalk.cyan(S_STEP_ACTIVE);
|
||||
case 'done':
|
||||
return chalk.green(S_STEP_SUBMIT);
|
||||
}
|
||||
};
|
||||
|
||||
type ExpandChoice =
|
||||
| { key: string; name: string }
|
||||
| { key: string; value: string }
|
||||
| { key: string; name: string; value: string };
|
||||
|
||||
type ExpandConfig = {
|
||||
message: string;
|
||||
choices: ReadonlyArray<ExpandChoice>;
|
||||
default?: string;
|
||||
expanded?: boolean;
|
||||
theme?: PartialDeep<Theme>;
|
||||
};
|
||||
|
||||
const helpChoice = {
|
||||
key: 'h',
|
||||
name: 'Help, list all options',
|
||||
value: undefined,
|
||||
};
|
||||
|
||||
function getChoiceKey(choice: ExpandChoice, key: 'name' | 'value'): string {
|
||||
if (key === 'name') {
|
||||
if ('name' in choice) return choice.name;
|
||||
return choice.value;
|
||||
}
|
||||
|
||||
if ('value' in choice) return choice.value;
|
||||
return choice.name;
|
||||
}
|
||||
|
||||
export default createPrompt<string, ExpandConfig>((config, done) => {
|
||||
const {
|
||||
choices,
|
||||
default: defaultKey = 'h',
|
||||
expanded: defaultExpandState = false,
|
||||
} = config;
|
||||
const [status, setStatus] = useState<string>('pending');
|
||||
const [value, setValue] = useState<string>('');
|
||||
const [expanded, setExpanded] = useState<boolean>(defaultExpandState);
|
||||
const [errorMsg, setError] = useState<string | undefined>(undefined);
|
||||
const theme = makeTheme(config.theme);
|
||||
const prefix = usePrefix({ theme });
|
||||
|
||||
useKeypress((event, rl) => {
|
||||
if (isEnterKey(event)) {
|
||||
const answer = (value || defaultKey).toLowerCase();
|
||||
if (answer === 'h' && !expanded) {
|
||||
setExpanded(true);
|
||||
} else {
|
||||
const selectedChoice = choices.find(({ key }) => key === answer);
|
||||
if (selectedChoice) {
|
||||
const finalValue = getChoiceKey(selectedChoice, 'value');
|
||||
setValue(finalValue);
|
||||
setStatus('done');
|
||||
done(finalValue);
|
||||
} else if (value === '') {
|
||||
setError('Please input a value');
|
||||
} else {
|
||||
setError(`"${chalk.red(value)}" isn't an available option`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setValue(rl.line);
|
||||
setError(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const message = theme.style.message(config.message);
|
||||
|
||||
const title = `${chalk.gray(S_BAR)}\n${symbol(status)} ${prefix} ${message}`;
|
||||
|
||||
if (status === 'done') {
|
||||
// TODO: `value` should be the display name instead of the raw value.
|
||||
return `${title}\n${chalk.gray(S_BAR)} ${theme.style.answer(value)}`;
|
||||
}
|
||||
|
||||
const allChoices = expanded ? choices : [...choices, helpChoice];
|
||||
|
||||
// Collapsed display style
|
||||
let longChoices = '';
|
||||
let shortChoices = allChoices
|
||||
.map(choice => {
|
||||
if (choice.key === defaultKey) {
|
||||
return choice.key.toUpperCase();
|
||||
}
|
||||
|
||||
return choice.key;
|
||||
})
|
||||
.join('');
|
||||
shortChoices = ` ${theme.style.defaultAnswer(shortChoices)}`;
|
||||
|
||||
// Expanded display style
|
||||
if (expanded) {
|
||||
shortChoices = '';
|
||||
longChoices = allChoices
|
||||
.map(choice => {
|
||||
const line = ` ${choice.key}) ${getChoiceKey(choice, 'name')}`;
|
||||
if (choice.key === value.toLowerCase()) {
|
||||
return `${chalk.cyan(S_BAR)} ${theme.style.highlight(line)}`;
|
||||
}
|
||||
|
||||
return `${chalk.cyan(S_BAR)} ${line}`;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
let helpTip = '';
|
||||
const currentOption = allChoices.find(
|
||||
({ key }) => key === value.toLowerCase()
|
||||
);
|
||||
if (currentOption) {
|
||||
helpTip = `${chalk.cyan(S_BAR)} ${chalk.cyan('>>')} ${getChoiceKey(
|
||||
currentOption,
|
||||
'name'
|
||||
)} `;
|
||||
}
|
||||
|
||||
let error = '';
|
||||
if (errorMsg) {
|
||||
error = theme.style.error(errorMsg);
|
||||
}
|
||||
|
||||
return [
|
||||
`${title}${shortChoices} \n${chalk.cyan(S_BAR)} ${value}`,
|
||||
`${[longChoices, helpTip, error, chalk.cyan(S_BAR_END)]
|
||||
.filter(Boolean)
|
||||
.join('\n')}`,
|
||||
];
|
||||
});
|
||||
129
packages/cli/src/util/custom-input/input.ts
Normal file
129
packages/cli/src/util/custom-input/input.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import {
|
||||
createPrompt,
|
||||
useState,
|
||||
useKeypress,
|
||||
isEnterKey,
|
||||
isBackspaceKey,
|
||||
makeTheme,
|
||||
type Theme,
|
||||
} from '@inquirer/core';
|
||||
|
||||
import type { PartialDeep } from '@inquirer/type';
|
||||
import chalk from 'chalk';
|
||||
import ansiEscapes from 'ansi-escapes';
|
||||
import isUnicodeSupported from './util/is-unicode-supported';
|
||||
|
||||
const unicode = isUnicodeSupported();
|
||||
const s = (c: string, fallback: string) => (unicode ? c : fallback);
|
||||
const S_STEP_ACTIVE = s('◆', '*');
|
||||
const S_STEP_CANCEL = s('■', 'x');
|
||||
const S_STEP_SUBMIT = s('◇', 'o');
|
||||
|
||||
const S_BAR = s('│', '|');
|
||||
const S_BAR_END = s('└', '—');
|
||||
|
||||
type InputConfig = {
|
||||
message: string;
|
||||
default?: string;
|
||||
transformer?: (value: string, { isFinal }: { isFinal: boolean }) => string;
|
||||
validate?: (value: string) => boolean | string | Promise<string | boolean>;
|
||||
theme?: PartialDeep<Theme>;
|
||||
};
|
||||
|
||||
type Status = 'pending' | 'loading' | 'done';
|
||||
|
||||
const symbol = (state: Status) => {
|
||||
switch (state) {
|
||||
case 'pending':
|
||||
return chalk.cyan(S_STEP_ACTIVE);
|
||||
case 'loading':
|
||||
return chalk.red(S_STEP_CANCEL);
|
||||
case 'done':
|
||||
return chalk.green(S_STEP_SUBMIT);
|
||||
}
|
||||
};
|
||||
|
||||
export default createPrompt<string, InputConfig>((config, done) => {
|
||||
const { validate = () => true } = config;
|
||||
const theme = makeTheme(config.theme);
|
||||
const [status, setStatus] = useState<Status>('pending');
|
||||
const [cursorIndex, setCursorIndex] = useState<number>(0);
|
||||
const [defaultValue = '', setDefaultValue] = useState<string>(
|
||||
config.default || ''
|
||||
);
|
||||
const [errorMsg, setError] = useState<string | undefined>(undefined);
|
||||
const [value, setValue] = useState<string>('');
|
||||
|
||||
useKeypress(async (key, rl) => {
|
||||
// Ignore keypress while our prompt is doing other processing.
|
||||
if (status !== 'pending') {
|
||||
return;
|
||||
}
|
||||
setCursorIndex(rl.cursor);
|
||||
if (isEnterKey(key)) {
|
||||
const answer = value || defaultValue;
|
||||
setStatus('loading');
|
||||
const isValid = await validate(answer);
|
||||
if (isValid === true) {
|
||||
setValue(answer);
|
||||
setStatus('done');
|
||||
done(answer);
|
||||
} else {
|
||||
// Reset the readline line value to the previous value. On line event, the value
|
||||
// get cleared, forcing the user to re-enter the value instead of fixing it.
|
||||
rl.write(value);
|
||||
setError(isValid || 'You must provide a valid value');
|
||||
setStatus('pending');
|
||||
}
|
||||
} else if (isBackspaceKey(key) && !value) {
|
||||
setDefaultValue('');
|
||||
} else if (key.name === 'tab' && !value) {
|
||||
setDefaultValue('');
|
||||
rl.clearLine(0); // Remove the tab character.
|
||||
rl.write(defaultValue);
|
||||
setValue(defaultValue);
|
||||
} else {
|
||||
setValue(rl.line);
|
||||
setError(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const message = theme.style.message(config.message);
|
||||
let formattedValue = value;
|
||||
if (typeof config.transformer === 'function') {
|
||||
formattedValue = config.transformer(value, { isFinal: status === 'done' });
|
||||
} else if (status === 'done') {
|
||||
formattedValue = theme.style.answer(value);
|
||||
}
|
||||
|
||||
let error = '';
|
||||
if (errorMsg) {
|
||||
error = theme.style.error(errorMsg);
|
||||
}
|
||||
|
||||
let valueWithCursor: any;
|
||||
if (cursorIndex && cursorIndex >= value.length) {
|
||||
valueWithCursor = `${value}${chalk.inverse(chalk.hidden(' '))}`;
|
||||
} else {
|
||||
const s1 = value.slice(0, cursorIndex);
|
||||
const s2 = value.slice(cursorIndex);
|
||||
if (s2.length !== 0)
|
||||
valueWithCursor = `${s1}${chalk.inverse(s2[0])}${s2.slice(1)}`;
|
||||
else valueWithCursor = `${s1}${chalk.inverse(' ')}`;
|
||||
}
|
||||
|
||||
const title = `${chalk.gray(S_BAR)}\n${symbol(status)} ${message}\n`;
|
||||
|
||||
const output = (status: any) => {
|
||||
switch (status) {
|
||||
case 'done':
|
||||
return `${title}${chalk.gray(S_BAR)} ${formattedValue}`;
|
||||
default:
|
||||
return `${title}${chalk.cyan(S_BAR)} ${valueWithCursor}\n${chalk.cyan(
|
||||
S_BAR_END
|
||||
)}\n${ansiEscapes.cursorHide}`;
|
||||
}
|
||||
};
|
||||
|
||||
return [output(status), error];
|
||||
});
|
||||
229
packages/cli/src/util/custom-input/select.ts
Normal file
229
packages/cli/src/util/custom-input/select.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
import {
|
||||
createPrompt,
|
||||
useState,
|
||||
useKeypress,
|
||||
usePrefix,
|
||||
usePagination,
|
||||
useRef,
|
||||
useMemo,
|
||||
isBackspaceKey,
|
||||
isEnterKey,
|
||||
isUpKey,
|
||||
isDownKey,
|
||||
isNumberKey,
|
||||
Separator,
|
||||
ValidationError,
|
||||
makeTheme,
|
||||
type Theme,
|
||||
} from '@inquirer/core';
|
||||
import type { PartialDeep } from '@inquirer/type';
|
||||
import chalk from 'chalk';
|
||||
import figures from './util/figures';
|
||||
import ansiEscapes from 'ansi-escapes';
|
||||
|
||||
import isUnicodeSupported from './util/is-unicode-supported';
|
||||
|
||||
const unicode = isUnicodeSupported();
|
||||
const s = (c: string, fallback: string) => (unicode ? c : fallback);
|
||||
const S_STEP_ACTIVE = s('◆', '*');
|
||||
const S_STEP_SUBMIT = s('◇', 'o');
|
||||
|
||||
const S_BAR = s('│', '|');
|
||||
const S_BAR_END = s('└', '—');
|
||||
|
||||
const S_RADIO_ACTIVE = s('●', '>');
|
||||
const S_RADIO_INACTIVE = s('○', ' ');
|
||||
|
||||
type SelectTheme = {
|
||||
icon: { cursor: string };
|
||||
style: { disabled: (text: string) => string };
|
||||
};
|
||||
|
||||
const selectTheme: SelectTheme = {
|
||||
icon: { cursor: figures.pointer },
|
||||
style: { disabled: (text: string) => chalk.dim(`- ${text}`) },
|
||||
};
|
||||
|
||||
type Choice<Value> = {
|
||||
value: Value;
|
||||
name?: string;
|
||||
description?: string;
|
||||
disabled?: boolean | string;
|
||||
type?: never;
|
||||
};
|
||||
|
||||
type SelectConfig<Value> = {
|
||||
message: string;
|
||||
choices: ReadonlyArray<Choice<Value> | Separator>;
|
||||
pageSize?: number;
|
||||
loop?: boolean;
|
||||
default?: unknown;
|
||||
theme?: PartialDeep<Theme<SelectTheme>>;
|
||||
};
|
||||
|
||||
type Item<Value> = Separator | Choice<Value>;
|
||||
|
||||
function isSelectable<Value>(item: Item<Value>): item is Choice<Value> {
|
||||
return !Separator.isSeparator(item) && !item.disabled;
|
||||
}
|
||||
|
||||
type Status = 'pending' | 'done';
|
||||
|
||||
const symbol = (state: Status) => {
|
||||
switch (state) {
|
||||
case 'pending':
|
||||
return chalk.cyan(S_STEP_ACTIVE);
|
||||
case 'done':
|
||||
return chalk.green(S_STEP_SUBMIT);
|
||||
}
|
||||
};
|
||||
|
||||
export default createPrompt(
|
||||
<Value>(
|
||||
config: SelectConfig<Value>,
|
||||
done: (value: Value) => void
|
||||
): string => {
|
||||
const { choices: items, loop = true, pageSize = 7 } = config;
|
||||
const firstRender = useRef(true);
|
||||
const theme = makeTheme<SelectTheme>(selectTheme, config.theme);
|
||||
const prefix = usePrefix({ theme });
|
||||
const [status, setStatus] = useState<Status>('pending');
|
||||
const searchTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const bounds = useMemo(() => {
|
||||
const first = items.findIndex(isSelectable);
|
||||
// TODO: Replace with `findLastIndex` when it's available.
|
||||
const last =
|
||||
items.length - 1 - [...items].reverse().findIndex(isSelectable);
|
||||
if (first < 0)
|
||||
throw new ValidationError(
|
||||
'[select prompt] No selectable choices. All choices are disabled.'
|
||||
);
|
||||
return { first, last };
|
||||
}, [items]);
|
||||
|
||||
const defaultItemIndex = useMemo(() => {
|
||||
if (!('default' in config)) return -1;
|
||||
return items.findIndex(
|
||||
item => isSelectable(item) && item.value === config.default
|
||||
);
|
||||
}, [config.default, items]);
|
||||
|
||||
const [active, setActive] = useState(
|
||||
defaultItemIndex === -1 ? bounds.first : defaultItemIndex
|
||||
);
|
||||
|
||||
// Safe to assume the cursor position always point to a Choice.
|
||||
const selectedChoice = items[active] as Choice<Value>;
|
||||
|
||||
useKeypress((key, rl) => {
|
||||
clearTimeout(searchTimeoutRef.current);
|
||||
|
||||
if (isEnterKey(key)) {
|
||||
setStatus('done');
|
||||
done(selectedChoice.value);
|
||||
} else if (isUpKey(key) || isDownKey(key)) {
|
||||
rl.clearLine(0);
|
||||
if (
|
||||
loop ||
|
||||
(isUpKey(key) && active !== bounds.first) ||
|
||||
(isDownKey(key) && active !== bounds.last)
|
||||
) {
|
||||
const offset = isUpKey(key) ? -1 : 1;
|
||||
let next = active;
|
||||
do {
|
||||
next = (next + offset + items.length) % items.length;
|
||||
} while (!isSelectable(items[next]!));
|
||||
setActive(next);
|
||||
}
|
||||
} else if (isNumberKey(key)) {
|
||||
rl.clearLine(0);
|
||||
const position = Number(key.name) - 1;
|
||||
const item = items[position];
|
||||
if (item != null && isSelectable(item)) {
|
||||
setActive(position);
|
||||
}
|
||||
} else if (isBackspaceKey(key)) {
|
||||
rl.clearLine(0);
|
||||
} else {
|
||||
// Default to search
|
||||
const searchTerm = rl.line.toLowerCase();
|
||||
const matchIndex = items.findIndex(item => {
|
||||
if (Separator.isSeparator(item) || !isSelectable(item)) return false;
|
||||
|
||||
return String(item.name || item.value)
|
||||
.toLowerCase()
|
||||
.startsWith(searchTerm);
|
||||
});
|
||||
|
||||
if (matchIndex >= 0) {
|
||||
setActive(matchIndex);
|
||||
}
|
||||
|
||||
searchTimeoutRef.current = setTimeout(() => {
|
||||
rl.clearLine(0);
|
||||
}, 700);
|
||||
}
|
||||
});
|
||||
|
||||
const message = theme.style.message(config.message);
|
||||
|
||||
let helpTip;
|
||||
if (firstRender.current && items.length <= pageSize) {
|
||||
firstRender.current = false;
|
||||
helpTip = theme.style.help('(Use arrow keys)');
|
||||
}
|
||||
|
||||
const page = usePagination<Item<Value>>({
|
||||
items,
|
||||
active,
|
||||
renderItem({ item, isActive }: { item: Item<Value>; isActive: boolean }) {
|
||||
if (Separator.isSeparator(item)) {
|
||||
return `${chalk.cyan(S_BAR)} ${item.separator}`;
|
||||
}
|
||||
|
||||
const line = item.name || item.value;
|
||||
if (item.disabled) {
|
||||
const disabledLabel =
|
||||
typeof item.disabled === 'string' ? item.disabled : '(disabled)';
|
||||
return theme.style.disabled(`${line} ${disabledLabel}`);
|
||||
}
|
||||
|
||||
const color = isActive ? theme.style.highlight : (x: string) => x;
|
||||
const cursor = isActive ? S_RADIO_ACTIVE : S_RADIO_INACTIVE;
|
||||
return `${chalk.cyan(S_BAR)} ${color(`${cursor} ${line}`)}`;
|
||||
},
|
||||
pageSize,
|
||||
loop,
|
||||
theme,
|
||||
});
|
||||
|
||||
const title = `${chalk.gray(S_BAR)}\n${symbol(status)} ${[
|
||||
prefix,
|
||||
message,
|
||||
helpTip,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}\n`;
|
||||
|
||||
if (status === 'done') {
|
||||
const answer =
|
||||
selectedChoice.name ||
|
||||
// TODO: Could we enforce that at the type level? Name should be defined for non-string values.
|
||||
String(selectedChoice.value);
|
||||
return `${title}${chalk.gray(S_BAR)} ${theme.style.answer(answer)}`;
|
||||
}
|
||||
|
||||
const choiceDescription = selectedChoice.description
|
||||
? `\n${selectedChoice.description}`
|
||||
: ``;
|
||||
|
||||
return `${title}${page}${choiceDescription}\n${chalk.cyan(S_BAR_END)}${
|
||||
ansiEscapes.cursorHide
|
||||
} `;
|
||||
}
|
||||
);
|
||||
|
||||
export { Separator };
|
||||
307
packages/cli/src/util/custom-input/util/figures.ts
Normal file
307
packages/cli/src/util/custom-input/util/figures.ts
Normal file
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import isUnicodeSupported from './is-unicode-supported';
|
||||
|
||||
const common = {
|
||||
circleQuestionMark: '(?)',
|
||||
questionMarkPrefix: '(?)',
|
||||
square: '█',
|
||||
squareDarkShade: '▓',
|
||||
squareMediumShade: '▒',
|
||||
squareLightShade: '░',
|
||||
squareTop: '▀',
|
||||
squareBottom: '▄',
|
||||
squareLeft: '▌',
|
||||
squareRight: '▐',
|
||||
squareCenter: '■',
|
||||
bullet: '●',
|
||||
dot: '․',
|
||||
ellipsis: '…',
|
||||
pointerSmall: '›',
|
||||
triangleUp: '▲',
|
||||
triangleUpSmall: '▴',
|
||||
triangleDown: '▼',
|
||||
triangleDownSmall: '▾',
|
||||
triangleLeftSmall: '◂',
|
||||
triangleRightSmall: '▸',
|
||||
home: '⌂',
|
||||
heart: '♥',
|
||||
musicNote: '♪',
|
||||
musicNoteBeamed: '♫',
|
||||
arrowUp: '↑',
|
||||
arrowDown: '↓',
|
||||
arrowLeft: '←',
|
||||
arrowRight: '→',
|
||||
arrowLeftRight: '↔',
|
||||
arrowUpDown: '↕',
|
||||
almostEqual: '≈',
|
||||
notEqual: '≠',
|
||||
lessOrEqual: '≤',
|
||||
greaterOrEqual: '≥',
|
||||
identical: '≡',
|
||||
infinity: '∞',
|
||||
subscriptZero: '₀',
|
||||
subscriptOne: '₁',
|
||||
subscriptTwo: '₂',
|
||||
subscriptThree: '₃',
|
||||
subscriptFour: '₄',
|
||||
subscriptFive: '₅',
|
||||
subscriptSix: '₆',
|
||||
subscriptSeven: '₇',
|
||||
subscriptEight: '₈',
|
||||
subscriptNine: '₉',
|
||||
oneHalf: '½',
|
||||
oneThird: '⅓',
|
||||
oneQuarter: '¼',
|
||||
oneFifth: '⅕',
|
||||
oneSixth: '⅙',
|
||||
oneEighth: '⅛',
|
||||
twoThirds: '⅔',
|
||||
twoFifths: '⅖',
|
||||
threeQuarters: '¾',
|
||||
threeFifths: '⅗',
|
||||
threeEighths: '⅜',
|
||||
fourFifths: '⅘',
|
||||
fiveSixths: '⅚',
|
||||
fiveEighths: '⅝',
|
||||
sevenEighths: '⅞',
|
||||
line: '─',
|
||||
lineBold: '━',
|
||||
lineDouble: '═',
|
||||
lineDashed0: '┄',
|
||||
lineDashed1: '┅',
|
||||
lineDashed2: '┈',
|
||||
lineDashed3: '┉',
|
||||
lineDashed4: '╌',
|
||||
lineDashed5: '╍',
|
||||
lineDashed6: '╴',
|
||||
lineDashed7: '╶',
|
||||
lineDashed8: '╸',
|
||||
lineDashed9: '╺',
|
||||
lineDashed10: '╼',
|
||||
lineDashed11: '╾',
|
||||
lineDashed12: '−',
|
||||
lineDashed13: '–',
|
||||
lineDashed14: '‐',
|
||||
lineDashed15: '⁃',
|
||||
lineVertical: '│',
|
||||
lineVerticalBold: '┃',
|
||||
lineVerticalDouble: '║',
|
||||
lineVerticalDashed0: '┆',
|
||||
lineVerticalDashed1: '┇',
|
||||
lineVerticalDashed2: '┊',
|
||||
lineVerticalDashed3: '┋',
|
||||
lineVerticalDashed4: '╎',
|
||||
lineVerticalDashed5: '╏',
|
||||
lineVerticalDashed6: '╵',
|
||||
lineVerticalDashed7: '╷',
|
||||
lineVerticalDashed8: '╹',
|
||||
lineVerticalDashed9: '╻',
|
||||
lineVerticalDashed10: '╽',
|
||||
lineVerticalDashed11: '╿',
|
||||
lineDownLeft: '┐',
|
||||
lineDownLeftArc: '╮',
|
||||
lineDownBoldLeftBold: '┓',
|
||||
lineDownBoldLeft: '┒',
|
||||
lineDownLeftBold: '┑',
|
||||
lineDownDoubleLeftDouble: '╗',
|
||||
lineDownDoubleLeft: '╖',
|
||||
lineDownLeftDouble: '╕',
|
||||
lineDownRight: '┌',
|
||||
lineDownRightArc: '╭',
|
||||
lineDownBoldRightBold: '┏',
|
||||
lineDownBoldRight: '┎',
|
||||
lineDownRightBold: '┍',
|
||||
lineDownDoubleRightDouble: '╔',
|
||||
lineDownDoubleRight: '╓',
|
||||
lineDownRightDouble: '╒',
|
||||
lineUpLeft: '┘',
|
||||
lineUpLeftArc: '╯',
|
||||
lineUpBoldLeftBold: '┛',
|
||||
lineUpBoldLeft: '┚',
|
||||
lineUpLeftBold: '┙',
|
||||
lineUpDoubleLeftDouble: '╝',
|
||||
lineUpDoubleLeft: '╜',
|
||||
lineUpLeftDouble: '╛',
|
||||
lineUpRight: '└',
|
||||
lineUpRightArc: '╰',
|
||||
lineUpBoldRightBold: '┗',
|
||||
lineUpBoldRight: '┖',
|
||||
lineUpRightBold: '┕',
|
||||
lineUpDoubleRightDouble: '╚',
|
||||
lineUpDoubleRight: '╙',
|
||||
lineUpRightDouble: '╘',
|
||||
lineUpDownLeft: '┤',
|
||||
lineUpBoldDownBoldLeftBold: '┫',
|
||||
lineUpBoldDownBoldLeft: '┨',
|
||||
lineUpDownLeftBold: '┥',
|
||||
lineUpBoldDownLeftBold: '┩',
|
||||
lineUpDownBoldLeftBold: '┪',
|
||||
lineUpDownBoldLeft: '┧',
|
||||
lineUpBoldDownLeft: '┦',
|
||||
lineUpDoubleDownDoubleLeftDouble: '╣',
|
||||
lineUpDoubleDownDoubleLeft: '╢',
|
||||
lineUpDownLeftDouble: '╡',
|
||||
lineUpDownRight: '├',
|
||||
lineUpBoldDownBoldRightBold: '┣',
|
||||
lineUpBoldDownBoldRight: '┠',
|
||||
lineUpDownRightBold: '┝',
|
||||
lineUpBoldDownRightBold: '┡',
|
||||
lineUpDownBoldRightBold: '┢',
|
||||
lineUpDownBoldRight: '┟',
|
||||
lineUpBoldDownRight: '┞',
|
||||
lineUpDoubleDownDoubleRightDouble: '╠',
|
||||
lineUpDoubleDownDoubleRight: '╟',
|
||||
lineUpDownRightDouble: '╞',
|
||||
lineDownLeftRight: '┬',
|
||||
lineDownBoldLeftBoldRightBold: '┳',
|
||||
lineDownLeftBoldRightBold: '┯',
|
||||
lineDownBoldLeftRight: '┰',
|
||||
lineDownBoldLeftBoldRight: '┱',
|
||||
lineDownBoldLeftRightBold: '┲',
|
||||
lineDownLeftRightBold: '┮',
|
||||
lineDownLeftBoldRight: '┭',
|
||||
lineDownDoubleLeftDoubleRightDouble: '╦',
|
||||
lineDownDoubleLeftRight: '╥',
|
||||
lineDownLeftDoubleRightDouble: '╤',
|
||||
lineUpLeftRight: '┴',
|
||||
lineUpBoldLeftBoldRightBold: '┻',
|
||||
lineUpLeftBoldRightBold: '┷',
|
||||
lineUpBoldLeftRight: '┸',
|
||||
lineUpBoldLeftBoldRight: '┹',
|
||||
lineUpBoldLeftRightBold: '┺',
|
||||
lineUpLeftRightBold: '┶',
|
||||
lineUpLeftBoldRight: '┵',
|
||||
lineUpDoubleLeftDoubleRightDouble: '╩',
|
||||
lineUpDoubleLeftRight: '╨',
|
||||
lineUpLeftDoubleRightDouble: '╧',
|
||||
lineUpDownLeftRight: '┼',
|
||||
lineUpBoldDownBoldLeftBoldRightBold: '╋',
|
||||
lineUpDownBoldLeftBoldRightBold: '╈',
|
||||
lineUpBoldDownLeftBoldRightBold: '╇',
|
||||
lineUpBoldDownBoldLeftRightBold: '╊',
|
||||
lineUpBoldDownBoldLeftBoldRight: '╉',
|
||||
lineUpBoldDownLeftRight: '╀',
|
||||
lineUpDownBoldLeftRight: '╁',
|
||||
lineUpDownLeftBoldRight: '┽',
|
||||
lineUpDownLeftRightBold: '┾',
|
||||
lineUpBoldDownBoldLeftRight: '╂',
|
||||
lineUpDownLeftBoldRightBold: '┿',
|
||||
lineUpBoldDownLeftBoldRight: '╃',
|
||||
lineUpBoldDownLeftRightBold: '╄',
|
||||
lineUpDownBoldLeftBoldRight: '╅',
|
||||
lineUpDownBoldLeftRightBold: '╆',
|
||||
lineUpDoubleDownDoubleLeftDoubleRightDouble: '╬',
|
||||
lineUpDoubleDownDoubleLeftRight: '╫',
|
||||
lineUpDownLeftDoubleRightDouble: '╪',
|
||||
lineCross: '╳',
|
||||
lineBackslash: '╲',
|
||||
lineSlash: '╱',
|
||||
};
|
||||
|
||||
const specialMainSymbols = {
|
||||
tick: '✔',
|
||||
info: 'ℹ',
|
||||
warning: '⚠',
|
||||
cross: '✘',
|
||||
squareSmall: '◻',
|
||||
squareSmallFilled: '◼',
|
||||
circle: '◯',
|
||||
circleFilled: '◉',
|
||||
circleDotted: '◌',
|
||||
circleDouble: '◎',
|
||||
circleCircle: 'ⓞ',
|
||||
circleCross: 'ⓧ',
|
||||
circlePipe: 'Ⓘ',
|
||||
radioOn: '◉',
|
||||
radioOff: '◯',
|
||||
checkboxOn: '☒',
|
||||
checkboxOff: '☐',
|
||||
checkboxCircleOn: 'ⓧ',
|
||||
checkboxCircleOff: 'Ⓘ',
|
||||
pointer: '❯',
|
||||
triangleUpOutline: '△',
|
||||
triangleLeft: '◀',
|
||||
triangleRight: '▶',
|
||||
lozenge: '◆',
|
||||
lozengeOutline: '◇',
|
||||
hamburger: '☰',
|
||||
smiley: '㋡',
|
||||
mustache: '෴',
|
||||
star: '★',
|
||||
play: '▶',
|
||||
nodejs: '⬢',
|
||||
oneSeventh: '⅐',
|
||||
oneNinth: '⅑',
|
||||
oneTenth: '⅒',
|
||||
};
|
||||
|
||||
const specialFallbackSymbols = {
|
||||
tick: '√',
|
||||
info: 'i',
|
||||
warning: '‼',
|
||||
cross: '×',
|
||||
squareSmall: '□',
|
||||
squareSmallFilled: '■',
|
||||
circle: '( )',
|
||||
circleFilled: '(*)',
|
||||
circleDotted: '( )',
|
||||
circleDouble: '( )',
|
||||
circleCircle: '(○)',
|
||||
circleCross: '(×)',
|
||||
circlePipe: '(│)',
|
||||
radioOn: '(*)',
|
||||
radioOff: '( )',
|
||||
checkboxOn: '[×]',
|
||||
checkboxOff: '[ ]',
|
||||
checkboxCircleOn: '(×)',
|
||||
checkboxCircleOff: '( )',
|
||||
pointer: '>',
|
||||
triangleUpOutline: '∆',
|
||||
triangleLeft: '◄',
|
||||
triangleRight: '►',
|
||||
lozenge: '♦',
|
||||
lozengeOutline: '◊',
|
||||
hamburger: '≡',
|
||||
smiley: '☺',
|
||||
mustache: '┌─┐',
|
||||
star: '✶',
|
||||
play: '►',
|
||||
nodejs: '♦',
|
||||
oneSeventh: '1/7',
|
||||
oneNinth: '1/9',
|
||||
oneTenth: '1/10',
|
||||
};
|
||||
|
||||
export const mainSymbols = { ...common, ...specialMainSymbols };
|
||||
export const fallbackSymbols = { ...common, ...specialFallbackSymbols };
|
||||
|
||||
const shouldUseMain = isUnicodeSupported();
|
||||
const figures = shouldUseMain ? mainSymbols : fallbackSymbols;
|
||||
export default figures;
|
||||
|
||||
const replacements = Object.entries(specialMainSymbols);
|
||||
|
||||
// On terminals which do not support Unicode symbols, substitute them to other symbols
|
||||
export const replaceSymbols = (
|
||||
string,
|
||||
{ useFallback = !shouldUseMain } = {}
|
||||
) => {
|
||||
if (useFallback) {
|
||||
for (const [key, mainSymbol] of replacements) {
|
||||
string = string.replaceAll(mainSymbol, fallbackSymbols[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return string;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export default function isUnicodeSupported() {
|
||||
if (process.platform !== 'win32') {
|
||||
return process.env.TERM !== 'linux'; // Linux console (kernel)
|
||||
}
|
||||
|
||||
return (
|
||||
Boolean(process.env.WT_SESSION) || // Windows Terminal
|
||||
Boolean(process.env.TERMINUS_SUBLIME) || // Terminus (<0.2.27)
|
||||
process.env.ConEmuTask === '{cmd::Cmder}' || // ConEmu and cmder
|
||||
process.env.TERM_PROGRAM === 'Terminus-Sublime' ||
|
||||
process.env.TERM_PROGRAM === 'vscode' ||
|
||||
process.env.TERM === 'xterm-256color' ||
|
||||
process.env.TERM === 'alacritty' ||
|
||||
process.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm'
|
||||
);
|
||||
}
|
||||
@@ -35,7 +35,7 @@ async function getConfigPrefix() {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function isGlobal() {
|
||||
export async function isGlobal() {
|
||||
try {
|
||||
// This is true for e.g. nvm, node path will be equal to now path
|
||||
if (dirname(process.argv[0]) === dirname(process.argv[1])) {
|
||||
|
||||
@@ -5,14 +5,8 @@ export default async function confirm(
|
||||
message: string,
|
||||
preferred: boolean
|
||||
): Promise<boolean> {
|
||||
require('./patch-inquirer');
|
||||
|
||||
const answers = await client.prompt({
|
||||
type: 'confirm',
|
||||
name: 'value',
|
||||
return client.input.confirm({
|
||||
message,
|
||||
default: preferred,
|
||||
});
|
||||
|
||||
return answers.value;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import inquirer from 'inquirer';
|
||||
import confirm from './confirm';
|
||||
import chalk from 'chalk';
|
||||
import frameworkList, { Framework } from '@vercel/frameworks';
|
||||
@@ -140,33 +139,16 @@ export default async function editProjectSettings(
|
||||
[]
|
||||
);
|
||||
|
||||
const { settingFields } = await inquirer.prompt<{
|
||||
settingFields: Array<
|
||||
Exclude<
|
||||
ConfigKeys,
|
||||
'framework' | 'commandForIgnoringBuildStep' | 'installCommand'
|
||||
>
|
||||
>;
|
||||
}>({
|
||||
name: 'settingFields',
|
||||
type: 'checkbox',
|
||||
const settingFields = (await client.input.checkbox({
|
||||
message: 'Which settings would you like to overwrite (select multiple)?',
|
||||
choices,
|
||||
});
|
||||
})) as ConfigKeys[];
|
||||
|
||||
for (let setting of settingFields) {
|
||||
const field = settingMap[setting];
|
||||
const answers = await inquirer.prompt<{
|
||||
[k in Exclude<
|
||||
ConfigKeys,
|
||||
'framework' | 'commandForIgnoringBuildStep' | 'installCommand'
|
||||
>]: string;
|
||||
}>({
|
||||
type: 'input',
|
||||
name: setting,
|
||||
settings[setting] = await client.input({
|
||||
message: `What's your ${chalk.bold(field)}?`,
|
||||
});
|
||||
settings[setting] = answers[setting];
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
@@ -78,12 +78,9 @@ export default async function inputProject(
|
||||
let project: Project | ProjectNotFound | null = null;
|
||||
|
||||
while (!project || project instanceof ProjectNotFound) {
|
||||
const answers = await client.prompt({
|
||||
type: 'input',
|
||||
name: 'existingProjectName',
|
||||
message: `What’s the name of your existing project?`,
|
||||
const projectName = await client.input({
|
||||
message: 'What’s the name of your existing project?',
|
||||
});
|
||||
const projectName = answers.existingProjectName as string;
|
||||
|
||||
if (!projectName) {
|
||||
output.error(`Project name cannot be empty`);
|
||||
@@ -109,13 +106,10 @@ export default async function inputProject(
|
||||
let newProjectName: string | null = null;
|
||||
|
||||
while (!newProjectName) {
|
||||
const answers = await client.prompt({
|
||||
type: 'input',
|
||||
name: 'newProjectName',
|
||||
newProjectName = await client.input({
|
||||
message: `What’s your project’s name?`,
|
||||
default: !detectedProject ? slugifiedName : undefined,
|
||||
});
|
||||
newProjectName = answers.newProjectName as string;
|
||||
|
||||
if (!newProjectName) {
|
||||
output.error(`Project name cannot be empty`);
|
||||
|
||||
@@ -14,9 +14,7 @@ export async function inputRootDirectory(
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const { rootDirectory } = await client.prompt({
|
||||
type: 'input',
|
||||
name: 'rootDirectory',
|
||||
const rootDirectory = await client.input({
|
||||
message: `In which directory is your code located?`,
|
||||
transformer: (input: string) => {
|
||||
return `${chalk.dim(`./`)}${input}`;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import inquirer from 'inquirer';
|
||||
import { Separator } from '@inquirer/prompts';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import Client from '../client';
|
||||
import eraseLines from '../output/erase-lines';
|
||||
@@ -14,7 +14,7 @@ interface ListSeparator {
|
||||
separator: string;
|
||||
}
|
||||
|
||||
export type ListChoice = ListEntry | ListSeparator | typeof inquirer.Separator;
|
||||
export type ListChoice = ListEntry | ListSeparator | typeof Separator;
|
||||
|
||||
interface ListOptions {
|
||||
message: string;
|
||||
@@ -54,8 +54,6 @@ export default async function list(
|
||||
eraseFinalAnswer = false, // If true, the line with the final answer that inquirer prints will be erased before returning
|
||||
}: ListOptions
|
||||
): Promise<string> {
|
||||
require('./patch-inquirer');
|
||||
|
||||
let biggestLength = 0;
|
||||
let selected: string | undefined;
|
||||
|
||||
@@ -70,14 +68,14 @@ export default async function list(
|
||||
}
|
||||
|
||||
const choices = _choices.map(choice => {
|
||||
if (choice instanceof inquirer.Separator) {
|
||||
if (choice instanceof Separator) {
|
||||
return choice;
|
||||
}
|
||||
|
||||
if ('separator' in choice) {
|
||||
const prefix = `── ${choice.separator} `;
|
||||
const suffix = '─'.repeat(biggestLength - getLength(prefix));
|
||||
return new inquirer.Separator(`${prefix}${suffix}`);
|
||||
return new Separator(`${prefix}${suffix}`);
|
||||
}
|
||||
|
||||
if ('short' in choice) {
|
||||
@@ -93,11 +91,11 @@ export default async function list(
|
||||
|
||||
if (separator) {
|
||||
for (let i = 0; i < choices.length; i += 2) {
|
||||
choices.splice(i, 0, new inquirer.Separator(' '));
|
||||
choices.splice(i, 0, new Separator(' '));
|
||||
}
|
||||
}
|
||||
|
||||
const cancelSeparator = new inquirer.Separator('─'.repeat(biggestLength));
|
||||
const cancelSeparator = new Separator('─'.repeat(biggestLength));
|
||||
const _cancel = {
|
||||
name: 'Cancel',
|
||||
value: '',
|
||||
@@ -110,18 +108,16 @@ export default async function list(
|
||||
choices.push(cancelSeparator, _cancel);
|
||||
}
|
||||
|
||||
const answer = await client.prompt({
|
||||
name: 'value',
|
||||
type: 'list',
|
||||
default: selected,
|
||||
const answer = await client.select({
|
||||
message,
|
||||
choices,
|
||||
pageSize,
|
||||
default: selected,
|
||||
});
|
||||
|
||||
if (eraseFinalAnswer === true) {
|
||||
process.stdout.write(eraseLines(2));
|
||||
}
|
||||
|
||||
return answer.value;
|
||||
return answer;
|
||||
}
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import Prompt from 'inquirer/lib/prompts/base';
|
||||
import Choice from 'inquirer/lib/objects/choice';
|
||||
import Separator from 'inquirer/lib/objects/separator';
|
||||
|
||||
/**
|
||||
* Here we patch inquirer with some tweaks:
|
||||
* - update "list" to use ● and ○ and hide tips
|
||||
* - update "checkbox" to use ◻︎ and ◼︎ and hide tips
|
||||
* - use '?' before questions
|
||||
* - do not apply color to question's answer
|
||||
*/
|
||||
|
||||
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/base.js#L126
|
||||
const getQuestion = function (this: Prompt) {
|
||||
let message = `${chalk.gray('?')} ${this.opt.message} `;
|
||||
|
||||
if (this.opt.type === 'confirm') {
|
||||
if (this.opt.default === 'y/N') {
|
||||
message += `[y/${chalk.bold('N')}] `;
|
||||
} else {
|
||||
message += `[${chalk.bold('Y')}/n] `;
|
||||
}
|
||||
}
|
||||
|
||||
// Append the default if available, and if question isn't answered
|
||||
else if (this.opt.default != null && this.status !== 'answered') {
|
||||
message += chalk.dim(`(${this.opt.default}) `);
|
||||
}
|
||||
|
||||
return message;
|
||||
};
|
||||
|
||||
inquirer.prompt.prompts.list.prototype.getQuestion = getQuestion;
|
||||
inquirer.prompt.prompts.checkbox.prototype.getQuestion = getQuestion;
|
||||
inquirer.prompt.prompts.input.prototype.getQuestion = getQuestion;
|
||||
inquirer.prompt.prompts.confirm.prototype.getQuestion = getQuestion;
|
||||
|
||||
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/list.js#L80
|
||||
inquirer.prompt.prompts.list.prototype.render = function () {
|
||||
// Render question
|
||||
let message = this.getQuestion();
|
||||
|
||||
// Render choices or answer depending on the state
|
||||
if (this.status === 'answered') {
|
||||
message += this.opt.choices.getChoice(this.selected).short;
|
||||
} else {
|
||||
let choicesStr = listRender(this.opt.choices, this.selected);
|
||||
let indexPosition = this.opt.choices.indexOf(
|
||||
this.opt.choices.getChoice(this.selected)
|
||||
);
|
||||
message +=
|
||||
'\n' +
|
||||
this.paginator.paginate(choicesStr, indexPosition, this.opt.pageSize);
|
||||
}
|
||||
|
||||
this.firstRender = false;
|
||||
|
||||
this.screen.render(message);
|
||||
};
|
||||
|
||||
function listRender(choices: (Choice | Separator)[], pointer: number) {
|
||||
let output = '';
|
||||
let separatorOffset = 0;
|
||||
|
||||
choices.forEach((choice, i) => {
|
||||
if (choice.type === 'separator') {
|
||||
separatorOffset++;
|
||||
output += ' ' + choice + '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
if (choice.disabled) {
|
||||
separatorOffset++;
|
||||
output += ' - ' + choice.name;
|
||||
output +=
|
||||
' (' +
|
||||
(typeof choice.disabled === 'string' ? choice.disabled : 'Disabled') +
|
||||
')';
|
||||
output += '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
let isSelected = i - separatorOffset === pointer;
|
||||
let line = (isSelected ? '● ' : '○ ') + choice.name;
|
||||
|
||||
if (isSelected) {
|
||||
line = chalk.cyan(line);
|
||||
}
|
||||
output += line + ' \n';
|
||||
});
|
||||
|
||||
return output.replace(/\n$/, '');
|
||||
}
|
||||
|
||||
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/checkbox.js#L84
|
||||
inquirer.prompt.prompts.checkbox.prototype.render = function (error?: string) {
|
||||
// Render question
|
||||
let message = this.getQuestion();
|
||||
let bottomContent = '';
|
||||
|
||||
if (!this.spaceKeyPressed) {
|
||||
message +=
|
||||
'(Press ' +
|
||||
chalk.cyan.bold('<space>') +
|
||||
' to select, ' +
|
||||
chalk.cyan.bold('<a>') +
|
||||
' to toggle all, ' +
|
||||
chalk.cyan.bold('<i>') +
|
||||
' to invert selection)';
|
||||
}
|
||||
|
||||
// Render choices or answer depending on the state
|
||||
if (this.status === 'answered') {
|
||||
message += this.selection.length > 0 ? this.selection.join(', ') : 'None';
|
||||
} else {
|
||||
let choicesStr = renderChoices(this.opt.choices, this.pointer);
|
||||
let indexPosition = this.opt.choices.indexOf(
|
||||
this.opt.choices.getChoice(this.pointer)
|
||||
);
|
||||
message +=
|
||||
'\n' +
|
||||
this.paginator.paginate(choicesStr, indexPosition, this.opt.pageSize);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
bottomContent = chalk.red('>> ') + error;
|
||||
}
|
||||
|
||||
this.screen.render(message, bottomContent);
|
||||
};
|
||||
|
||||
function renderChoices(choices: (Choice | Separator)[], pointer: number) {
|
||||
let output = '';
|
||||
let separatorOffset = 0;
|
||||
|
||||
choices.forEach(function (choice, i) {
|
||||
if (choice.type === 'separator') {
|
||||
separatorOffset++;
|
||||
output += '' + choice + '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
if (choice.disabled) {
|
||||
separatorOffset++;
|
||||
output += '- ' + choice.name;
|
||||
output +=
|
||||
' (' +
|
||||
(typeof choice.disabled === 'string' ? choice.disabled : 'Disabled') +
|
||||
')';
|
||||
} else {
|
||||
if (i - separatorOffset === pointer) {
|
||||
output += chalk.cyan(
|
||||
(choice.checked ? '› ▪︎' : '› ▫︎') + ' ' + choice.name
|
||||
);
|
||||
} else {
|
||||
output += chalk.cyan(
|
||||
(choice.checked ? ' ▪︎' : ' ▫︎') + ' ' + choice.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
output += '\n';
|
||||
});
|
||||
|
||||
return output.replace(/\n$/, '');
|
||||
}
|
||||
|
||||
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/input.js#L44
|
||||
inquirer.prompt.prompts.input.prototype.render = function (error?: string) {
|
||||
let bottomContent = '';
|
||||
let appendContent = '';
|
||||
let message = this.getQuestion();
|
||||
let transformer = this.opt.transformer;
|
||||
let isFinal = this.status === 'answered';
|
||||
|
||||
if (isFinal) {
|
||||
appendContent = this.answer;
|
||||
} else {
|
||||
appendContent = this.rl.line;
|
||||
}
|
||||
|
||||
if (transformer) {
|
||||
message += transformer(appendContent, this.answers, { isFinal });
|
||||
} else {
|
||||
message += appendContent;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
bottomContent = chalk.red('>> ') + error;
|
||||
}
|
||||
|
||||
this.screen.render(message, bottomContent);
|
||||
};
|
||||
|
||||
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/confirm.js#L64
|
||||
inquirer.prompt.prompts.confirm.prototype.render = function (answer?: boolean) {
|
||||
let message = this.getQuestion();
|
||||
|
||||
if (this.status === 'answered') {
|
||||
message += answer ? 'y' : 'n';
|
||||
} else {
|
||||
message += this.rl.line;
|
||||
}
|
||||
|
||||
this.screen.render(message);
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -10,7 +10,6 @@ export default async function selectOrg(
|
||||
question: string,
|
||||
autoConfirm?: boolean
|
||||
): Promise<Org> {
|
||||
require('./patch-inquirer');
|
||||
const {
|
||||
output,
|
||||
config: { currentTeam },
|
||||
@@ -52,14 +51,9 @@ export default async function selectOrg(
|
||||
return choices[defaultChoiceIndex].value;
|
||||
}
|
||||
|
||||
const answers = await client.prompt({
|
||||
type: 'list',
|
||||
name: 'org',
|
||||
return await client.select({
|
||||
message: question,
|
||||
choices,
|
||||
default: defaultChoiceIndex,
|
||||
default: choices[defaultChoiceIndex].value,
|
||||
});
|
||||
|
||||
const org = answers.org;
|
||||
return org;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import { Separator } from '@inquirer/prompts';
|
||||
import pluralize from 'pluralize';
|
||||
import { homedir } from 'os';
|
||||
import slugify from '@sindresorhus/slugify';
|
||||
@@ -144,16 +144,13 @@ export async function ensureRepoLink(
|
||||
if (yes) {
|
||||
remoteName = defaultRemote;
|
||||
} else {
|
||||
const answer = await client.prompt({
|
||||
type: 'list',
|
||||
name: 'value',
|
||||
remoteName = await client.select({
|
||||
message: 'Which Git remote should be used?',
|
||||
choices: remoteNames.map(name => {
|
||||
return { name: name, value: name };
|
||||
}),
|
||||
default: defaultRemote,
|
||||
});
|
||||
remoteName = answer.value;
|
||||
}
|
||||
}
|
||||
const repoUrl = remoteUrls[remoteName];
|
||||
@@ -222,15 +219,13 @@ export async function ensureRepoLink(
|
||||
selected = projects;
|
||||
} else {
|
||||
const addSeparators = projects.length > 0 && detectedProjectsCount > 0;
|
||||
const answer = await client.prompt({
|
||||
type: 'checkbox',
|
||||
name: 'selected',
|
||||
selected = await client.input.checkbox({
|
||||
message: `Which Projects should be ${
|
||||
projects.length ? 'linked to' : 'created'
|
||||
}?`,
|
||||
choices: [
|
||||
...(addSeparators
|
||||
? [new inquirer.Separator('----- Existing Projects -----')]
|
||||
? [new Separator('----- Existing Projects -----')]
|
||||
: []),
|
||||
...projects.map(project => {
|
||||
return {
|
||||
@@ -240,7 +235,7 @@ export async function ensureRepoLink(
|
||||
};
|
||||
}),
|
||||
...(addSeparators
|
||||
? [new inquirer.Separator('----- New Projects to be created -----')]
|
||||
? [new Separator('----- New Projects to be created -----')]
|
||||
: []),
|
||||
...Array.from(detectedProjects.entries()).flatMap(
|
||||
([rootDirectory, frameworks]) =>
|
||||
@@ -267,9 +262,16 @@ export async function ensureRepoLink(
|
||||
};
|
||||
})
|
||||
),
|
||||
],
|
||||
] as (
|
||||
| Separator
|
||||
| {
|
||||
newProject?: boolean;
|
||||
name: string;
|
||||
value: Project | NewProject;
|
||||
checked: boolean;
|
||||
}
|
||||
)[],
|
||||
});
|
||||
selected = answer.selected;
|
||||
}
|
||||
|
||||
if (selected.length === 0) {
|
||||
|
||||
@@ -198,6 +198,7 @@ export default async function setupAndLink(
|
||||
projectSettings: {
|
||||
...localConfigurationOverrides,
|
||||
sourceFilesOutsideRootDirectory,
|
||||
rootDirectory,
|
||||
},
|
||||
autoAssignCustomDomains: true,
|
||||
};
|
||||
|
||||
@@ -62,12 +62,7 @@ export async function readInput(
|
||||
|
||||
while (!input) {
|
||||
try {
|
||||
const { val } = await client.prompt({
|
||||
type: 'input',
|
||||
name: 'val',
|
||||
message,
|
||||
});
|
||||
input = val;
|
||||
input = await client.input({ message });
|
||||
} catch (err: any) {
|
||||
console.log(); // \n
|
||||
|
||||
|
||||
@@ -97,16 +97,13 @@ async function getProjectLinkFromRepoLink(
|
||||
} else {
|
||||
const selectableProjects =
|
||||
projects.length > 0 ? projects : repoLink.repoConfig.projects;
|
||||
const { p } = await client.prompt({
|
||||
name: 'p',
|
||||
type: 'list',
|
||||
project = await client.select({
|
||||
message: `Please select a Project:`,
|
||||
choices: selectableProjects.map(p => ({
|
||||
value: p,
|
||||
name: p.name,
|
||||
})),
|
||||
});
|
||||
project = p;
|
||||
}
|
||||
if (project) {
|
||||
return {
|
||||
|
||||
1
packages/cli/test/fixtures/unit/.gitignore
vendored
Normal file
1
packages/cli/test/fixtures/unit/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
8
packages/cli/test/fixtures/unit/commands/build/monorepo/.vercel/project.json
vendored
Normal file
8
packages/cli/test/fixtures/unit/commands/build/monorepo/.vercel/project.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"orgId": ".",
|
||||
"projectId": ".",
|
||||
"settings": {
|
||||
"framework": "next",
|
||||
"rootDirectory": "apps/nextjs"
|
||||
}
|
||||
}
|
||||
36
packages/cli/test/fixtures/unit/commands/build/monorepo/apps/nextjs/.gitignore
vendored
Normal file
36
packages/cli/test/fixtures/unit/commands/build/monorepo/apps/nextjs/.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
1
packages/cli/test/fixtures/unit/commands/build/monorepo/apps/nextjs/next.config.js
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/build/monorepo/apps/nextjs/next.config.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
11
packages/cli/test/fixtures/unit/commands/build/monorepo/apps/nextjs/package.json
vendored
Normal file
11
packages/cli/test/fixtures/unit/commands/build/monorepo/apps/nextjs/package.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "nextjs-monorepo-test",
|
||||
"scripts": {
|
||||
"build": "next build"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"next": "^12"
|
||||
}
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/commands/build/monorepo/apps/nextjs/pages/index.jsx
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/build/monorepo/apps/nextjs/pages/index.jsx
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default () => <div>Hi</div>
|
||||
463
packages/cli/test/fixtures/unit/commands/build/monorepo/package-lock.json
generated
vendored
Normal file
463
packages/cli/test/fixtures/unit/commands/build/monorepo/package-lock.json
generated
vendored
Normal file
@@ -0,0 +1,463 @@
|
||||
{
|
||||
"name": "monorepo",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "monorepo",
|
||||
"version": "0.0.0",
|
||||
"workspaces": [
|
||||
"apps/*"
|
||||
]
|
||||
},
|
||||
"apps/nextjs": {
|
||||
"name": "nextjs-monorepo-test",
|
||||
"dependencies": {
|
||||
"next": "^12",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/@next/swc-android-arm-eabi": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.4.tgz",
|
||||
"integrity": "sha512-cM42Cw6V4Bz/2+j/xIzO8nK/Q3Ly+VSlZJTa1vHzsocJRYz8KT6MrreXaci2++SIZCF1rVRCDgAg5PpqRibdIA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/@next/swc-android-arm64": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.3.4.tgz",
|
||||
"integrity": "sha512-5jf0dTBjL+rabWjGj3eghpLUxCukRhBcEJgwLedewEA/LJk2HyqCvGIwj5rH+iwmq1llCWbOky2dO3pVljrapg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.4.tgz",
|
||||
"integrity": "sha512-DqsSTd3FRjQUR6ao0E1e2OlOcrF5br+uegcEGPVonKYJpcr0MJrtYmPxd4v5T6UCJZ+XzydF7eQo5wdGvSZAyA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/@next/swc-darwin-x64": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.4.tgz",
|
||||
"integrity": "sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/@next/swc-freebsd-x64": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.4.tgz",
|
||||
"integrity": "sha512-KM9JXRXi/U2PUM928z7l4tnfQ9u8bTco/jb939pdFUHqc28V43Ohd31MmZD1QzEK4aFlMRaIBQOWQZh4D/E5lQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/@next/swc-linux-arm-gnueabihf": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.4.tgz",
|
||||
"integrity": "sha512-3zqD3pO+z5CZyxtKDTnOJ2XgFFRUBciOox6EWkoZvJfc9zcidNAQxuwonUeNts6Xbm8Wtm5YGIRC0x+12YH7kw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.4.tgz",
|
||||
"integrity": "sha512-kiX0vgJGMZVv+oo1QuObaYulXNvdH/IINmvdZnVzMO/jic/B8EEIGlZ8Bgvw8LCjH3zNVPO3mGrdMvnEEPEhKA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.4.tgz",
|
||||
"integrity": "sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.4.tgz",
|
||||
"integrity": "sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.4.tgz",
|
||||
"integrity": "sha512-YeBmI+63Ro75SUiL/QXEVXQ19T++58aI/IINOyhpsRL1LKdyfK/35iilraZEFz9bLQrwy1LYAR5lK200A9Gjbg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.4.tgz",
|
||||
"integrity": "sha512-Sd0qFUJv8Tj0PukAYbCCDbmXcMkbIuhnTeHm9m4ZGjCf6kt7E/RMs55Pd3R5ePjOkN7dJEuxYBehawTR/aPDSQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/@next/swc-win32-ia32-msvc": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.4.tgz",
|
||||
"integrity": "sha512-rt/vv/vg/ZGGkrkKcuJ0LyliRdbskQU+91bje+PgoYmxTZf/tYs6IfbmgudBJk6gH3QnjHWbkphDdRQrseRefQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.4.tgz",
|
||||
"integrity": "sha512-DQ20JEfTBZAgF8QCjYfJhv2/279M6onxFjdG/+5B0Cyj00/EdBxiWb2eGGFgQhrBbNv/lsvzFbbi0Ptf8Vw/bg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/next": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-12.3.4.tgz",
|
||||
"integrity": "sha512-VcyMJUtLZBGzLKo3oMxrEF0stxh8HwuW976pAzlHhI3t8qJ4SROjCrSh1T24bhrbjw55wfZXAbXPGwPt5FLRfQ==",
|
||||
"dependencies": {
|
||||
"@next/env": "12.3.4",
|
||||
"@swc/helpers": "0.4.11",
|
||||
"caniuse-lite": "^1.0.30001406",
|
||||
"postcss": "8.4.14",
|
||||
"styled-jsx": "5.0.7",
|
||||
"use-sync-external-store": "1.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"next": "dist/bin/next"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.22.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-android-arm-eabi": "12.3.4",
|
||||
"@next/swc-android-arm64": "12.3.4",
|
||||
"@next/swc-darwin-arm64": "12.3.4",
|
||||
"@next/swc-darwin-x64": "12.3.4",
|
||||
"@next/swc-freebsd-x64": "12.3.4",
|
||||
"@next/swc-linux-arm-gnueabihf": "12.3.4",
|
||||
"@next/swc-linux-arm64-gnu": "12.3.4",
|
||||
"@next/swc-linux-arm64-musl": "12.3.4",
|
||||
"@next/swc-linux-x64-gnu": "12.3.4",
|
||||
"@next/swc-linux-x64-musl": "12.3.4",
|
||||
"@next/swc-win32-arm64-msvc": "12.3.4",
|
||||
"@next/swc-win32-ia32-msvc": "12.3.4",
|
||||
"@next/swc-win32-x64-msvc": "12.3.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"fibers": ">= 3.1.0",
|
||||
"node-sass": "^6.0.0 || ^7.0.0",
|
||||
"react": "^17.0.2 || ^18.0.0-0",
|
||||
"react-dom": "^17.0.2 || ^18.0.0-0",
|
||||
"sass": "^1.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"fibers": {
|
||||
"optional": true
|
||||
},
|
||||
"node-sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps/nextjs/node_modules/postcss": {
|
||||
"version": "8.4.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
|
||||
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.4",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"apps/remix": {
|
||||
"name": "my-remix-app",
|
||||
"extraneous": true,
|
||||
"dependencies": {
|
||||
"@remix-run/node": "^2.8.0",
|
||||
"@remix-run/react": "^2.8.0",
|
||||
"@remix-run/server-runtime": "^2.8.0",
|
||||
"@vercel/analytics": "^1.2.2",
|
||||
"@vercel/remix": "^2.8.0",
|
||||
"isbot": "^4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@remix-run/dev": "^2.8.0",
|
||||
"@types/react": "^18.2.20",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^5.1.0",
|
||||
"vite-tsconfig-paths": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.4.tgz",
|
||||
"integrity": "sha512-H/69Lc5Q02dq3o+dxxy5O/oNxFsZpdL6WREtOOtOM1B/weonIwDXkekr1KV5DPVPr12IHFPrMrcJQ6bgPMfn7A=="
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.11.tgz",
|
||||
"integrity": "sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001594",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001594.tgz",
|
||||
"integrity": "sha512-VblSX6nYqyJVs8DKFMldE2IVCJjZ225LW00ydtUWwh5hk9IfkTOffO6r8gJNsH0qqqeAF8KrbMYA2VEwTlGW5g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/nextjs-monorepo-test": {
|
||||
"resolved": "apps/nextjs",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
|
||||
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-jsx": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.7.tgz",
|
||||
"integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==",
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@babel/core": {
|
||||
"optional": true
|
||||
},
|
||||
"babel-plugin-macros": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
||||
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
packages/cli/test/fixtures/unit/commands/build/monorepo/package.json
vendored
Normal file
9
packages/cli/test/fixtures/unit/commands/build/monorepo/package.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "monorepo",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"workspaces": [
|
||||
"apps/*"
|
||||
]
|
||||
}
|
||||
7
packages/cli/test/fixtures/unit/commands/build/with-flags/.vercel/project.json
vendored
Normal file
7
packages/cli/test/fixtures/unit/commands/build/with-flags/.vercel/project.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"orgId": ".",
|
||||
"projectId": ".",
|
||||
"settings": {
|
||||
"framework": null
|
||||
}
|
||||
}
|
||||
21
packages/cli/test/fixtures/unit/commands/build/with-flags/build.js
vendored
Executable file
21
packages/cli/test/fixtures/unit/commands/build/with-flags/build.js
vendored
Executable file
@@ -0,0 +1,21 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
async function main() {
|
||||
const outputDir = path.join(__dirname, '.vercel', 'output');
|
||||
|
||||
await fs.promises.mkdir(outputDir).catch((error) => {
|
||||
if (error.code === 'EEXIST') return;
|
||||
throw error;
|
||||
});
|
||||
|
||||
await fs.promises.copyFile(path.join(__dirname, 'config.json'), path.join(outputDir, 'config.json'));
|
||||
await fs.promises.copyFile(path.join(__dirname, 'flags.json'), path.join(outputDir, 'flags.json'));
|
||||
}
|
||||
|
||||
main().then(() => {
|
||||
process.exit(0);
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
3
packages/cli/test/fixtures/unit/commands/build/with-flags/config.json
vendored
Normal file
3
packages/cli/test/fixtures/unit/commands/build/with-flags/config.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"version": 3
|
||||
}
|
||||
7
packages/cli/test/fixtures/unit/commands/build/with-flags/flags.json
vendored
Normal file
7
packages/cli/test/fixtures/unit/commands/build/with-flags/flags.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"definitions": {
|
||||
"my-next-flag": {
|
||||
"options": [{ "value": true }, { "value": false }]
|
||||
}
|
||||
}
|
||||
}
|
||||
5
packages/cli/test/fixtures/unit/commands/build/with-flags/package.json
vendored
Normal file
5
packages/cli/test/fixtures/unit/commands/build/with-flags/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build": "node build.js"
|
||||
}
|
||||
}
|
||||
4
packages/cli/test/fixtures/unit/commands/deploy/node-low-starting-range/.vercel/project.json
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/deploy/node-low-starting-range/.vercel/project.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "node-low-starting-range"
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/commands/deploy/node-low-starting-range/api/index.js
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/deploy/node-low-starting-range/api/index.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = (req, res) => res.end('Vercel');
|
||||
8
packages/cli/test/fixtures/unit/commands/deploy/node-low-starting-range/package.json
vendored
Normal file
8
packages/cli/test/fixtures/unit/commands/deploy/node-low-starting-range/package.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "node-low-starting-range",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">10"
|
||||
}
|
||||
}
|
||||
4
packages/cli/test/fixtures/unit/commands/deploy/node-low-version/.vercel/project.json
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/deploy/node-low-version/.vercel/project.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "node-low-version"
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/commands/deploy/node-low-version/api/index.js
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/deploy/node-low-version/api/index.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = (req, res) => res.end('Vercel');
|
||||
8
packages/cli/test/fixtures/unit/commands/deploy/node-low-version/package.json
vendored
Normal file
8
packages/cli/test/fixtures/unit/commands/deploy/node-low-version/package.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "node-low-version",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "10"
|
||||
}
|
||||
}
|
||||
4
packages/cli/test/fixtures/unit/commands/deploy/node/.vercel/project.json
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/deploy/node/.vercel/project.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "node"
|
||||
}
|
||||
8
packages/cli/test/fixtures/unit/commands/deploy/node/package.json
vendored
Normal file
8
packages/cli/test/fixtures/unit/commands/deploy/node/package.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "node",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">= 20.x"
|
||||
}
|
||||
}
|
||||
@@ -265,6 +265,23 @@ module.exports = async function prepare(session, binaryPath, tmpFixturesDir) {
|
||||
},
|
||||
}),
|
||||
},
|
||||
'zero-config-next-js-nested': {
|
||||
'app/pages/index.js':
|
||||
'export default () => <div><h1>Now CLI test</h1><p>Zero-config + Next.js</p></div>',
|
||||
'app/package.json': JSON.stringify({
|
||||
name: 'zero-config-next-js-test',
|
||||
scripts: {
|
||||
dev: 'next',
|
||||
start: 'next start',
|
||||
build: 'next build',
|
||||
},
|
||||
dependencies: {
|
||||
next: 'latest',
|
||||
react: 'latest',
|
||||
'react-dom': 'latest',
|
||||
},
|
||||
}),
|
||||
},
|
||||
'lambda-with-128-memory': {
|
||||
'api/memory.js': `
|
||||
module.exports = (req, res) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import type { CLIProcess } from './types';
|
||||
|
||||
function getPromptErrorDetails(
|
||||
@@ -54,7 +55,7 @@ export default async function waitForPrompt(
|
||||
};
|
||||
|
||||
const onData = (rawChunk: Buffer) => {
|
||||
const chunk = rawChunk.toString();
|
||||
const chunk = stripAnsi(rawChunk.toString());
|
||||
|
||||
mostRecentChunk = chunk;
|
||||
console.log('> ' + chunk);
|
||||
|
||||
32
packages/cli/test/integration-1.test.ts
vendored
32
packages/cli/test/integration-1.test.ts
vendored
@@ -19,6 +19,7 @@ import {
|
||||
import formatOutput from './helpers/format-output';
|
||||
import type http from 'http';
|
||||
import type { CLIProcess } from './helpers/types';
|
||||
|
||||
const TEST_TIMEOUT = 3 * 60 * 1000;
|
||||
jest.setTimeout(TEST_TIMEOUT);
|
||||
|
||||
@@ -584,6 +585,37 @@ test('deploy fails using --local-config flag with non-existent path', async () =
|
||||
expect(stderr).toMatch(/does-not-exist\.json/);
|
||||
});
|
||||
|
||||
test('deploy using --local-config flag above target', async () => {
|
||||
const root = await setupE2EFixture('zero-config-next-js-nested');
|
||||
const projectName = `project-link-dev-${
|
||||
Math.random().toString(36).split('.')[1]
|
||||
}`;
|
||||
|
||||
const vc = execCli(binaryPath, ['deploy', `--name=${projectName}`], {
|
||||
cwd: root,
|
||||
});
|
||||
|
||||
await waitForPrompt(vc, /Set up and deploy [^?]+\?/);
|
||||
vc.stdin?.write('yes\n');
|
||||
|
||||
await waitForPrompt(vc, 'Which scope do you want to deploy to?');
|
||||
vc.stdin?.write('\n');
|
||||
|
||||
await waitForPrompt(vc, 'Link to existing project?');
|
||||
vc.stdin?.write('no\n');
|
||||
|
||||
await waitForPrompt(vc, `What’s your project’s name? (${projectName})`);
|
||||
vc.stdin?.write(`\n`);
|
||||
|
||||
await waitForPrompt(vc, 'In which directory is your code located?');
|
||||
vc.stdin?.write('app\n');
|
||||
|
||||
// This means the framework detection worked!
|
||||
await waitForPrompt(vc, 'Auto-detected Project Settings (Next.js)');
|
||||
|
||||
vc.kill();
|
||||
});
|
||||
|
||||
test('deploy using --local-config flag above target', async () => {
|
||||
const root = await setupE2EFixture('local-config-above-target');
|
||||
const target = path.join(root, 'dir');
|
||||
|
||||
39
packages/cli/test/integration-2.test.ts
vendored
39
packages/cli/test/integration-2.test.ts
vendored
@@ -1006,6 +1006,45 @@ test('[vc link --yes] should not show prompts and autolink', async () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('[vc link] should detect frameworks in project rootDirectory', async () => {
|
||||
const dir = await setupE2EFixture('zero-config-next-js-nested');
|
||||
const projectRootDir = 'app';
|
||||
|
||||
const projectName = `project-link-dev-${
|
||||
Math.random().toString(36).split('.')[1]
|
||||
}`;
|
||||
|
||||
// remove previously linked project if it exists
|
||||
await remove(path.join(dir, '.vercel'));
|
||||
|
||||
const vc = execCli(binaryPath, ['link', `--project=${projectName}`], {
|
||||
cwd: dir,
|
||||
env: {
|
||||
FORCE_TTY: '1',
|
||||
},
|
||||
});
|
||||
|
||||
await waitForPrompt(vc, /Set up [^?]+\?/);
|
||||
vc.stdin?.write('yes\n');
|
||||
|
||||
await waitForPrompt(vc, 'Which scope should contain your project?');
|
||||
vc.stdin?.write('\n');
|
||||
|
||||
await waitForPrompt(vc, 'Link to existing project?');
|
||||
vc.stdin?.write('no\n');
|
||||
|
||||
await waitForPrompt(vc, 'What’s your project’s name?');
|
||||
vc.stdin?.write(`${projectName}\n`);
|
||||
|
||||
await waitForPrompt(vc, 'In which directory is your code located?');
|
||||
vc.stdin?.write(`${projectRootDir}\n`);
|
||||
|
||||
// This means the framework detection worked!
|
||||
await waitForPrompt(vc, 'Auto-detected Project Settings (Next.js)');
|
||||
|
||||
vc.kill();
|
||||
});
|
||||
|
||||
test('[vc link] should not duplicate paths in .gitignore', async () => {
|
||||
const dir = await setupE2EFixture('project-link-gitignore');
|
||||
|
||||
|
||||
4
packages/cli/test/integration-3.test.ts
vendored
4
packages/cli/test/integration-3.test.ts
vendored
@@ -617,7 +617,7 @@ test('ensure we render a prompt when deploying home directory', async () => {
|
||||
binaryPath,
|
||||
[directory, '--public', '--name', session, '--force'],
|
||||
{
|
||||
input: 'N',
|
||||
input: 'N\n',
|
||||
}
|
||||
);
|
||||
|
||||
@@ -625,7 +625,7 @@ test('ensure we render a prompt when deploying home directory', async () => {
|
||||
expect(exitCode, formatOutput({ stdout, stderr })).toBe(0);
|
||||
|
||||
expect(stderr).toContain(
|
||||
'You are deploying your home directory. Do you want to continue? [y/N]'
|
||||
'You are deploying your home directory. Do you want to continue? (y/N)'
|
||||
);
|
||||
expect(stderr).toContain('Canceled');
|
||||
});
|
||||
|
||||
@@ -93,8 +93,6 @@ export class MockClient extends Client {
|
||||
this.stderr.pause();
|
||||
this.stderr.isTTY = true;
|
||||
|
||||
this._createPromptModule();
|
||||
|
||||
this.output = new Output(this.stderr);
|
||||
|
||||
this.argv = [];
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import type { Readable } from 'stream';
|
||||
import type { MatcherState } from 'expect';
|
||||
import type { MatcherHintOptions } from 'jest-matcher-utils';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
|
||||
export async function toOutput(
|
||||
this: MatcherState,
|
||||
@@ -27,7 +28,7 @@ export async function toOutput(
|
||||
matcherHint(matcherName, 'stream', 'test', matcherHintOptions) + '\n\n';
|
||||
|
||||
function onData(data: string) {
|
||||
output += data;
|
||||
output += stripAnsi(data);
|
||||
if (output.includes(test)) {
|
||||
cleanup();
|
||||
resolve({
|
||||
|
||||
@@ -785,7 +785,7 @@ describe('build', () => {
|
||||
expect(files.sort()).toEqual(['index.html', 'package.json']);
|
||||
});
|
||||
|
||||
it('should set `VERCEL_ANALYTICS_ID` environment variable and warn users', async () => {
|
||||
it('should set `VERCEL_ANALYTICS_ID` environment variable', async () => {
|
||||
const cwd = fixture('vercel-analytics');
|
||||
const output = join(cwd, '.vercel/output');
|
||||
client.cwd = cwd;
|
||||
@@ -794,9 +794,6 @@ describe('build', () => {
|
||||
|
||||
const env = await fs.readJSON(join(output, 'static', 'env.json'));
|
||||
expect(Object.keys(env).includes('VERCEL_ANALYTICS_ID')).toEqual(true);
|
||||
await expect(client.stderr).toOutput(
|
||||
'Vercel Speed Insights auto-injection is deprecated in favor of @vercel/speed-insights package. Learn more: https://vercel.link/upgrate-to-speed-insights-package'
|
||||
);
|
||||
});
|
||||
|
||||
it('should load environment variables from `.vercel/.env.preview.local`', async () => {
|
||||
@@ -1250,43 +1247,74 @@ describe('build', () => {
|
||||
(await fs.readFile(join(output, 'static/index.txt'), 'utf8')).trim()
|
||||
).toEqual('marketing');
|
||||
});
|
||||
});
|
||||
|
||||
it('should create symlinks for duplicate references to Lambda / EdgeFunction instances', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping test on Windows');
|
||||
return;
|
||||
}
|
||||
const cwd = fixture('functions-symlink');
|
||||
const output = join(cwd, '.vercel/output');
|
||||
client.cwd = cwd;
|
||||
const exitCode = await build(client);
|
||||
expect(exitCode).toEqual(0);
|
||||
it('should write to flags.json', async () => {
|
||||
const cwd = fixture('with-flags');
|
||||
const output = join(cwd, '.vercel', 'output');
|
||||
|
||||
// "functions" directory has output Functions
|
||||
const functions = await fs.readdir(join(output, 'functions'));
|
||||
expect(functions.sort()).toEqual([
|
||||
'edge.func',
|
||||
'edge2.func',
|
||||
'lambda.func',
|
||||
'lambda2.func',
|
||||
]);
|
||||
expect(
|
||||
fs.lstatSync(join(output, 'functions/lambda.func')).isDirectory()
|
||||
).toEqual(true);
|
||||
expect(
|
||||
fs.lstatSync(join(output, 'functions/edge.func')).isDirectory()
|
||||
).toEqual(true);
|
||||
expect(
|
||||
fs.lstatSync(join(output, 'functions/lambda2.func')).isSymbolicLink()
|
||||
).toEqual(true);
|
||||
expect(
|
||||
fs.lstatSync(join(output, 'functions/edge2.func')).isSymbolicLink()
|
||||
).toEqual(true);
|
||||
expect(fs.readlinkSync(join(output, 'functions/lambda2.func'))).toEqual(
|
||||
'lambda.func'
|
||||
);
|
||||
expect(fs.readlinkSync(join(output, 'functions/edge2.func'))).toEqual(
|
||||
'edge.func'
|
||||
);
|
||||
client.cwd = cwd;
|
||||
client.setArgv('build', '--yes');
|
||||
|
||||
const exitCode = await build(client);
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
expect(fs.existsSync(join(output, 'flags.json'))).toBe(true);
|
||||
expect(fs.readJSONSync(join(output, 'flags.json'))).toEqual({
|
||||
definitions: {
|
||||
'my-next-flag': {
|
||||
options: [{ value: true }, { value: false }],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should detect framework version in monorepo app', async () => {
|
||||
const cwd = fixture('monorepo');
|
||||
const output = join(cwd, '.vercel/output');
|
||||
client.cwd = cwd;
|
||||
const exitCode = await build(client);
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
const config = await fs.readJSON(join(output, 'config.json'));
|
||||
expect(typeof config.framework.version).toEqual('string');
|
||||
});
|
||||
|
||||
it('should create symlinks for duplicate references to Lambda / EdgeFunction instances', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping test on Windows');
|
||||
return;
|
||||
}
|
||||
const cwd = fixture('functions-symlink');
|
||||
const output = join(cwd, '.vercel/output');
|
||||
client.cwd = cwd;
|
||||
const exitCode = await build(client);
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
// "functions" directory has output Functions
|
||||
const functions = await fs.readdir(join(output, 'functions'));
|
||||
expect(functions.sort()).toEqual([
|
||||
'edge.func',
|
||||
'edge2.func',
|
||||
'lambda.func',
|
||||
'lambda2.func',
|
||||
]);
|
||||
expect(
|
||||
fs.lstatSync(join(output, 'functions/lambda.func')).isDirectory()
|
||||
).toEqual(true);
|
||||
expect(
|
||||
fs.lstatSync(join(output, 'functions/edge.func')).isDirectory()
|
||||
).toEqual(true);
|
||||
expect(
|
||||
fs.lstatSync(join(output, 'functions/lambda2.func')).isSymbolicLink()
|
||||
).toEqual(true);
|
||||
expect(
|
||||
fs.lstatSync(join(output, 'functions/edge2.func')).isSymbolicLink()
|
||||
).toEqual(true);
|
||||
expect(fs.readlinkSync(join(output, 'functions/lambda2.func'))).toEqual(
|
||||
'lambda.func'
|
||||
);
|
||||
expect(fs.readlinkSync(join(output, 'functions/edge2.func'))).toEqual(
|
||||
'edge.func'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -416,4 +416,152 @@ describe('deploy', () => {
|
||||
version: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('should send `projectSettings.nodeVersion` based on `engines.node` package.json field', async () => {
|
||||
const user = useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
name: 'node',
|
||||
id: 'QmbKpqpiUqbcke',
|
||||
});
|
||||
|
||||
let body: any;
|
||||
client.scenario.post(`/v13/deployments`, (req, res) => {
|
||||
body = req.body;
|
||||
res.json({
|
||||
creator: {
|
||||
uid: user.id,
|
||||
username: user.username,
|
||||
},
|
||||
id: 'dpl_',
|
||||
});
|
||||
});
|
||||
client.scenario.get(`/v13/deployments/dpl_`, (req, res) => {
|
||||
res.json({
|
||||
creator: {
|
||||
uid: user.id,
|
||||
username: user.username,
|
||||
},
|
||||
id: 'dpl_',
|
||||
readyState: 'READY',
|
||||
aliasAssigned: true,
|
||||
alias: [],
|
||||
});
|
||||
});
|
||||
|
||||
const repoRoot = setupUnitFixture('commands/deploy/node');
|
||||
client.cwd = repoRoot;
|
||||
client.setArgv('deploy');
|
||||
const exitCode = await deploy(client);
|
||||
expect(exitCode).toEqual(0);
|
||||
expect(body).toMatchObject({
|
||||
source: 'cli',
|
||||
version: 2,
|
||||
projectSettings: {
|
||||
nodeVersion: '20.x',
|
||||
sourceFilesOutsideRootDirectory: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should send latest supported node version when given a >low-node-version based on `engines.node` package.json field', async () => {
|
||||
const user = useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
name: 'node-low-starting-range',
|
||||
id: 'QmbKpqpiUqbcke',
|
||||
});
|
||||
|
||||
let body: any;
|
||||
client.scenario.post(`/v13/deployments`, (req, res) => {
|
||||
body = req.body;
|
||||
res.json({
|
||||
creator: {
|
||||
uid: user.id,
|
||||
username: user.username,
|
||||
},
|
||||
id: 'dpl_',
|
||||
});
|
||||
});
|
||||
client.scenario.get(`/v13/deployments/dpl_`, (req, res) => {
|
||||
res.json({
|
||||
creator: {
|
||||
uid: user.id,
|
||||
username: user.username,
|
||||
},
|
||||
id: 'dpl_',
|
||||
readyState: 'READY',
|
||||
aliasAssigned: true,
|
||||
alias: [],
|
||||
});
|
||||
});
|
||||
|
||||
const repoRoot = setupUnitFixture(
|
||||
'commands/deploy/node-low-starting-range'
|
||||
);
|
||||
client.cwd = repoRoot;
|
||||
client.setArgv('deploy');
|
||||
const exitCode = await deploy(client);
|
||||
expect(exitCode).toEqual(0);
|
||||
expect(body).toMatchObject({
|
||||
source: 'cli',
|
||||
version: 2,
|
||||
projectSettings: {
|
||||
nodeVersion: '20.x',
|
||||
sourceFilesOutsideRootDirectory: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should send no version when `engines.node` package.json field is fixed to an outdated version', async () => {
|
||||
const user = useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
name: 'node-low-version',
|
||||
id: 'QmbKpqpiUqbcke',
|
||||
});
|
||||
|
||||
let body: any;
|
||||
client.scenario.post(`/v13/deployments`, (req, res) => {
|
||||
body = req.body;
|
||||
res.json({
|
||||
creator: {
|
||||
uid: user.id,
|
||||
username: user.username,
|
||||
},
|
||||
id: 'dpl_',
|
||||
});
|
||||
});
|
||||
client.scenario.get(`/v13/deployments/dpl_`, (req, res) => {
|
||||
res.json({
|
||||
creator: {
|
||||
uid: user.id,
|
||||
username: user.username,
|
||||
},
|
||||
id: 'dpl_',
|
||||
readyState: 'READY',
|
||||
aliasAssigned: true,
|
||||
alias: [],
|
||||
});
|
||||
});
|
||||
|
||||
const repoRoot = setupUnitFixture('commands/deploy/node-low-version');
|
||||
client.cwd = repoRoot;
|
||||
client.setArgv('deploy');
|
||||
const exitCodePromise = deploy(client);
|
||||
await expect(client.stderr).toOutput(
|
||||
'WARN! Node.js Version "10.x" is discontinued and must be upgraded. Please set "engines": { "node": "20.x" } in your `package.json` file to use Node.js 20.'
|
||||
);
|
||||
await expect(exitCodePromise).resolves.toEqual(0);
|
||||
expect(body).toMatchObject({
|
||||
source: 'cli',
|
||||
version: 2,
|
||||
projectSettings: {
|
||||
sourceFilesOutsideRootDirectory: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('git', () => {
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput('Set up');
|
||||
client.stdin.write('y\n');
|
||||
client.stdin.write('\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
'Which scope should contain your project?'
|
||||
@@ -87,12 +87,12 @@ describe('git', () => {
|
||||
client.stdin.write('\r');
|
||||
|
||||
await expect(client.stderr).toOutput('Found project');
|
||||
client.stdin.write('y\n');
|
||||
client.stdin.write('\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Do you still want to connect https://github.com/user2/repo2?`
|
||||
);
|
||||
client.stdin.write('y\n');
|
||||
client.stdin.write('h\n'); // vim left to switch from default no
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Connecting Git remote: https://github.com/user2/repo2`
|
||||
@@ -425,7 +425,7 @@ describe('git', () => {
|
||||
await expect(client.stderr).toOutput(
|
||||
`Are you sure you want to disconnect user/repo from your project?`
|
||||
);
|
||||
client.stdin.write('y\n');
|
||||
client.stdin.write('h\n'); // vim left to switch from default no
|
||||
await expect(client.stderr).toOutput('Disconnected user/repo.');
|
||||
|
||||
const newProjectData: Project = await client.fetch(
|
||||
@@ -526,9 +526,9 @@ describe('git', () => {
|
||||
`Found a repository in your local Git Config: https://github.com/user/repo`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Do you still want to connect https://github.com/user2/repo2? [y/N]`
|
||||
`Do you still want to connect https://github.com/user2/repo2?`
|
||||
);
|
||||
client.stdin.write('y\n');
|
||||
client.stdin.write('h\n'); // vim left to switch from default no
|
||||
await expect(client.stderr).toOutput(
|
||||
`Connecting Git remote: https://github.com/user2/repo2`
|
||||
);
|
||||
@@ -574,9 +574,9 @@ describe('git', () => {
|
||||
`Found multiple Git repositories in your local Git config:\n • origin: https://github.com/user/repo.git\n • secondary: https://github.com/user/repo2.git`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Do you still want to connect https://github.com/user3/repo3? [y/N]`
|
||||
`Do you still want to connect https://github.com/user3/repo3?`
|
||||
);
|
||||
client.stdin.write('y\n');
|
||||
client.stdin.write('h\n'); // vim left to switch away from default no
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Connecting Git remote: https://github.com/user3/repo3`
|
||||
|
||||
@@ -26,16 +26,17 @@ describe('link', () => {
|
||||
client.cwd = cwd;
|
||||
const exitCodePromise = link(client);
|
||||
|
||||
client.stderr.pipe(process.stderr);
|
||||
await expect(client.stderr).toOutput('Set up');
|
||||
client.stdin.write('y\n');
|
||||
client.stdin.write('\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
'Which scope should contain your project?'
|
||||
);
|
||||
client.stdin.write('y\n');
|
||||
client.stdin.write('\n');
|
||||
|
||||
await expect(client.stderr).toOutput('Link to it?');
|
||||
client.stdin.write('y\n');
|
||||
client.stdin.write('\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Linked to ${user.username}/${project.name} (created .vercel and added it to .gitignore)`
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('login', () => {
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\r'); // Return key
|
||||
|
||||
await expect(client.stderr).toOutput('? Enter your email address:');
|
||||
await expect(client.stderr).toOutput('Enter your email address:');
|
||||
|
||||
client.stdin.write(`${user.email}\n`);
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('login', () => {
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\r'); // Return key
|
||||
|
||||
await expect(client.stderr).toOutput('? Enter your email address:');
|
||||
await expect(client.stderr).toOutput('Enter your email address:');
|
||||
|
||||
client.stdin.write(`${user.email}\n`);
|
||||
|
||||
@@ -119,7 +119,7 @@ describe('login', () => {
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\r'); // Return key
|
||||
|
||||
await expect(client.stderr).toOutput('? Enter your email address:');
|
||||
await expect(client.stderr).toOutput('Enter your email address:');
|
||||
|
||||
client.stdin.write(`${user.email}\n`);
|
||||
|
||||
@@ -162,7 +162,7 @@ describe('login', () => {
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\r'); // Return key
|
||||
|
||||
await expect(client.stderr).toOutput('? Enter your email address:');
|
||||
await expect(client.stderr).toOutput('Enter your email address:');
|
||||
|
||||
client.stdin.write(`${user.email}\n`);
|
||||
|
||||
|
||||
@@ -45,55 +45,6 @@ describe('project', () => {
|
||||
expect(data).toEqual([project.project.name, 'https://foobar.com']);
|
||||
});
|
||||
|
||||
it('should list projects running on an soon-to-be-deprecated Node.js version', async () => {
|
||||
jest.useFakeTimers().setSystemTime(new Date('2023-12-08'));
|
||||
|
||||
const user = useUser();
|
||||
useTeams('team_dummy');
|
||||
const project = useProject({
|
||||
...defaultProject,
|
||||
nodeVersion: '16.x',
|
||||
});
|
||||
|
||||
client.setArgv('project', 'ls', '--update-required');
|
||||
await projects(client);
|
||||
|
||||
const lines = createLineIterator(client.stderr);
|
||||
|
||||
let line = await lines.next();
|
||||
expect(line.value).toEqual(`Fetching projects in ${user.username}`);
|
||||
|
||||
line = await lines.next();
|
||||
expect(line.value).toEqual(
|
||||
'WARN! The following Node.js versions will be deprecated soon: 16.x. Please upgrade your projects immediately.'
|
||||
);
|
||||
line = await lines.next();
|
||||
expect(line.value).toEqual(
|
||||
'> For more information visit: https://vercel.com/docs/functions/serverless-functions/runtimes/node-js#node.js-version'
|
||||
);
|
||||
line = await lines.next();
|
||||
expect(line.value).toContain(user.username);
|
||||
|
||||
// empty line
|
||||
line = await lines.next();
|
||||
expect(line.value).toEqual('');
|
||||
|
||||
line = await lines.next();
|
||||
const header = parseSpacedTableRow(line.value!);
|
||||
expect(header).toEqual([
|
||||
'Project Name',
|
||||
'Latest Production URL',
|
||||
'Updated',
|
||||
]);
|
||||
|
||||
line = await lines.next();
|
||||
const data = parseSpacedTableRow(line.value!);
|
||||
data.pop();
|
||||
expect(data).toEqual([project.project.name, 'https://foobar.com']);
|
||||
|
||||
jest.clearAllTimers();
|
||||
});
|
||||
|
||||
it('should list projects when there is no production deployment', async () => {
|
||||
const user = useUser();
|
||||
useTeams('team_dummy');
|
||||
|
||||
@@ -113,7 +113,7 @@ describe('promote', () => {
|
||||
`Fetching deployment "${previousDeployment.url}" in ${previousDeployment.creator?.username}`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
'? This deployment does not target production, therefore promotion will not apply\n production environment variables. Are you sure you want to continue?'
|
||||
'This deployment does not target production, therefore promotion will not \napply production environment variables. Are you sure you want to continue?'
|
||||
);
|
||||
|
||||
// say "no" to the prompt
|
||||
@@ -136,7 +136,7 @@ describe('promote', () => {
|
||||
`Fetching deployment "${previousDeployment.url}" in ${previousDeployment.creator?.username}`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
'? This deployment does not target production, therefore promotion will not apply\n production environment variables. Are you sure you want to continue?'
|
||||
'This deployment does not target production, therefore promotion will not \napply production environment variables. Are you sure you want to continue?'
|
||||
);
|
||||
|
||||
// say "yes" to the prompt
|
||||
|
||||
@@ -4,29 +4,29 @@ import { client } from '../../mocks/client';
|
||||
describe('confirm()', () => {
|
||||
it('should work with multiple prompts', async () => {
|
||||
// true (explicit)
|
||||
let confirmedPromise = confirm(client, 'Explictly true?', false);
|
||||
await expect(client.stderr).toOutput('Explictly true? [y/N]');
|
||||
client.stdin.write('yes\n');
|
||||
let confirmedPromise = confirm(client, 'Explicitly true?', false);
|
||||
await expect(client.stderr).toOutput('Explicitly true?');
|
||||
client.stdin.write('h\n'); // vim left
|
||||
let confirmed = await confirmedPromise;
|
||||
expect(confirmed).toEqual(true);
|
||||
|
||||
// false (explicit)
|
||||
confirmedPromise = confirm(client, 'Explcitly false?', true);
|
||||
await expect(client.stderr).toOutput('Explcitly false? [Y/n]');
|
||||
client.stdin.write('no\n');
|
||||
confirmedPromise = confirm(client, 'Explicitly false?', true);
|
||||
await expect(client.stderr).toOutput('Explicitly false?');
|
||||
client.stdin.write('l\n'); // vim right
|
||||
confirmed = await confirmedPromise;
|
||||
expect(confirmed).toEqual(false);
|
||||
|
||||
// true (default)
|
||||
confirmedPromise = confirm(client, 'Default true?', true);
|
||||
await expect(client.stderr).toOutput('Default true? [Y/n]');
|
||||
await expect(client.stderr).toOutput('Default true?');
|
||||
client.stdin.write('\n');
|
||||
confirmed = await confirmedPromise;
|
||||
expect(confirmed).toEqual(true);
|
||||
|
||||
// false (default)
|
||||
confirmedPromise = confirm(client, 'Default false?', false);
|
||||
await expect(client.stderr).toOutput('Default false? [y/N]');
|
||||
await expect(client.stderr).toOutput('Default false?');
|
||||
client.stdin.write('\n');
|
||||
confirmed = await confirmedPromise;
|
||||
expect(confirmed).toEqual(false);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user