mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-26 19:00:08 +00:00
Compare commits
24 Commits
@vercel/go
...
@vercel/ne
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b52d01f809 | ||
|
|
ffefaf82a1 | ||
|
|
6d8dbfc7d6 | ||
|
|
551cd7f688 | ||
|
|
2dfb6b45cd | ||
|
|
65ae2a289e | ||
|
|
72ea3532b1 | ||
|
|
78fac00823 | ||
|
|
9e255afa37 | ||
|
|
e4be68270f | ||
|
|
9c636dc1ba | ||
|
|
c98c9996bf | ||
|
|
0fcf172a10 | ||
|
|
99e5c4a6db | ||
|
|
b8269b0111 | ||
|
|
decac0fe3f | ||
|
|
591d1686d0 | ||
|
|
5e1d5c921c | ||
|
|
603b1256c6 | ||
|
|
6957c72828 | ||
|
|
9be3650cb7 | ||
|
|
6e1ee7a7d6 | ||
|
|
767ce2cff1 | ||
|
|
bb1d0ce1b7 |
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@@ -26,12 +26,12 @@ jobs:
|
||||
fi
|
||||
- name: Setup Go
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- name: Setup Node
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
- name: Install
|
||||
|
||||
8
.github/workflows/test-integration-cli.yml
vendored
8
.github/workflows/test-integration-cli.yml
vendored
@@ -25,20 +25,20 @@ jobs:
|
||||
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 100
|
||||
- run: git --version
|
||||
- run: git fetch origin main --depth=100
|
||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
||||
- run: git diff origin/main...HEAD --name-only
|
||||
- run: yarn install --network-timeout 1000000
|
||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||
- run: yarn run build
|
||||
- run: yarn test-integration-cli
|
||||
env:
|
||||
|
||||
8
.github/workflows/test-unit.yml
vendored
8
.github/workflows/test-unit.yml
vendored
@@ -25,20 +25,20 @@ jobs:
|
||||
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 100
|
||||
- run: git --version
|
||||
- run: git fetch origin main --depth=100
|
||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
||||
- run: git diff origin/main...HEAD --name-only
|
||||
- run: yarn install --network-timeout 1000000
|
||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||
- run: yarn run build
|
||||
- run: yarn run lint
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node == 14 # only run lint once
|
||||
|
||||
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
@@ -19,14 +19,14 @@ jobs:
|
||||
tests: ${{ steps['set-tests'].outputs['tests'] }}
|
||||
dplUrl: ${{ steps.waitForTarball.outputs.url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- run: git --version
|
||||
- run: git fetch origin main
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'yarn'
|
||||
- run: yarn install --network-timeout 1000000
|
||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||
- id: set-tests
|
||||
run: |
|
||||
TESTS_ARRAY=$(node utils/chunk-tests.js $SCRIPT_NAME)
|
||||
@@ -58,13 +58,13 @@ jobs:
|
||||
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'yarn'
|
||||
|
||||
@@ -115,7 +115,8 @@ export async function shouldServe(options: ShouldServeOptions) {
|
||||
}
|
||||
```
|
||||
|
||||
If this function is not defined, Vercel CLI will use the [default implementation](https://github.com/vercel/vercel/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
|
||||
If this function is not defined, Vercel CLI will use the [default
|
||||
implementation](https://github.com/vercel/vercel/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
|
||||
|
||||
### `startDevServer()`
|
||||
|
||||
@@ -189,7 +190,8 @@ If you need to share state between those steps, use the filesystem.
|
||||
|
||||
### Directory and Cache Lifecycle
|
||||
|
||||
When a new build is created, we pre-populate the `workPath` supplied to `analyze` with the results of the `prepareCache` step of the previous build.
|
||||
When a new build is created, we pre-populate the `workPath` supplied to `analyze` with the results of the `prepareCache` step of the
|
||||
previous build.
|
||||
|
||||
The `analyze` step can modify that directory, and it will not be re-created when it's supplied to `build` and `prepareCache`.
|
||||
|
||||
@@ -197,6 +199,77 @@ The `analyze` step can modify that directory, and it will not be re-created when
|
||||
|
||||
The env and secrets specified by the user as `build.env` are passed to the Runtime process. This means you can access user env via `process.env` in Node.js.
|
||||
|
||||
### Supporting Large Environment
|
||||
|
||||
We provide the ability to support more than 4KB of environment (up to 64KB) by way of
|
||||
a Lambda runtime wrapper that is added to every Lambda function we create. These are
|
||||
supported by many of the existing Lambda runtimes, but custom runtimes may require
|
||||
additional work.
|
||||
|
||||
The following Lambda runtime families have built-in support for the runtime wrapper:
|
||||
|
||||
- `nodejs`
|
||||
- `python` (>= 3.8)
|
||||
- `ruby`
|
||||
- `java11`
|
||||
- `java8.al2` (not `java8`)
|
||||
- `dotnetcore`
|
||||
|
||||
If a custom runtime is based on one of these Lambda runtimes, large environment
|
||||
support will be available without further configuration. Custom runtimes based on
|
||||
other Lambda runtimes, including those that provide the runtime via `provided` and
|
||||
`provided.al2`, must implement runtime wrapper support and indicate it via the
|
||||
`supportsWrapper` flag when calling [`createLambda`](#createlambda()).
|
||||
|
||||
To add support for runtime wrappers to a custom runtime, first check the value of the
|
||||
`AWS_LAMBDA_EXEC_WRAPPER` environment variable in the bootstrap script. Its value is
|
||||
the path to the wrapper executable.
|
||||
|
||||
The wrapper must be passed the path to the runtime as well as any parameters that the
|
||||
runtime requires. This is most easily done in a small `bootstrap` script.
|
||||
|
||||
In this simple `bash` example, the runtime is called directly if
|
||||
`AWS_LAMBDA_EXEC_WRAPPER` has no value, otherwise the wrapper is called with the
|
||||
runtime command as parameters.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
exec $AWS_LAMBDA_EXEC_WRAPPER path/to/runtime param1 param2
|
||||
```
|
||||
|
||||
If the `bootstrap` file is not a launcher script, but the entrypoint of the runtime
|
||||
itself, replace the bootstrap process with the wrapper. Pass the path and parameters
|
||||
of the executing file, ensuring the `AWS_LAMBDA_EXEC_WRAPPER` environment variable is
|
||||
set to blank.
|
||||
|
||||
This `bash` example uses `exec` to replace the running bootstrap process with the
|
||||
wrapper, passing its own path and parameters.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
if [[ -n $AWS_LAMBDA_EXEC_WRAPPER ]]
|
||||
__WRAPPER=$AWS_LAMBDA_EXEC_WRAPPER
|
||||
AWS_LAMBDA_EXEC_WRAPPER=""
|
||||
exec $__WRAPPER "$0" "${@}"
|
||||
fi
|
||||
|
||||
# start the actual runtime functionality
|
||||
```
|
||||
|
||||
Note that unsetting the variable may not have the desired effect due to the way
|
||||
Lambda spawns runtime processes. It is better to explicitly set it to blank.
|
||||
|
||||
The best way to replace the existing bootstrap process is with the
|
||||
[`execve`](https://www.man7.org/linux/man-pages/man2/execve.2.html) syscall.
|
||||
This is achieved by using `exec` in `bash` to replace the running process with the wrapper,
|
||||
maintaining the same PID and environment.
|
||||
|
||||
Once support for runtime wrappers is included, ensure `supportsWrapper` is set to
|
||||
`true` in the call to [`createLambda`](#createlambda()). This will inform the build
|
||||
process to enable large environment support for this runtime.
|
||||
|
||||
### Utilities as peerDependencies
|
||||
|
||||
When you publish your Runtime to npm, make sure to not specify `@vercel/build-utils` (as seen below in the API definitions) as a dependency, but rather as part of `peerDependencies`.
|
||||
@@ -304,6 +377,7 @@ This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refere
|
||||
- `handler: String` path to handler file and (optionally) a function name it exports
|
||||
- `runtime: LambdaRuntime` the name of the lambda runtime
|
||||
- `environment: Object` key-value map of handler-related (aside of those passed by user) environment variables
|
||||
- `supportsWrapper: Boolean` set to true to indicate that Lambda runtime wrappers are supported by this runtime
|
||||
|
||||
### `LambdaRuntime`
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "5.3.0",
|
||||
"version": "5.3.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
@@ -8,17 +8,13 @@ import FileFsRef from '../file-fs-ref';
|
||||
|
||||
export type GlobOptions = vanillaGlob_.IOptions;
|
||||
|
||||
interface FsFiles {
|
||||
[filePath: string]: FileFsRef;
|
||||
}
|
||||
|
||||
const vanillaGlob = promisify(vanillaGlob_);
|
||||
|
||||
export default async function glob(
|
||||
pattern: string,
|
||||
opts: GlobOptions | string,
|
||||
mountpoint?: string
|
||||
): Promise<FsFiles> {
|
||||
): Promise<Record<string, FileFsRef>> {
|
||||
let options: GlobOptions;
|
||||
if (typeof opts === 'string') {
|
||||
options = { cwd: opts };
|
||||
@@ -36,10 +32,11 @@ export default async function glob(
|
||||
throw new Error(`basePath/cwd must be an absolute path (${options.cwd})`);
|
||||
}
|
||||
|
||||
const results: FsFiles = {};
|
||||
const results: Record<string, FileFsRef> = {};
|
||||
const statCache: Record<string, Stats> = {};
|
||||
|
||||
options.symlinks = {};
|
||||
options.statCache = {};
|
||||
options.statCache = statCache;
|
||||
options.stat = true;
|
||||
options.dot = true;
|
||||
|
||||
@@ -47,7 +44,7 @@ export default async function glob(
|
||||
|
||||
for (const relativePath of files) {
|
||||
const fsPath = normalizePath(path.join(options.cwd, relativePath));
|
||||
let stat: Stats = options.statCache[fsPath] as Stats;
|
||||
let stat = statCache[fsPath];
|
||||
assert(
|
||||
stat,
|
||||
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`
|
||||
|
||||
@@ -335,6 +335,7 @@ export interface ProjectSettings {
|
||||
directoryListing?: boolean;
|
||||
gitForkProtection?: boolean;
|
||||
commandForIgnoringBuildStep?: string | null;
|
||||
skipGitConnectDuringLink?: boolean;
|
||||
}
|
||||
|
||||
export interface BuilderV2 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "27.4.0",
|
||||
"version": "28.0.0",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -41,16 +41,16 @@
|
||||
"node": ">= 14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "5.3.0",
|
||||
"@vercel/go": "2.1.0",
|
||||
"@vercel/hydrogen": "0.0.13",
|
||||
"@vercel/next": "3.1.17",
|
||||
"@vercel/node": "2.5.7",
|
||||
"@vercel/python": "3.1.8",
|
||||
"@vercel/redwood": "1.0.17",
|
||||
"@vercel/remix": "1.0.18",
|
||||
"@vercel/ruby": "1.3.24",
|
||||
"@vercel/static-build": "1.0.17",
|
||||
"@vercel/build-utils": "5.3.1",
|
||||
"@vercel/go": "2.1.1",
|
||||
"@vercel/hydrogen": "0.0.14",
|
||||
"@vercel/next": "3.1.18",
|
||||
"@vercel/node": "2.5.8",
|
||||
"@vercel/python": "3.1.9",
|
||||
"@vercel/redwood": "1.0.18",
|
||||
"@vercel/remix": "1.0.19",
|
||||
"@vercel/ruby": "1.3.25",
|
||||
"@vercel/static-build": "1.0.18",
|
||||
"update-notifier": "5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -96,7 +96,7 @@
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@types/yauzl-promise": "2.1.0",
|
||||
"@vercel/client": "12.1.11",
|
||||
"@vercel/client": "12.2.0",
|
||||
"@vercel/frameworks": "1.1.3",
|
||||
"@vercel/fs-detectors": "2.0.5",
|
||||
"@vercel/fun": "1.0.4",
|
||||
@@ -118,7 +118,6 @@
|
||||
"chokidar": "3.3.1",
|
||||
"codecov": "3.8.2",
|
||||
"cpy": "7.2.0",
|
||||
"credit-card": "3.0.1",
|
||||
"date-fns": "1.29.0",
|
||||
"debug": "3.1.0",
|
||||
"dot": "1.1.3",
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
import ansiEscapes from 'ansi-escapes';
|
||||
import chalk from 'chalk';
|
||||
import ccValidator from 'credit-card';
|
||||
import textInput from '../../util/input/text';
|
||||
import cardBrands from '../../util/billing/card-brands';
|
||||
import success from '../../util/output/success';
|
||||
import wait from '../../util/output/wait';
|
||||
import chars from '../../util/output/chars';
|
||||
import error from '../../util/output/error';
|
||||
|
||||
const expDateMiddleware = data => data;
|
||||
|
||||
export default async function ({ creditCards, clear = false, contextName }) {
|
||||
const state = {
|
||||
error: undefined,
|
||||
cardGroupLabel: `> ${chalk.bold(
|
||||
`Enter your card details for ${chalk.bold(contextName)}`
|
||||
)}`,
|
||||
|
||||
name: {
|
||||
label: 'Full Name'.padEnd(12),
|
||||
placeholder: 'John Appleseed',
|
||||
validateValue: data => data.trim().length > 0,
|
||||
},
|
||||
|
||||
cardNumber: {
|
||||
label: 'Number'.padEnd(12),
|
||||
mask: 'cc',
|
||||
placeholder: '#### #### #### ####',
|
||||
validateKeypress: (data, value) => /\d/.test(data) && value.length < 19,
|
||||
validateValue: data => {
|
||||
data = data.replace(/ /g, '');
|
||||
const type = ccValidator.determineCardType(data);
|
||||
if (!type) {
|
||||
return false;
|
||||
}
|
||||
return ccValidator.isValidCardNumber(data, type);
|
||||
},
|
||||
},
|
||||
|
||||
ccv: {
|
||||
label: 'CCV'.padEnd(12),
|
||||
mask: 'ccv',
|
||||
placeholder: '###',
|
||||
validateValue: data => {
|
||||
const brand = state.cardNumber.brand.toLowerCase();
|
||||
return ccValidator.doesCvvMatchType(data, brand);
|
||||
},
|
||||
},
|
||||
|
||||
expDate: {
|
||||
label: 'Exp. Date'.padEnd(12),
|
||||
mask: 'expDate',
|
||||
placeholder: 'mm / yyyy',
|
||||
middleware: expDateMiddleware,
|
||||
validateValue: data => !ccValidator.isExpired(...data.split(' / ')),
|
||||
},
|
||||
};
|
||||
|
||||
async function render() {
|
||||
for (const key in state) {
|
||||
if (!Object.hasOwnProperty.call(state, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const piece = state[key];
|
||||
|
||||
if (typeof piece === 'string') {
|
||||
console.log(piece);
|
||||
} else if (typeof piece === 'object') {
|
||||
let result;
|
||||
|
||||
try {
|
||||
/* eslint-disable no-await-in-loop */
|
||||
result = await textInput({
|
||||
label: `- ${piece.label}`,
|
||||
initialValue: piece.initialValue || piece.value,
|
||||
placeholder: piece.placeholder,
|
||||
mask: piece.mask,
|
||||
validateKeypress: piece.validateKeypress,
|
||||
validateValue: piece.validateValue,
|
||||
autoComplete: piece.autoComplete,
|
||||
});
|
||||
|
||||
piece.value = result;
|
||||
|
||||
if (key === 'cardNumber') {
|
||||
let brand = cardBrands[ccValidator.determineCardType(result)];
|
||||
piece.brand = brand;
|
||||
|
||||
if (brand === 'American Express') {
|
||||
state.ccv.placeholder = '#'.repeat(4);
|
||||
} else {
|
||||
state.ccv.placeholder = '#'.repeat(3);
|
||||
}
|
||||
|
||||
brand = chalk.cyan(`[${brand}]`);
|
||||
const masked = chalk.gray('#### '.repeat(3)) + result.split(' ')[3];
|
||||
process.stdout.write(
|
||||
`${chalk.cyan(chars.tick)} ${piece.label}${masked} ${brand}\n`
|
||||
);
|
||||
} else if (key === 'ccv') {
|
||||
process.stdout.write(
|
||||
`${chalk.cyan(chars.tick)} ${piece.label}${'*'.repeat(
|
||||
result.length
|
||||
)}\n`
|
||||
);
|
||||
} else if (key === 'expDate') {
|
||||
let text = result.split(' / ');
|
||||
text = text[0] + chalk.gray(' / ') + text[1];
|
||||
process.stdout.write(
|
||||
`${chalk.cyan(chars.tick)} ${piece.label}${text}\n`
|
||||
);
|
||||
} else {
|
||||
process.stdout.write(
|
||||
`${chalk.cyan(chars.tick)} ${piece.label}${result}\n`
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.message === 'USER_ABORT') {
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.error(error(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(''); // New line
|
||||
const stopSpinner = wait(process.stderr, 'Saving card');
|
||||
|
||||
try {
|
||||
const res = await creditCards.add({
|
||||
name: state.name.value,
|
||||
cardNumber: state.cardNumber.value,
|
||||
ccv: state.ccv.value,
|
||||
expDate: state.expDate.value,
|
||||
});
|
||||
|
||||
stopSpinner();
|
||||
|
||||
if (clear) {
|
||||
const linesToClear = state.error ? 15 : 14;
|
||||
process.stdout.write(ansiEscapes.eraseLines(linesToClear));
|
||||
}
|
||||
|
||||
console.log(
|
||||
success(
|
||||
`${state.cardNumber.brand || state.cardNumber.card.brand} ending in ${
|
||||
res.last4 || res.card.last4
|
||||
} was added to ${chalk.bold(contextName)}`
|
||||
)
|
||||
);
|
||||
} catch (err) {
|
||||
stopSpinner();
|
||||
const linesToClear = state.error ? 15 : 14;
|
||||
process.stdout.write(ansiEscapes.eraseLines(linesToClear));
|
||||
state.error = `${chalk.red('> Error!')} ${
|
||||
err.message
|
||||
} Please make sure the info is correct`;
|
||||
await render();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await render();
|
||||
} catch (err) {
|
||||
console.erorr(err);
|
||||
}
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import plural from 'pluralize';
|
||||
import { error } from '../../util/error';
|
||||
import NowCreditCards from '../../util/credit-cards';
|
||||
import indent from '../../util/output/indent';
|
||||
import listInput from '../../util/input/list';
|
||||
import success from '../../util/output/success';
|
||||
import promptBool from '../../util/input/prompt-bool';
|
||||
import info from '../../util/output/info';
|
||||
import logo from '../../util/output/logo';
|
||||
import addBilling from './add';
|
||||
import exit from '../../util/exit';
|
||||
import getScope from '../../util/get-scope.ts';
|
||||
import { getPkgName } from '../../util/pkg-name.ts';
|
||||
import getArgs from '../../util/get-args.ts';
|
||||
import handleError from '../../util/handle-error.ts';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${getPkgName()} billing`)} [options] <command>
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
ls Show all of your credit cards
|
||||
add Add a new credit card
|
||||
rm [id] Remove a credit card
|
||||
set-default [id] Make a credit card your default one
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||
'FILE'
|
||||
)} Path to the local ${'`vercel.json`'} file
|
||||
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Add a new credit card (interactively)
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} billing add`)}
|
||||
`);
|
||||
};
|
||||
|
||||
let argv;
|
||||
let subcommand;
|
||||
|
||||
export default async client => {
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
|
||||
subcommand = argv._[0];
|
||||
|
||||
if (argv['--help'] || !subcommand) {
|
||||
help();
|
||||
return 2;
|
||||
}
|
||||
|
||||
const {
|
||||
output,
|
||||
config: { currentTeam },
|
||||
} = client;
|
||||
|
||||
const start = new Date();
|
||||
const creditCards = new NowCreditCards({
|
||||
client,
|
||||
currentTeam,
|
||||
});
|
||||
|
||||
let contextName = null;
|
||||
|
||||
try {
|
||||
({ contextName } = await getScope(client));
|
||||
} catch (err) {
|
||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
||||
output.error(err.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
const args = argv._.slice(1);
|
||||
|
||||
switch (subcommand) {
|
||||
case 'ls':
|
||||
case 'list': {
|
||||
let cards;
|
||||
|
||||
try {
|
||||
cards = await creditCards.ls();
|
||||
} catch (err) {
|
||||
console.error(error(err.message));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const text = cards.sources
|
||||
.map(source => {
|
||||
const _default =
|
||||
source.id === cards.defaultSource
|
||||
? ` ${chalk.bold('(default)')}`
|
||||
: '';
|
||||
const id = `${chalk.gray('-')} ${chalk.cyan(
|
||||
`ID: ${source.id}`
|
||||
)}${_default}`;
|
||||
const number = `${chalk.gray('#### ').repeat(3)}${
|
||||
source.last4 || source.card.last4
|
||||
}`;
|
||||
|
||||
return [
|
||||
id,
|
||||
indent(source.name || source.owner.name, 2),
|
||||
indent(`${source.brand || source.card.brand} ${number}`, 2),
|
||||
].join('\n');
|
||||
})
|
||||
.join('\n\n');
|
||||
|
||||
const elapsed = ms(new Date() - start);
|
||||
console.log(
|
||||
`> ${plural(
|
||||
'card',
|
||||
cards.sources.length,
|
||||
true
|
||||
)} found under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
if (text) {
|
||||
console.log(`\n${text}\n`);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'set-default': {
|
||||
if (args.length > 1) {
|
||||
console.error(error('Invalid number of arguments'));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const start = new Date();
|
||||
|
||||
let cards;
|
||||
try {
|
||||
cards = await creditCards.ls();
|
||||
} catch (err) {
|
||||
console.error(error(err.message));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cards.sources.length === 0) {
|
||||
console.error(error('You have no credit cards to choose from'));
|
||||
return 0;
|
||||
}
|
||||
|
||||
let cardId = args[0];
|
||||
|
||||
if (cardId === undefined) {
|
||||
const elapsed = ms(new Date() - start);
|
||||
const message = `Selecting a new default payment card for ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(`[${elapsed}]`)}`;
|
||||
const choices = buildInquirerChoices(cards);
|
||||
|
||||
cardId = await listInput(client, {
|
||||
message,
|
||||
choices,
|
||||
separator: true,
|
||||
abort: 'end',
|
||||
});
|
||||
}
|
||||
|
||||
// Check if the provided cardId (in case the user
|
||||
// typed `vercel billing set-default <some-id>`) is valid
|
||||
if (cardId) {
|
||||
const label = `Are you sure that you to set this card as the default?`;
|
||||
const confirmation = await promptBool(label, {
|
||||
...client,
|
||||
trailing: '\n',
|
||||
});
|
||||
|
||||
if (!confirmation) {
|
||||
console.log(info('Aborted'));
|
||||
break;
|
||||
}
|
||||
|
||||
const start = new Date();
|
||||
await creditCards.setDefault(cardId);
|
||||
|
||||
const card = cards.sources.find(card => card.id === cardId);
|
||||
const elapsed = ms(new Date() - start);
|
||||
console.log(
|
||||
success(
|
||||
`${card.brand || card.card.brand} ending in ${
|
||||
card.last4 || card.card.last4
|
||||
} is now the default ${chalk.gray(`[${elapsed}]`)}`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.log('No changes made');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'rm':
|
||||
case 'remove': {
|
||||
if (args.length > 1) {
|
||||
console.error(error('Invalid number of arguments'));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const start = new Date();
|
||||
let cards;
|
||||
try {
|
||||
cards = await creditCards.ls();
|
||||
} catch (err) {
|
||||
console.error(error(err.message));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cards.sources.length === 0) {
|
||||
console.error(
|
||||
error(
|
||||
`You have no credit cards to choose from to delete under ${chalk.bold(
|
||||
contextName
|
||||
)}`
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
let cardId = args[0];
|
||||
|
||||
if (cardId === undefined) {
|
||||
const elapsed = ms(new Date() - start);
|
||||
const message = `Selecting a card to ${chalk.underline(
|
||||
'remove'
|
||||
)} under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`;
|
||||
const choices = buildInquirerChoices(cards);
|
||||
|
||||
cardId = await listInput(client, {
|
||||
message,
|
||||
choices,
|
||||
separator: true,
|
||||
abort: 'start',
|
||||
});
|
||||
}
|
||||
|
||||
// Shoud check if the provided cardId (in case the user
|
||||
// typed `vercel billing rm <some-id>`) is valid
|
||||
if (cardId) {
|
||||
const label = `Are you sure that you want to remove this card?`;
|
||||
const confirmation = await promptBool(label, client);
|
||||
if (!confirmation) {
|
||||
console.log('Aborted');
|
||||
break;
|
||||
}
|
||||
const start = new Date();
|
||||
await creditCards.rm(cardId);
|
||||
|
||||
const deletedCard = cards.sources.find(card => card.id === cardId);
|
||||
const remainingCards = cards.sources.filter(card => card.id !== cardId);
|
||||
|
||||
let text = `${deletedCard.brand || deletedCard.card.brand} ending in ${
|
||||
deletedCard.last4 || deletedCard.card.last4
|
||||
} was deleted`;
|
||||
// ${chalk.gray(`[${elapsed}]`)}
|
||||
|
||||
if (cardId === cards.defaultSource) {
|
||||
if (remainingCards.length === 0) {
|
||||
// The user deleted the last card in their account
|
||||
text += `\n${chalk.yellow('Warning!')} You have no default card`;
|
||||
} else {
|
||||
// We can't guess the current default card – let's ask the API
|
||||
const cards = await creditCards.ls();
|
||||
const newDefaultCard = cards.sources.find(
|
||||
card => card.id === cards.defaultCardId
|
||||
);
|
||||
|
||||
text += `\n${
|
||||
newDefaultCard.brand || newDefaultCard.card.brand
|
||||
} ending in ${
|
||||
newDefaultCard.last4 || newDefaultCard.card.last4
|
||||
} in now default for ${chalk.bold(contextName)}`;
|
||||
}
|
||||
}
|
||||
|
||||
const elapsed = ms(new Date() - start);
|
||||
text += ` ${chalk.gray(`[${elapsed}]`)}`;
|
||||
console.log(success(text));
|
||||
} else {
|
||||
console.log('No changes made');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'add': {
|
||||
await addBilling({
|
||||
creditCards,
|
||||
contextName,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
console.error(
|
||||
error('Please specify a valid subcommand: ls | add | rm | set-default')
|
||||
);
|
||||
help();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// This is required, otherwise we get those weird zlib errors
|
||||
return exit(0);
|
||||
};
|
||||
|
||||
// Builds a `choices` object that can be passesd to inquirer.prompt()
|
||||
function buildInquirerChoices(cards) {
|
||||
return cards.sources.map(source => {
|
||||
const _default =
|
||||
source.id === cards.defaultSource ? ` ${chalk.bold('(default)')}` : '';
|
||||
const id = `${chalk.cyan(`ID: ${source.id}`)}${_default}`;
|
||||
const number = `${chalk.gray('#### ').repeat(3)}${
|
||||
source.last4 || source.card.last4
|
||||
}`;
|
||||
const str = [
|
||||
id,
|
||||
indent(source.name || source.owner.name, 2),
|
||||
indent(`${source.brand || source.card.brand} ${number}`, 2),
|
||||
].join('\n');
|
||||
|
||||
return {
|
||||
name: str, // Will be displayed by Inquirer
|
||||
value: source.id, // Will be used to identify the answer
|
||||
short: source.id, // Will be displayed after the users answers
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -91,7 +91,7 @@ const help = () => {
|
||||
--output [path] Directory where built assets should be written to
|
||||
--prod Build a production deployment
|
||||
-d, --debug Debug mode [off]
|
||||
-y, --yes Pull environment variables and project settings if not found locally
|
||||
-y, --yes Skip the confirmation prompt about pulling environment variables and project settings when not found locally
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ export const help = () => `
|
||||
|
||||
${chalk.dim('Basic')}
|
||||
|
||||
billing Manages the account payment methods
|
||||
deploy [path] Performs a deployment ${chalk.bold(
|
||||
'(default)'
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,11 @@ import fs from 'fs-extra';
|
||||
import bytes from 'bytes';
|
||||
import chalk from 'chalk';
|
||||
import { join, resolve, basename } from 'path';
|
||||
import { fileNameSymbol, VercelConfig } from '@vercel/client';
|
||||
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';
|
||||
@@ -66,10 +70,11 @@ 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 '../../util/is-error';
|
||||
|
||||
export default async (client: Client) => {
|
||||
export default async (client: Client): Promise<number> => {
|
||||
const { output } = client;
|
||||
|
||||
let argv = null;
|
||||
@@ -87,6 +92,7 @@ export default async (client: Client) => {
|
||||
'--regions': String,
|
||||
'--prebuilt': Boolean,
|
||||
'--prod': Boolean,
|
||||
'--archive': String,
|
||||
'--yes': Boolean,
|
||||
'-f': '--force',
|
||||
'-p': '--public',
|
||||
@@ -261,6 +267,12 @@ export default async (client: Client) => {
|
||||
}
|
||||
}
|
||||
|
||||
const archive = argv['--archive'];
|
||||
if (typeof archive === 'string' && !isValidArchive(archive)) {
|
||||
output.error(`Format must be one of: ${VALID_ARCHIVE_FORMATS.join(', ')}`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// retrieve `project` and `org` from .vercel
|
||||
const link = await getLinkedProject(client, path);
|
||||
|
||||
@@ -545,7 +557,8 @@ export default async (client: Client) => {
|
||||
createArgs,
|
||||
org,
|
||||
!project,
|
||||
path
|
||||
path,
|
||||
archive
|
||||
);
|
||||
|
||||
if (deployment.code === 'missing_project_settings') {
|
||||
@@ -918,4 +931,6 @@ const printDeploymentStatus = async (
|
||||
) + newline;
|
||||
output.print(message + link);
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
@@ -116,9 +116,7 @@ export default async function buy(
|
||||
|
||||
if (buyResult instanceof ERRORS.SourceNotFound) {
|
||||
output.error(
|
||||
`Could not purchase domain. Please add a payment method using ${getCommandName(
|
||||
`billing add`
|
||||
)}.`
|
||||
`Could not purchase domain. Please add a payment method using the dashboard.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -111,9 +111,7 @@ export default async function transferIn(
|
||||
|
||||
if (transferInResult instanceof ERRORS.SourceNotFound) {
|
||||
output.error(
|
||||
`Could not purchase domain. Please add a payment method using ${getCommandName(
|
||||
`billing add`
|
||||
)}.`
|
||||
`Could not purchase domain. Please add a payment method using the dashboard.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
7
packages/cli/src/commands/env/pull.ts
vendored
7
packages/cli/src/commands/env/pull.ts
vendored
@@ -117,7 +117,12 @@ export default async function pull(
|
||||
if (exists) {
|
||||
oldEnv = await createEnvObject(fullPath, output);
|
||||
if (oldEnv) {
|
||||
deltaString = buildDeltaString(oldEnv, records);
|
||||
// Removes any double quotes from `records`, if they exist
|
||||
// We need this because double quotes are stripped from the local .env file,
|
||||
// but `records` is already in the form of a JSON object that doesn't filter
|
||||
// double quotes.
|
||||
const newEnv = JSON.parse(JSON.stringify(records).replace(/\\"/g, ''));
|
||||
deltaString = buildDeltaString(oldEnv, newEnv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
packages/cli/src/commands/env/rm.ts
vendored
5
packages/cli/src/commands/env/rm.ts
vendored
@@ -1,5 +1,4 @@
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import { Project } from '../../types';
|
||||
import { Output } from '../../util/output';
|
||||
import confirm from '../../util/input/confirm';
|
||||
@@ -45,7 +44,7 @@ export default async function rm(
|
||||
let [envName, envTarget, envGitBranch] = args;
|
||||
|
||||
while (!envName) {
|
||||
const { inputName } = await inquirer.prompt({
|
||||
const { inputName } = await client.prompt({
|
||||
type: 'input',
|
||||
name: 'inputName',
|
||||
message: `What’s the name of the variable?`,
|
||||
@@ -87,7 +86,7 @@ export default async function rm(
|
||||
}
|
||||
|
||||
while (envs.length > 1) {
|
||||
const { id } = await inquirer.prompt({
|
||||
const { id } = await client.prompt({
|
||||
name: 'id',
|
||||
type: 'list',
|
||||
message: `Remove ${envName} from which Environments?`,
|
||||
|
||||
@@ -1,21 +1,54 @@
|
||||
import { Dictionary } from '@vercel/client';
|
||||
import chalk from 'chalk';
|
||||
import { join } from 'path';
|
||||
import { Org, Project } from '../../types';
|
||||
import { Org, Project, ProjectLinkData } from '../../types';
|
||||
import Client from '../../util/client';
|
||||
import { parseGitConfig, pluckRemoteUrls } from '../../util/create-git-meta';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import list, { ListChoice } from '../../util/input/list';
|
||||
import { Output } from '../../util/output';
|
||||
import link from '../../util/output/link';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import {
|
||||
connectGitProvider,
|
||||
disconnectGitProvider,
|
||||
formatProvider,
|
||||
RepoInfo,
|
||||
parseRepoUrl,
|
||||
} from '../../util/projects/connect-git-provider';
|
||||
printRemoteUrls,
|
||||
} from '../../util/git/connect-git-provider';
|
||||
import validatePaths from '../../util/validate-paths';
|
||||
|
||||
interface GitRepoCheckParams {
|
||||
client: Client;
|
||||
confirm: boolean;
|
||||
gitProviderLink?: ProjectLinkData;
|
||||
org: Org;
|
||||
gitOrg: string;
|
||||
project: Project;
|
||||
provider: string;
|
||||
repo: string;
|
||||
repoPath: string;
|
||||
}
|
||||
|
||||
interface ConnectArgParams {
|
||||
client: Client;
|
||||
org: Org;
|
||||
project: Project;
|
||||
confirm: boolean;
|
||||
repoInfo: RepoInfo;
|
||||
}
|
||||
|
||||
interface ConnectGitArgParams extends ConnectArgParams {
|
||||
gitConfig: Dictionary<any>;
|
||||
}
|
||||
|
||||
interface PromptConnectArgParams {
|
||||
client: Client;
|
||||
yes: boolean;
|
||||
repoInfo: RepoInfo;
|
||||
remoteUrls: Dictionary<string>;
|
||||
}
|
||||
|
||||
export default async function connect(
|
||||
client: Client,
|
||||
argv: any,
|
||||
@@ -25,8 +58,9 @@ export default async function connect(
|
||||
) {
|
||||
const { output } = client;
|
||||
const confirm = Boolean(argv['--yes']);
|
||||
const repoArg = argv._[1];
|
||||
|
||||
if (args.length !== 0) {
|
||||
if (args.length > 1) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
`${getCommandName('project connect')}`
|
||||
@@ -36,7 +70,7 @@ export default async function connect(
|
||||
}
|
||||
if (!project || !org) {
|
||||
output.error(
|
||||
`Can't find \`org\` or \`project\`. Make sure your current directory is linked to a Vercel projet by running ${getCommandName(
|
||||
`Can't find \`org\` or \`project\`. Make sure your current directory is linked to a Vercel project by running ${getCommandName(
|
||||
'link'
|
||||
)}.`
|
||||
);
|
||||
@@ -57,9 +91,38 @@ export default async function connect(
|
||||
// get project from .git
|
||||
const gitConfigPath = join(path, '.git/config');
|
||||
const gitConfig = await parseGitConfig(gitConfigPath, output);
|
||||
|
||||
if (repoArg) {
|
||||
// parse repo arg
|
||||
const parsedUrlArg = parseRepoUrl(repoArg);
|
||||
if (!parsedUrlArg) {
|
||||
output.error(
|
||||
`Failed to parse URL "${repoArg}". Please ensure the URL is valid.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
if (gitConfig) {
|
||||
return await connectArgWithLocalGit({
|
||||
client,
|
||||
org,
|
||||
project,
|
||||
confirm,
|
||||
gitConfig,
|
||||
repoInfo: parsedUrlArg,
|
||||
});
|
||||
}
|
||||
return await connectArg({
|
||||
client,
|
||||
confirm,
|
||||
org,
|
||||
project,
|
||||
repoInfo: repoArg,
|
||||
});
|
||||
}
|
||||
|
||||
if (!gitConfig) {
|
||||
output.error(
|
||||
`No local git repo found. Run ${chalk.cyan(
|
||||
`No local Git repository found. Run ${chalk.cyan(
|
||||
'`git clone <url>`'
|
||||
)} to clone a remote Git repository first.`
|
||||
);
|
||||
@@ -78,7 +141,7 @@ export default async function connect(
|
||||
let remoteUrl: string;
|
||||
|
||||
if (Object.keys(remoteUrls).length > 1) {
|
||||
output.log(`Found multiple remote URLs.`);
|
||||
output.log('Found multiple remote URLs.');
|
||||
remoteUrl = await selectRemoteUrl(client, remoteUrls);
|
||||
} else {
|
||||
// If only one is found, get it — usually "origin"
|
||||
@@ -92,8 +155,8 @@ export default async function connect(
|
||||
|
||||
output.log(`Connecting Git remote: ${link(remoteUrl)}`);
|
||||
|
||||
const parsedUrl = parseRepoUrl(remoteUrl);
|
||||
if (!parsedUrl) {
|
||||
const repoInfo = parseRepoUrl(remoteUrl);
|
||||
if (!repoInfo) {
|
||||
output.error(
|
||||
`Failed to parse Git repo data from the following remote URL: ${link(
|
||||
remoteUrl
|
||||
@@ -101,10 +164,176 @@ export default async function connect(
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
const { provider, org: gitOrg, repo } = parsedUrl;
|
||||
const { provider, org: gitOrg, repo } = repoInfo;
|
||||
const repoPath = `${gitOrg}/${repo}`;
|
||||
let connectedRepoPath;
|
||||
|
||||
const checkAndConnect = await checkExistsAndConnect({
|
||||
client,
|
||||
confirm,
|
||||
org,
|
||||
project,
|
||||
gitProviderLink,
|
||||
provider,
|
||||
repoPath,
|
||||
gitOrg,
|
||||
repo,
|
||||
});
|
||||
if (typeof checkAndConnect === 'number') {
|
||||
return checkAndConnect;
|
||||
}
|
||||
|
||||
output.log(
|
||||
`Connected ${formatProvider(provider)} repository ${chalk.cyan(repoPath)}!`
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function connectArg({
|
||||
client,
|
||||
confirm,
|
||||
org,
|
||||
project,
|
||||
repoInfo,
|
||||
}: ConnectArgParams) {
|
||||
const { url: repoUrl } = repoInfo;
|
||||
client.output.log(`Connecting Git remote: ${link(repoUrl)}`);
|
||||
const parsedRepoArg = parseRepoUrl(repoUrl);
|
||||
if (!parsedRepoArg) {
|
||||
client.output.error(
|
||||
`Failed to parse URL "${repoUrl}". Please ensure the URL is valid.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
const { provider, org: gitOrg, repo } = parsedRepoArg;
|
||||
const repoPath = `${gitOrg}/${repo}`;
|
||||
const connect = await checkExistsAndConnect({
|
||||
client,
|
||||
confirm,
|
||||
org,
|
||||
project,
|
||||
gitProviderLink: project.link,
|
||||
provider,
|
||||
repoPath,
|
||||
gitOrg,
|
||||
repo,
|
||||
});
|
||||
if (typeof connect === 'number') {
|
||||
return connect;
|
||||
}
|
||||
client.output.log(
|
||||
`Connected ${formatProvider(provider)} repository ${chalk.cyan(repoPath)}!`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function connectArgWithLocalGit({
|
||||
client,
|
||||
org,
|
||||
project,
|
||||
confirm,
|
||||
gitConfig,
|
||||
repoInfo,
|
||||
}: ConnectGitArgParams) {
|
||||
const remoteUrls = pluckRemoteUrls(gitConfig);
|
||||
if (remoteUrls) {
|
||||
const shouldConnect = await promptConnectArg({
|
||||
client,
|
||||
yes: confirm,
|
||||
repoInfo,
|
||||
remoteUrls,
|
||||
});
|
||||
if (!shouldConnect) {
|
||||
return 1;
|
||||
}
|
||||
if (shouldConnect) {
|
||||
const { provider, org: gitOrg, repo, url: repoUrl } = repoInfo;
|
||||
const repoPath = `${gitOrg}/${repo}`;
|
||||
client.output.log(`Connecting Git remote: ${link(repoUrl)}`);
|
||||
const connect = await checkExistsAndConnect({
|
||||
client,
|
||||
confirm,
|
||||
org,
|
||||
project,
|
||||
gitProviderLink: project.link,
|
||||
provider,
|
||||
repoPath,
|
||||
gitOrg,
|
||||
repo,
|
||||
});
|
||||
if (typeof connect === 'number') {
|
||||
return connect;
|
||||
}
|
||||
client.output.log(
|
||||
`Connected ${formatProvider(provider)} repository ${chalk.cyan(
|
||||
repoPath
|
||||
)}!`
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return await connectArg({ client, confirm, org, project, repoInfo });
|
||||
}
|
||||
|
||||
async function promptConnectArg({
|
||||
client,
|
||||
yes,
|
||||
repoInfo: repoInfoFromArg,
|
||||
remoteUrls,
|
||||
}: PromptConnectArgParams) {
|
||||
if (Object.keys(remoteUrls).length > 1) {
|
||||
client.output.log(
|
||||
'Found multiple Git repositories in your local Git config:'
|
||||
);
|
||||
printRemoteUrls(client.output, remoteUrls);
|
||||
} else {
|
||||
const url = Object.values(remoteUrls)[0];
|
||||
const repoInfoFromGitConfig = parseRepoUrl(url);
|
||||
if (!repoInfoFromGitConfig) {
|
||||
client.output.error(
|
||||
`Failed to parse URL "${url}". Please ensure the URL is valid.`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
JSON.stringify(repoInfoFromGitConfig) === JSON.stringify(repoInfoFromArg)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
client.output.log(
|
||||
`Found a repository in your local Git Config: ${chalk.cyan(
|
||||
Object.values(remoteUrls)[0]
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
let shouldConnect = yes;
|
||||
if (!shouldConnect) {
|
||||
const { url: repoUrlFromArg } = repoInfoFromArg;
|
||||
shouldConnect = await confirm(
|
||||
client,
|
||||
`Do you still want to connect ${link(repoUrlFromArg)}?`,
|
||||
false
|
||||
);
|
||||
if (!shouldConnect) {
|
||||
client.output.log('Aborted. Repo not connected.');
|
||||
}
|
||||
}
|
||||
return shouldConnect;
|
||||
}
|
||||
|
||||
async function checkExistsAndConnect({
|
||||
client,
|
||||
confirm,
|
||||
org,
|
||||
project,
|
||||
gitProviderLink,
|
||||
provider,
|
||||
repoPath,
|
||||
gitOrg,
|
||||
repo,
|
||||
}: GitRepoCheckParams) {
|
||||
if (!gitProviderLink) {
|
||||
const connect = await connectGitProvider(
|
||||
client,
|
||||
@@ -120,14 +349,14 @@ export default async function connect(
|
||||
const connectedProvider = gitProviderLink.type;
|
||||
const connectedOrg = gitProviderLink.org;
|
||||
const connectedRepo = gitProviderLink.repo;
|
||||
connectedRepoPath = `${connectedOrg}/${connectedRepo}`;
|
||||
const connectedRepoPath = `${connectedOrg}/${connectedRepo}`;
|
||||
|
||||
const isSameRepo =
|
||||
connectedProvider === provider &&
|
||||
connectedOrg === gitOrg &&
|
||||
connectedRepo === repo;
|
||||
if (isSameRepo) {
|
||||
output.log(
|
||||
client.output.log(
|
||||
`${chalk.cyan(connectedRepoPath)} is already connected to your project.`
|
||||
);
|
||||
return 1;
|
||||
@@ -135,8 +364,8 @@ export default async function connect(
|
||||
|
||||
const shouldReplaceRepo = await confirmRepoConnect(
|
||||
client,
|
||||
output,
|
||||
confirm,
|
||||
connectedProvider,
|
||||
connectedRepoPath
|
||||
);
|
||||
if (!shouldReplaceRepo) {
|
||||
@@ -155,31 +384,27 @@ export default async function connect(
|
||||
return connect;
|
||||
}
|
||||
}
|
||||
|
||||
output.log(
|
||||
`Connected ${formatProvider(provider)} repository ${chalk.cyan(repoPath)}!`
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function confirmRepoConnect(
|
||||
client: Client,
|
||||
output: Output,
|
||||
yes: boolean,
|
||||
connectedProvider: string,
|
||||
connectedRepoPath: string
|
||||
) {
|
||||
let shouldReplaceProject = yes;
|
||||
if (!shouldReplaceProject) {
|
||||
shouldReplaceProject = await confirm(
|
||||
client,
|
||||
`Looks like you already have a repository connected: ${chalk.cyan(
|
||||
`Looks like you already have a ${formatProvider(
|
||||
connectedProvider
|
||||
)} repository connected: ${chalk.cyan(
|
||||
connectedRepoPath
|
||||
)}. Do you want to replace it?`,
|
||||
true
|
||||
);
|
||||
if (!shouldReplaceProject) {
|
||||
output.log(`Aborted. Repo not connected.`);
|
||||
client.output.log('Aborted. Repo not connected.');
|
||||
}
|
||||
}
|
||||
return shouldReplaceProject;
|
||||
@@ -187,7 +412,7 @@ async function confirmRepoConnect(
|
||||
|
||||
async function selectRemoteUrl(
|
||||
client: Client,
|
||||
remoteUrls: { [key: string]: string }
|
||||
remoteUrls: Dictionary<string>
|
||||
): Promise<string> {
|
||||
let choices: ListChoice[] = [];
|
||||
for (const [urlKey, urlValue] of Object.entries(remoteUrls)) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Org, Project } from '../../types';
|
||||
import Client from '../../util/client';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { disconnectGitProvider } from '../../util/projects/connect-git-provider';
|
||||
import { disconnectGitProvider } from '../../util/git/connect-git-provider';
|
||||
|
||||
export default async function disconnect(
|
||||
client: Client,
|
||||
|
||||
@@ -16,7 +16,7 @@ const help = () => {
|
||||
|
||||
${chalk.dim('Commands:')}
|
||||
|
||||
connect Connect your Git config "origin" remote as a Git provider to your project
|
||||
connect [url] Connect your Vercel Project to your Git repository or provide the remote URL to your Git repository
|
||||
disconnect Disconnect the Git provider repository from your project
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
@@ -29,9 +29,17 @@ const help = () => {
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Connect a Git provider repository
|
||||
${chalk.gray(
|
||||
'–'
|
||||
)} Connect your Vercel Project to your Git repository defined in your local .git config
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} git connect`)}
|
||||
|
||||
${chalk.gray('–')} Connect your Vercel Project to a Git repository using the remote URL
|
||||
|
||||
${chalk.cyan(
|
||||
`$ ${getPkgName()} git connect https://github.com/user/repo.git`
|
||||
)}
|
||||
|
||||
${chalk.gray('–')} Disconnect the Git provider repository
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
export default new Map([
|
||||
['alias', 'alias'],
|
||||
['aliases', 'alias'],
|
||||
['billing', 'billing'],
|
||||
['bisect', 'bisect'],
|
||||
['build', 'build'],
|
||||
['cc', 'billing'],
|
||||
['cert', 'certs'],
|
||||
['certs', 'certs'],
|
||||
['deploy', 'deploy'],
|
||||
@@ -36,6 +34,5 @@ export default new Map([
|
||||
['switch', 'teams'],
|
||||
['team', 'teams'],
|
||||
['teams', 'teams'],
|
||||
['update', 'update'],
|
||||
['whoami', 'whoami'],
|
||||
]);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import table from 'text-table';
|
||||
import title from 'title';
|
||||
import Now from '../util';
|
||||
import getArgs from '../util/get-args';
|
||||
import { handleError } from '../util/error';
|
||||
import cmd from '../util/output/cmd';
|
||||
import logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed';
|
||||
import strlen from '../util/strlen';
|
||||
@@ -14,12 +14,13 @@ import { isValidName } from '../util/is-valid-name';
|
||||
import getCommandFlags from '../util/get-command-flags';
|
||||
import { getPkgName, getCommandName } from '../util/pkg-name';
|
||||
import Client from '../util/client';
|
||||
import { Deployment } from '../types';
|
||||
import { Deployment } from '@vercel/client';
|
||||
import validatePaths from '../util/validate-paths';
|
||||
import { getLinkedProject } from '../util/projects/link';
|
||||
import { ensureLink } from '../util/ensure-link';
|
||||
import getScope from '../util/get-scope';
|
||||
import { isAPIError } from '../util/errors-ts';
|
||||
import { isErrnoException } from '../util/is-error';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -43,6 +44,7 @@ const help = () => {
|
||||
-m, --meta Filter deployments by metadata (e.g.: ${chalk.dim(
|
||||
'`-m KEY=value`'
|
||||
)}). Can appear many times.
|
||||
--prod Filter for production URLs
|
||||
-N, --next Show next page of results
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
@@ -78,6 +80,7 @@ export default async function main(client: Client) {
|
||||
'-m': '--meta',
|
||||
'--next': Number,
|
||||
'-N': '--next',
|
||||
'--prod': Boolean,
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
|
||||
@@ -110,9 +113,9 @@ export default async function main(client: Client) {
|
||||
}
|
||||
|
||||
const yes = !!argv['--yes'];
|
||||
const prod = argv['--prod'] || false;
|
||||
|
||||
const meta = parseMeta(argv['--meta']);
|
||||
const { includeScheme } = config;
|
||||
|
||||
let paths = [process.cwd()];
|
||||
const pathValidation = await validatePaths(client, paths);
|
||||
@@ -146,11 +149,25 @@ export default async function main(client: Client) {
|
||||
if (typeof linkedProject === 'number') {
|
||||
return linkedProject;
|
||||
}
|
||||
link.org = linkedProject.org;
|
||||
link.project = linkedProject.project;
|
||||
org = linkedProject.org;
|
||||
project = linkedProject.project;
|
||||
app = project.name;
|
||||
}
|
||||
|
||||
let { contextName, team } = await getScope(client);
|
||||
let contextName;
|
||||
let team;
|
||||
|
||||
try {
|
||||
({ contextName, team } = await getScope(client));
|
||||
} catch (err: unknown) {
|
||||
if (
|
||||
isErrnoException(err) &&
|
||||
(err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED')
|
||||
) {
|
||||
error(err.message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If user passed in a custom scope, update the current team & context name
|
||||
if (argv['--scope']) {
|
||||
@@ -208,11 +225,15 @@ export default async function main(client: Client) {
|
||||
|
||||
debug('Fetching deployments');
|
||||
|
||||
const response = await now.list(app, {
|
||||
version: 6,
|
||||
meta,
|
||||
nextTimestamp,
|
||||
});
|
||||
const response = await now.list(
|
||||
app,
|
||||
{
|
||||
version: 6,
|
||||
meta,
|
||||
nextTimestamp,
|
||||
},
|
||||
prod
|
||||
);
|
||||
|
||||
let {
|
||||
deployments,
|
||||
@@ -222,6 +243,14 @@ export default async function main(client: Client) {
|
||||
pagination: { count: number; next: number };
|
||||
} = response;
|
||||
|
||||
let showUsername = false;
|
||||
for (const deployment of deployments) {
|
||||
const username = deployment.creator?.username;
|
||||
if (username !== contextName) {
|
||||
showUsername = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (app && !deployments.length) {
|
||||
debug(
|
||||
'No deployments: attempting to find deployment that matches supplied app name'
|
||||
@@ -257,37 +286,36 @@ export default async function main(client: Client) {
|
||||
}
|
||||
|
||||
log(
|
||||
`Deployments under ${chalk.bold(contextName)} ${elapsed(
|
||||
Date.now() - start
|
||||
)}`
|
||||
`${prod ? `Production deployments` : `Deployments`} for ${chalk.bold(
|
||||
app
|
||||
)} under ${chalk.bold(contextName)} ${elapsed(Date.now() - start)}`
|
||||
);
|
||||
|
||||
// information to help the user find other deployments or instances
|
||||
if (app == null) {
|
||||
log(
|
||||
`To list more deployments for a project run ${cmd(
|
||||
`${getCommandName('ls [project]')}`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
log(
|
||||
`To list more deployments for a project, run ${getCommandName(
|
||||
'ls [project]'
|
||||
)}.`
|
||||
);
|
||||
|
||||
print('\n');
|
||||
|
||||
const headers = ['Age', 'Deployment', 'Status', 'Duration'];
|
||||
if (showUsername) headers.push('Username');
|
||||
|
||||
client.output.print(
|
||||
`${table(
|
||||
[
|
||||
['project', 'latest deployment', 'state', 'age', 'username'].map(
|
||||
header => chalk.dim(header)
|
||||
),
|
||||
headers.map(header => chalk.bold(chalk.cyan(header))),
|
||||
...deployments
|
||||
.sort(sortRecent())
|
||||
.map(dep => [
|
||||
[
|
||||
getProjectName(dep),
|
||||
chalk.bold((includeScheme ? 'https://' : '') + dep.url),
|
||||
stateString(dep.state),
|
||||
chalk.gray(ms(Date.now() - dep.createdAt)),
|
||||
dep.creator.username,
|
||||
`https://${dep.url}`,
|
||||
stateString(dep.state || ''),
|
||||
chalk.gray(getDeploymentDuration(dep)),
|
||||
showUsername ? chalk.gray(dep.creator?.username) : '',
|
||||
],
|
||||
])
|
||||
// flatten since the previous step returns a nested
|
||||
@@ -300,8 +328,8 @@ export default async function main(client: Client) {
|
||||
),
|
||||
],
|
||||
{
|
||||
align: ['l', 'l', 'r', 'l', 'l'],
|
||||
hsep: ' '.repeat(4),
|
||||
align: ['l', 'l', 'l', 'l', 'l'],
|
||||
hsep: ' '.repeat(5),
|
||||
stringLength: strlen,
|
||||
}
|
||||
).replace(/^/gm, ' ')}\n\n`
|
||||
@@ -317,27 +345,36 @@ export default async function main(client: Client) {
|
||||
}
|
||||
}
|
||||
|
||||
function getProjectName(d: Deployment) {
|
||||
// We group both file and files into a single project
|
||||
if (d.name === 'file') {
|
||||
return 'files';
|
||||
export function getDeploymentDuration(dep: Deployment): string {
|
||||
if (!dep || !dep.ready || !dep.buildingAt) {
|
||||
return '?';
|
||||
}
|
||||
|
||||
return d.name;
|
||||
const duration = ms(dep.ready - dep.buildingAt);
|
||||
if (duration === '0ms') {
|
||||
return '--';
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
// renders the state string
|
||||
export function stateString(s: string) {
|
||||
const CIRCLE = '● ';
|
||||
// make `s` title case
|
||||
const sTitle = title(s);
|
||||
switch (s) {
|
||||
case 'INITIALIZING':
|
||||
return chalk.yellow(s);
|
||||
|
||||
case 'BUILDING':
|
||||
case 'DEPLOYING':
|
||||
case 'ANALYZING':
|
||||
return chalk.yellow(CIRCLE) + sTitle;
|
||||
case 'ERROR':
|
||||
return chalk.red(s);
|
||||
|
||||
return chalk.red(CIRCLE) + sTitle;
|
||||
case 'READY':
|
||||
return s;
|
||||
|
||||
return chalk.green(CIRCLE) + sTitle;
|
||||
case 'QUEUED':
|
||||
return chalk.white(CIRCLE) + sTitle;
|
||||
case 'CANCELED':
|
||||
return chalk.gray(sTitle);
|
||||
default:
|
||||
return chalk.gray('UNKNOWN');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import table from 'text-table';
|
||||
import { Project } from '../../types';
|
||||
import Client from '../../util/client';
|
||||
import getCommandFlags from '../../util/get-command-flags';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
@@ -34,10 +35,10 @@ export default async function list(
|
||||
}
|
||||
|
||||
const {
|
||||
projects: list,
|
||||
projects: projectList,
|
||||
pagination,
|
||||
}: {
|
||||
projects: [{ name: string; updatedAt: number }];
|
||||
projects: Project[];
|
||||
pagination: { count: number; next: number };
|
||||
} = await client.fetch(projectsUrl, {
|
||||
method: 'GET',
|
||||
@@ -48,39 +49,48 @@ export default async function list(
|
||||
const elapsed = ms(Date.now() - start);
|
||||
|
||||
output.log(
|
||||
`${list.length > 0 ? 'Projects' : 'No projects'} found under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
`${
|
||||
projectList.length > 0 ? 'Projects' : 'No projects'
|
||||
} found under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
|
||||
if (list.length > 0) {
|
||||
const cur = Date.now();
|
||||
const header = [['', 'name', 'updated'].map(title => chalk.dim(title))];
|
||||
|
||||
const out = table(
|
||||
header.concat(
|
||||
list.map(secret => [
|
||||
'',
|
||||
chalk.bold(secret.name),
|
||||
chalk.gray(`${ms(cur - secret.updatedAt)} ago`),
|
||||
])
|
||||
),
|
||||
if (projectList.length > 0) {
|
||||
const tablePrint = table(
|
||||
[
|
||||
['Project Name', 'Latest Production URL', 'Updated'].map(header =>
|
||||
chalk.bold(chalk.cyan(header))
|
||||
),
|
||||
...projectList
|
||||
.map(project => [
|
||||
[
|
||||
chalk.bold(project.name),
|
||||
getLatestProdUrl(project),
|
||||
chalk.gray(ms(Date.now() - project.updatedAt)),
|
||||
],
|
||||
])
|
||||
.flat(),
|
||||
],
|
||||
{
|
||||
align: ['l', 'l', 'l'],
|
||||
hsep: ' '.repeat(2),
|
||||
hsep: ' '.repeat(3),
|
||||
stringLength: strlen,
|
||||
}
|
||||
);
|
||||
|
||||
if (out) {
|
||||
output.print(`\n${out}\n\n`);
|
||||
}
|
||||
).replace(/^/gm, ' ');
|
||||
output.print(`\n${tablePrint}\n\n`);
|
||||
|
||||
if (pagination && pagination.count === 20) {
|
||||
const flags = getCommandFlags(argv, ['_', '--next', '-N', '-d', '-y']);
|
||||
const nextCmd = `project ls${flags} --next ${pagination.next}`;
|
||||
output.log(`To display the next page run ${getCommandName(nextCmd)}`);
|
||||
output.log(`To display the next page, run ${getCommandName(nextCmd)}`);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getLatestProdUrl(project: Project): string {
|
||||
const alias =
|
||||
project.alias?.filter(al => al.deployment)?.[0]?.domain ||
|
||||
project.alias?.[0]?.domain;
|
||||
if (alias) return 'https://' + alias;
|
||||
return '--';
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import cmd from '../util/output/cmd';
|
||||
import logo from '../util/output/logo';
|
||||
import handleError from '../util/handle-error';
|
||||
import getArgs from '../util/get-args';
|
||||
import Client from '../util/client';
|
||||
import getUpdateCommand from '../util/get-update-command';
|
||||
import { getPkgName, getTitleName } from '../util/pkg-name';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${getPkgName()} update`)} [options]
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
-d, --debug Debug mode [off]
|
||||
-c ${chalk.bold.underline('NAME')}, --channel=${chalk.bold.underline(
|
||||
'NAME'
|
||||
)} Specify which release channel to install [stable]
|
||||
-r ${chalk.bold.underline('VERSION')}, --release=${chalk.bold.underline(
|
||||
'VERSION'
|
||||
)} Specfic version to install (overrides \`--channel\`)
|
||||
-y, --yes Skip the confirmation prompt
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Update ${getTitleName()} CLI to the latest "canary" version
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} update --channel=canary`)}
|
||||
`);
|
||||
};
|
||||
|
||||
export default async function main(client: Client): Promise<number> {
|
||||
let argv;
|
||||
const { output } = client;
|
||||
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--channel': String,
|
||||
'-c': '--channel',
|
||||
'--release': String,
|
||||
'-V': '--release',
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
});
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
return 2;
|
||||
}
|
||||
|
||||
output.log(
|
||||
`Please run ${cmd(
|
||||
await getUpdateCommand()
|
||||
)} to update ${getTitleName()} CLI`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
@@ -323,14 +323,7 @@ const main = async () => {
|
||||
client.argv.push('-h');
|
||||
}
|
||||
|
||||
const subcommandsWithoutToken = [
|
||||
'login',
|
||||
'logout',
|
||||
'help',
|
||||
'init',
|
||||
'update',
|
||||
'build',
|
||||
];
|
||||
const subcommandsWithoutToken = ['login', 'logout', 'help', 'init', 'build'];
|
||||
|
||||
// Prompt for login if there is no current token
|
||||
if (
|
||||
@@ -505,9 +498,6 @@ const main = async () => {
|
||||
case 'alias':
|
||||
func = require('./commands/alias').default;
|
||||
break;
|
||||
case 'billing':
|
||||
func = require('./commands/billing').default;
|
||||
break;
|
||||
case 'bisect':
|
||||
func = require('./commands/bisect').default;
|
||||
break;
|
||||
@@ -571,9 +561,6 @@ const main = async () => {
|
||||
case 'teams':
|
||||
func = require('./commands/teams').default;
|
||||
break;
|
||||
case 'update':
|
||||
func = require('./commands/update').default;
|
||||
break;
|
||||
case 'whoami':
|
||||
func = require('./commands/whoami').default;
|
||||
break;
|
||||
|
||||
@@ -30,7 +30,6 @@ export interface GlobalConfig {
|
||||
'// Note'?: string;
|
||||
'// Docs'?: string;
|
||||
currentTeam?: string;
|
||||
includeScheme?: string;
|
||||
collectMetrics?: boolean;
|
||||
api?: string;
|
||||
|
||||
@@ -142,6 +141,7 @@ export type Deployment = {
|
||||
meta: {
|
||||
[key: string]: any;
|
||||
};
|
||||
alias?: string[];
|
||||
};
|
||||
|
||||
export type Alias = {
|
||||
@@ -211,6 +211,7 @@ export interface ProjectAliasTarget {
|
||||
configuredBy?: null | 'CNAME' | 'A';
|
||||
configuredChangedAt?: null | number;
|
||||
configuredChangeAttempts?: [number, number];
|
||||
deployment?: Deployment | undefined;
|
||||
}
|
||||
|
||||
export interface Secret {
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"VISA": "Visa",
|
||||
"MASTERCARD": "MasterCard",
|
||||
"AMERICANEXPRESS": "American Express",
|
||||
"DINERSCLUB": "Diners Club",
|
||||
"DISCOVER": "Discover",
|
||||
"JCB": "JCB"
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import mapCertError from '../certs/map-cert-error';
|
||||
import { Org } from '../../types';
|
||||
import Now, { CreateOptions } from '..';
|
||||
import Client from '../client';
|
||||
import { DeploymentError } from '../../../../client/dist';
|
||||
import { ArchiveFormat, DeploymentError } from '@vercel/client';
|
||||
|
||||
export default async function createDeploy(
|
||||
client: Client,
|
||||
@@ -16,10 +16,18 @@ export default async function createDeploy(
|
||||
createArgs: CreateOptions,
|
||||
org: Org,
|
||||
isSettingUpProject: boolean,
|
||||
cwd?: string
|
||||
cwd?: string,
|
||||
archive?: ArchiveFormat
|
||||
): Promise<any | DeploymentError> {
|
||||
try {
|
||||
return await now.create(paths, createArgs, org, isSettingUpProject, cwd);
|
||||
return await now.create(
|
||||
paths,
|
||||
createArgs,
|
||||
org,
|
||||
isSettingUpProject,
|
||||
archive,
|
||||
cwd
|
||||
);
|
||||
} catch (err: unknown) {
|
||||
if (ERRORS_TS.isAPIError(err)) {
|
||||
if (err.code === 'rate_limited') {
|
||||
|
||||
@@ -2,12 +2,12 @@ import bytes from 'bytes';
|
||||
import Progress from 'progress';
|
||||
import chalk from 'chalk';
|
||||
import {
|
||||
ArchiveFormat,
|
||||
createDeployment,
|
||||
DeploymentOptions,
|
||||
VercelClientOptions,
|
||||
} from '@vercel/client';
|
||||
import { Output } from '../output';
|
||||
// @ts-ignore
|
||||
import Now from '../../util';
|
||||
import { Org } from '../../types';
|
||||
import ua from '../ua';
|
||||
@@ -32,6 +32,7 @@ export default async function processDeployment({
|
||||
cwd,
|
||||
projectName,
|
||||
isSettingUpProject,
|
||||
archive,
|
||||
skipAutoDetectionConfirmation,
|
||||
...args
|
||||
}: {
|
||||
@@ -48,6 +49,7 @@ export default async function processDeployment({
|
||||
prebuilt: boolean;
|
||||
projectName: string;
|
||||
isSettingUpProject: boolean;
|
||||
archive?: ArchiveFormat;
|
||||
skipAutoDetectionConfirmation?: boolean;
|
||||
cwd?: string;
|
||||
rootDirectory?: string;
|
||||
@@ -87,14 +89,13 @@ export default async function processDeployment({
|
||||
prebuilt,
|
||||
rootDirectory,
|
||||
skipAutoDetectionConfirmation,
|
||||
archive,
|
||||
};
|
||||
|
||||
output.spinner(
|
||||
isSettingUpProject
|
||||
? 'Setting up project'
|
||||
: `Deploying ${chalk.bold(`${org.slug}/${projectName}`)}`,
|
||||
0
|
||||
);
|
||||
const deployingSpinnerVal = isSettingUpProject
|
||||
? 'Setting up project'
|
||||
: `Deploying ${chalk.bold(`${org.slug}/${projectName}`)}`;
|
||||
output.spinner(deployingSpinnerVal, 0);
|
||||
|
||||
// collect indications to show the user once
|
||||
// the deployment is done
|
||||
@@ -107,12 +108,11 @@ export default async function processDeployment({
|
||||
}
|
||||
|
||||
if (event.type === 'file-count') {
|
||||
debug(
|
||||
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
|
||||
);
|
||||
const { total, missing, uploads } = event.payload;
|
||||
debug(`Total files ${total.size}, ${missing.length} changed`);
|
||||
|
||||
const missingSize = event.payload.missing
|
||||
.map((sha: string) => event.payload.total.get(sha).data.length)
|
||||
const missingSize = missing
|
||||
.map((sha: string) => total.get(sha).data.length)
|
||||
.reduce((a: number, b: number) => a + b, 0);
|
||||
|
||||
output.stopSpinner();
|
||||
@@ -123,6 +123,26 @@ export default async function processDeployment({
|
||||
total: missingSize,
|
||||
clear: true,
|
||||
});
|
||||
|
||||
bar.tick(0);
|
||||
|
||||
uploads.forEach((e: any) =>
|
||||
e.on('progress', () => {
|
||||
if (!bar) return;
|
||||
|
||||
const totalBytesUploaded = uploads.reduce((acc: number, e: any) => {
|
||||
return acc + e.bytesUploaded;
|
||||
}, 0);
|
||||
// set the current progress bar value
|
||||
bar.curr = totalBytesUploaded;
|
||||
// trigger rendering
|
||||
bar.tick(0);
|
||||
|
||||
if (bar.complete) {
|
||||
output.spinner(deployingSpinnerVal, 0);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (event.type === 'file-uploaded') {
|
||||
@@ -131,10 +151,6 @@ export default async function processDeployment({
|
||||
event.payload.file.data.length
|
||||
)})`
|
||||
);
|
||||
|
||||
if (bar) {
|
||||
bar.tick(event.payload.file.data.length);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === 'created') {
|
||||
|
||||
7
packages/cli/src/util/deploy/validate-archive-format.ts
Normal file
7
packages/cli/src/util/deploy/validate-archive-format.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { ArchiveFormat, VALID_ARCHIVE_FORMATS } from '@vercel/client';
|
||||
|
||||
const validArchiveFormats = new Set<string>(VALID_ARCHIVE_FORMATS);
|
||||
|
||||
export function isValidArchive(archive: string): archive is ArchiveFormat {
|
||||
return validArchiveFormats.has(archive);
|
||||
}
|
||||
59
packages/cli/src/util/dev/parse-query-string.ts
Normal file
59
packages/cli/src/util/dev/parse-query-string.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* This function is necessary to account for the difference between
|
||||
* `?a=` and `?a` because native `url.parse(str, true)` can't tell.
|
||||
* @param querystring - The querystring to parse, also known as the "search" string.
|
||||
*/
|
||||
export function parseQueryString(
|
||||
querystring?: string
|
||||
): Record<string, string[]> {
|
||||
const query: Record<string, string[]> = Object.create(null);
|
||||
if (!querystring || !querystring.startsWith('?') || querystring === '?') {
|
||||
return query;
|
||||
}
|
||||
const params = querystring.slice(1).split('&');
|
||||
for (let param of params) {
|
||||
let [key, value] = param.split('=');
|
||||
if (key !== undefined) {
|
||||
key = decodeURIComponent(key);
|
||||
}
|
||||
if (value !== undefined) {
|
||||
value = decodeURIComponent(value);
|
||||
}
|
||||
|
||||
let existing = query[key];
|
||||
if (!existing) {
|
||||
existing = [];
|
||||
query[key] = existing;
|
||||
}
|
||||
|
||||
existing.push(value);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is necessary to account for the difference between
|
||||
* `?a=` and `?a` because native `url.format({ query })` can't tell.
|
||||
* @param query - The query object to stringify.
|
||||
*/
|
||||
export function formatQueryString(
|
||||
query: Record<string, string[]> | undefined
|
||||
): string | undefined {
|
||||
if (!query) {
|
||||
return undefined;
|
||||
}
|
||||
let s = '';
|
||||
let prefix = '?';
|
||||
for (let [key, values] of Object.entries(query)) {
|
||||
for (let value of values) {
|
||||
s += prefix;
|
||||
s += encodeURIComponent(key);
|
||||
if (value !== undefined) {
|
||||
s += '=';
|
||||
s += encodeURIComponent(value);
|
||||
}
|
||||
prefix = '&';
|
||||
}
|
||||
}
|
||||
return s || undefined;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import DevServer from './server';
|
||||
|
||||
import { VercelConfig, HttpHeadersConfig, RouteResult } from './types';
|
||||
import { isHandler, Route, HandleValue } from '@vercel/routing-utils';
|
||||
import { parseQueryString } from './parse-query-string';
|
||||
|
||||
export function resolveRouteParameters(
|
||||
str: string,
|
||||
@@ -56,7 +57,8 @@ export async function devRouter(
|
||||
phase?: HandleValue | null
|
||||
): Promise<RouteResult> {
|
||||
let result: RouteResult | undefined;
|
||||
let { query, pathname: reqPathname = '/' } = url.parse(reqUrl, true);
|
||||
let { pathname: reqPathname = '/', search: reqSearch } = url.parse(reqUrl);
|
||||
const reqQuery = parseQueryString(reqSearch);
|
||||
const combinedHeaders: HttpHeadersConfig = { ...previousHeaders };
|
||||
let status: number | undefined;
|
||||
let isContinue = false;
|
||||
@@ -174,7 +176,7 @@ export async function devRouter(
|
||||
isDestUrl,
|
||||
status: routeConfig.status || status,
|
||||
headers: combinedHeaders,
|
||||
uri_args: query,
|
||||
query: reqQuery,
|
||||
matched_route: routeConfig,
|
||||
matched_route_idx: idx,
|
||||
phase,
|
||||
@@ -184,17 +186,19 @@ export async function devRouter(
|
||||
if (!destPath.startsWith('/')) {
|
||||
destPath = `/${destPath}`;
|
||||
}
|
||||
const destParsed = url.parse(destPath, true);
|
||||
Object.assign(destParsed.query, query);
|
||||
const { pathname: destPathname = '/', search: destSearch } =
|
||||
url.parse(destPath);
|
||||
const destQuery = parseQueryString(destSearch);
|
||||
Object.assign(destQuery, reqQuery);
|
||||
result = {
|
||||
found: true,
|
||||
dest: destParsed.pathname || '/',
|
||||
dest: destPathname,
|
||||
continue: isContinue,
|
||||
userDest: Boolean(routeConfig.dest),
|
||||
isDestUrl,
|
||||
status: routeConfig.status || status,
|
||||
headers: combinedHeaders,
|
||||
uri_args: destParsed.query,
|
||||
query: destQuery,
|
||||
matched_route: routeConfig,
|
||||
matched_route_idx: idx,
|
||||
phase,
|
||||
@@ -212,7 +216,7 @@ export async function devRouter(
|
||||
continue: isContinue,
|
||||
status,
|
||||
isDestUrl: false,
|
||||
uri_args: query,
|
||||
query: reqQuery,
|
||||
headers: combinedHeaders,
|
||||
phase,
|
||||
};
|
||||
|
||||
@@ -94,6 +94,7 @@ import { ProjectEnvVariable, ProjectSettings } from '../../types';
|
||||
import exposeSystemEnvs from './expose-system-envs';
|
||||
import { treeKill } from '../tree-kill';
|
||||
import { nodeHeadersToFetchHeaders } from './headers';
|
||||
import { formatQueryString, parseQueryString } from './parse-query-string';
|
||||
import {
|
||||
errorToString,
|
||||
isErrnoException,
|
||||
@@ -1396,9 +1397,11 @@ export default class DevServer {
|
||||
|
||||
const getReqUrl = (rr: RouteResult): string | undefined => {
|
||||
if (rr.dest) {
|
||||
if (rr.uri_args) {
|
||||
const destParsed = url.parse(rr.dest, true);
|
||||
Object.assign(destParsed.query, rr.uri_args);
|
||||
if (rr.query) {
|
||||
const destParsed = url.parse(rr.dest);
|
||||
const destQuery = parseQueryString(destParsed.search);
|
||||
Object.assign(destQuery, rr.query);
|
||||
destParsed.search = formatQueryString(destQuery);
|
||||
return url.format(destParsed);
|
||||
}
|
||||
return rr.dest;
|
||||
@@ -1533,9 +1536,8 @@ export default class DevServer {
|
||||
|
||||
// Retain orginal pathname, but override query parameters from the rewrite
|
||||
const beforeRewriteUrl = req.url || '/';
|
||||
const rewriteUrlParsed = url.parse(beforeRewriteUrl, true);
|
||||
delete rewriteUrlParsed.search;
|
||||
rewriteUrlParsed.query = url.parse(rewritePath, true).query;
|
||||
const rewriteUrlParsed = url.parse(beforeRewriteUrl);
|
||||
rewriteUrlParsed.search = url.parse(rewritePath).search;
|
||||
req.url = url.format(rewriteUrlParsed);
|
||||
debug(
|
||||
`Rewrote incoming HTTP URL from "${beforeRewriteUrl}" to "${req.url}"`
|
||||
@@ -1594,9 +1596,10 @@ export default class DevServer {
|
||||
|
||||
if (routeResult.isDestUrl) {
|
||||
// Mix the `routes` result dest query params into the req path
|
||||
const destParsed = url.parse(routeResult.dest, true);
|
||||
delete destParsed.search;
|
||||
Object.assign(destParsed.query, routeResult.uri_args);
|
||||
const destParsed = url.parse(routeResult.dest);
|
||||
const destQuery = parseQueryString(destParsed.search);
|
||||
Object.assign(destQuery, routeResult.query);
|
||||
destParsed.search = formatQueryString(destQuery);
|
||||
const destUrl = url.format(destParsed);
|
||||
|
||||
debug(`ProxyPass: ${destUrl}`);
|
||||
@@ -1737,7 +1740,7 @@ export default class DevServer {
|
||||
throw new Error('Expected Route Result but none was found.');
|
||||
}
|
||||
|
||||
const { dest, headers, uri_args } = routeResult;
|
||||
const { dest, query, headers } = routeResult;
|
||||
|
||||
// Set any headers defined in the matched `route` config
|
||||
for (const [name, value] of Object.entries(headers)) {
|
||||
@@ -1773,10 +1776,11 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
this.setResponseHeaders(res, requestId);
|
||||
const origUrl = url.parse(req.url || '/', true);
|
||||
delete origUrl.search;
|
||||
const origUrl = url.parse(req.url || '/');
|
||||
const origQuery = parseQueryString(origUrl.search);
|
||||
origUrl.pathname = dest;
|
||||
Object.assign(origUrl.query, uri_args);
|
||||
Object.assign(origQuery, query);
|
||||
origUrl.search = formatQueryString(origQuery);
|
||||
req.url = url.format(origUrl);
|
||||
return proxyPass(req, res, upstream, this, requestId, false);
|
||||
}
|
||||
@@ -1798,10 +1802,11 @@ export default class DevServer {
|
||||
Array.isArray(buildResult.routes) &&
|
||||
buildResult.routes.length > 0
|
||||
) {
|
||||
const origUrl = url.parse(req.url || '/', true);
|
||||
delete origUrl.search;
|
||||
const origUrl = url.parse(req.url || '/');
|
||||
const origQuery = parseQueryString(origUrl.search);
|
||||
origUrl.pathname = dest;
|
||||
Object.assign(origUrl.query, uri_args);
|
||||
Object.assign(origQuery, query);
|
||||
origUrl.search = formatQueryString(origQuery);
|
||||
const newUrl = url.format(origUrl);
|
||||
debug(
|
||||
`Checking build result's ${buildResult.routes.length} \`routes\` to match ${newUrl}`
|
||||
@@ -1897,11 +1902,13 @@ export default class DevServer {
|
||||
);
|
||||
|
||||
// Mix in the routing based query parameters
|
||||
const parsed = url.parse(req.url || '/', true);
|
||||
Object.assign(parsed.query, uri_args);
|
||||
const origUrl = url.parse(req.url || '/');
|
||||
const origQuery = parseQueryString(origUrl.search);
|
||||
Object.assign(origQuery, query);
|
||||
origUrl.search = formatQueryString(origQuery);
|
||||
req.url = url.format({
|
||||
pathname: parsed.pathname,
|
||||
query: parsed.query,
|
||||
pathname: origUrl.pathname,
|
||||
search: origUrl.search,
|
||||
});
|
||||
|
||||
// Add the Vercel platform proxy request headers
|
||||
@@ -2017,11 +2024,13 @@ export default class DevServer {
|
||||
requestId = generateRequestId(this.podId, true);
|
||||
|
||||
// Mix the `routes` result dest query params into the req path
|
||||
const parsed = url.parse(req.url || '/', true);
|
||||
Object.assign(parsed.query, uri_args);
|
||||
const origUrl = url.parse(req.url || '/');
|
||||
const origQuery = parseQueryString(origUrl.search);
|
||||
Object.assign(origQuery, query);
|
||||
origUrl.search = formatQueryString(origQuery);
|
||||
const path = url.format({
|
||||
pathname: parsed.pathname,
|
||||
query: parsed.query,
|
||||
pathname: origUrl.pathname,
|
||||
search: origUrl.search,
|
||||
});
|
||||
|
||||
const body = await rawBody(req);
|
||||
|
||||
@@ -145,8 +145,8 @@ export interface RouteResult {
|
||||
status?: number;
|
||||
// "headers": <object of the added response header values>
|
||||
headers: HttpHeadersConfig;
|
||||
// "uri_args": <object (key=value) list of new uri args to be passed along to dest >
|
||||
uri_args?: { [key: string]: any };
|
||||
// "query": <object (key=values) of new uri args to be passed along to dest>
|
||||
query?: Record<string, string[]>;
|
||||
// "matched_route": <object of the route spec that matched>
|
||||
matched_route?: Route;
|
||||
// "matched_route_idx": <integer of the index of the route matched>
|
||||
|
||||
@@ -152,9 +152,7 @@ export class SourceNotFound extends NowError<'SOURCE_NOT_FOUND', {}> {
|
||||
super({
|
||||
code: 'SOURCE_NOT_FOUND',
|
||||
meta: {},
|
||||
message: `Not able to purchase. Please add a payment method using ${getCommandName(
|
||||
`billing add`
|
||||
)}.`,
|
||||
message: `Not able to purchase. Please add a payment method using the dashboard.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,22 @@ import { Org } from '../../types';
|
||||
import chalk from 'chalk';
|
||||
import link from '../output/link';
|
||||
import { isAPIError } from '../errors-ts';
|
||||
import { Output } from '../output';
|
||||
import { Dictionary } from '@vercel/client';
|
||||
|
||||
export interface RepoInfo {
|
||||
url: string;
|
||||
provider: string;
|
||||
org: string;
|
||||
repo: string;
|
||||
}
|
||||
|
||||
export async function disconnectGitProvider(
|
||||
client: Client,
|
||||
org: Org,
|
||||
projectId: string
|
||||
) {
|
||||
const fetchUrl = `/v4/projects/${projectId}/link?${stringify({
|
||||
const fetchUrl = `/v9/projects/${projectId}/link?${stringify({
|
||||
teamId: org.type === 'team' ? org.id : undefined,
|
||||
})}`;
|
||||
return client.fetch(fetchUrl, {
|
||||
@@ -28,7 +37,7 @@ export async function connectGitProvider(
|
||||
type: string,
|
||||
repo: string
|
||||
) {
|
||||
const fetchUrl = `/v4/projects/${projectId}/link?${stringify({
|
||||
const fetchUrl = `/v9/projects/${projectId}/link?${stringify({
|
||||
teamId: org.type === 'team' ? org.id : undefined,
|
||||
})}`;
|
||||
try {
|
||||
@@ -43,22 +52,21 @@ export async function connectGitProvider(
|
||||
}),
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
if (isAPIError(err)) {
|
||||
if (
|
||||
err.meta?.action === 'Install GitHub App' ||
|
||||
err.code === 'repo_not_found'
|
||||
) {
|
||||
client.output.error(
|
||||
`Failed to link ${chalk.cyan(
|
||||
repo
|
||||
)}. Make sure there aren't any typos and that you have access to the repository if it's private.`
|
||||
);
|
||||
} else if (err.action === 'Add a Login Connection') {
|
||||
client.output.error(
|
||||
err.message.replace(repo, chalk.cyan(repo)) +
|
||||
`\nVisit ${link(err.link)} for more information.`
|
||||
);
|
||||
}
|
||||
const apiError = isAPIError(err);
|
||||
if (
|
||||
apiError &&
|
||||
(err.action === 'Install GitHub App' || err.code === 'repo_not_found')
|
||||
) {
|
||||
client.output.error(
|
||||
`Failed to link ${chalk.cyan(
|
||||
repo
|
||||
)}. Make sure there aren't any typos and that you have access to the repository if it's private.`
|
||||
);
|
||||
} else if (apiError && err.action === 'Add a Login Connection') {
|
||||
client.output.error(
|
||||
err.message.replace(repo, chalk.cyan(repo)) +
|
||||
`\nVisit ${link(err.link)} for more information.`
|
||||
);
|
||||
} else {
|
||||
client.output.error(
|
||||
`Failed to connect the ${formatProvider(
|
||||
@@ -83,15 +91,13 @@ export function formatProvider(type: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function parseRepoUrl(originUrl: string): {
|
||||
provider: string;
|
||||
org: string;
|
||||
repo: string;
|
||||
} | null {
|
||||
export function parseRepoUrl(originUrl: string): RepoInfo | null {
|
||||
const isSSH = originUrl.startsWith('git@');
|
||||
// Matches all characters between (// or @) and (.com or .org)
|
||||
// eslint-disable-next-line prefer-named-capture-group
|
||||
const provider = /(?<=(\/\/|@)).*(?=(\.com|\.org))/.exec(originUrl);
|
||||
const provider =
|
||||
/(?<=(\/\/|@)).*(?=(\.com|\.org))/.exec(originUrl)?.[0] ||
|
||||
originUrl.replace('www.', '').split('.')[0];
|
||||
if (!provider) {
|
||||
return null;
|
||||
}
|
||||
@@ -104,8 +110,8 @@ export function parseRepoUrl(originUrl: string): {
|
||||
repo = originUrl.split('/')[1]?.replace('.git', '');
|
||||
} else {
|
||||
// Assume https:// or git://
|
||||
org = originUrl.split('/')[3];
|
||||
repo = originUrl.split('/')[4]?.replace('.git', '');
|
||||
org = originUrl.replace('//', '').split('/')[1];
|
||||
repo = originUrl.replace('//', '').split('/')[2]?.replace('.git', '');
|
||||
}
|
||||
|
||||
if (!org || !repo) {
|
||||
@@ -113,8 +119,17 @@ export function parseRepoUrl(originUrl: string): {
|
||||
}
|
||||
|
||||
return {
|
||||
provider: provider[0],
|
||||
url: originUrl,
|
||||
provider,
|
||||
org,
|
||||
repo,
|
||||
};
|
||||
}
|
||||
export function printRemoteUrls(
|
||||
output: Output,
|
||||
remoteUrls: Dictionary<string>
|
||||
) {
|
||||
for (const [name, url] of Object.entries(remoteUrls)) {
|
||||
output.print(` • ${name}: ${chalk.cyan(url)}\n`);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import printIndications from './print-indications';
|
||||
import { GitMetadata, Org } from '../types';
|
||||
import { VercelConfig } from './dev/types';
|
||||
import Client, { FetchOptions, isJSONObject } from './client';
|
||||
import { Dictionary } from '@vercel/client';
|
||||
import { ArchiveFormat, Dictionary } from '@vercel/client';
|
||||
|
||||
export interface NowOptions {
|
||||
client: Client;
|
||||
@@ -131,6 +131,7 @@ export default class Now extends EventEmitter {
|
||||
}: CreateOptions,
|
||||
org: Org,
|
||||
isSettingUpProject: boolean,
|
||||
archive?: ArchiveFormat,
|
||||
cwd?: string
|
||||
) {
|
||||
let hashes: any = {};
|
||||
@@ -168,6 +169,7 @@ export default class Now extends EventEmitter {
|
||||
org,
|
||||
projectName: name,
|
||||
isSettingUpProject,
|
||||
archive,
|
||||
skipAutoDetectionConfirmation,
|
||||
cwd,
|
||||
prebuilt,
|
||||
@@ -330,7 +332,8 @@ export default class Now extends EventEmitter {
|
||||
|
||||
async list(
|
||||
app?: string,
|
||||
{ version = 4, meta = {}, nextTimestamp }: ListOptions = {}
|
||||
{ version = 4, meta = {}, nextTimestamp }: ListOptions = {},
|
||||
prod?: boolean
|
||||
) {
|
||||
const fetchRetry = async (url: string, options: FetchOptions = {}) => {
|
||||
return this.retry(
|
||||
@@ -393,6 +396,9 @@ export default class Now extends EventEmitter {
|
||||
if (nextTimestamp) {
|
||||
query.set('until', String(nextTimestamp));
|
||||
}
|
||||
if (prod) {
|
||||
query.set('target', 'production');
|
||||
}
|
||||
|
||||
const response = await fetchRetry(`/v${version}/now/deployments?${query}`);
|
||||
return response;
|
||||
|
||||
107
packages/cli/src/util/link/add-git-connection.ts
Normal file
107
packages/cli/src/util/link/add-git-connection.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Dictionary } from '@vercel/client';
|
||||
import { parseRepoUrl } from '../git/connect-git-provider';
|
||||
import Client from '../client';
|
||||
import { Org, Project, ProjectSettings } from '../../types';
|
||||
import { handleOptions } from './handle-options';
|
||||
import {
|
||||
promptGitConnectMultipleUrls,
|
||||
promptGitConnectSingleUrl,
|
||||
} from './git-connect-prompts';
|
||||
|
||||
function getProjectSettings(project: Project): ProjectSettings {
|
||||
return {
|
||||
createdAt: project.createdAt,
|
||||
framework: project.framework,
|
||||
devCommand: project.devCommand,
|
||||
installCommand: project.installCommand,
|
||||
buildCommand: project.buildCommand,
|
||||
outputDirectory: project.outputDirectory,
|
||||
rootDirectory: project.rootDirectory,
|
||||
directoryListing: project.directoryListing,
|
||||
nodeVersion: project.nodeVersion,
|
||||
skipGitConnectDuringLink: project.skipGitConnectDuringLink,
|
||||
};
|
||||
}
|
||||
|
||||
export async function addGitConnection(
|
||||
client: Client,
|
||||
org: Org,
|
||||
project: Project,
|
||||
remoteUrls: Dictionary<string>,
|
||||
settings?: ProjectSettings
|
||||
): Promise<number | void> {
|
||||
if (!settings) {
|
||||
settings = getProjectSettings(project);
|
||||
}
|
||||
if (Object.keys(remoteUrls).length === 1) {
|
||||
return addSingleGitRemote(
|
||||
client,
|
||||
org,
|
||||
project,
|
||||
remoteUrls,
|
||||
settings || project
|
||||
);
|
||||
} else if (Object.keys(remoteUrls).length > 1 && !project.link) {
|
||||
return addMultipleGitRemotes(
|
||||
client,
|
||||
org,
|
||||
project,
|
||||
remoteUrls,
|
||||
settings || project
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function addSingleGitRemote(
|
||||
client: Client,
|
||||
org: Org,
|
||||
project: Project,
|
||||
remoteUrls: Dictionary<string>,
|
||||
settings: ProjectSettings
|
||||
) {
|
||||
const [remoteName, remoteUrl] = Object.entries(remoteUrls)[0];
|
||||
const repoInfo = parseRepoUrl(remoteUrl);
|
||||
if (!repoInfo) {
|
||||
client.output.debug(`Could not parse repo url ${repoInfo}.`);
|
||||
return 1;
|
||||
}
|
||||
const { org: parsedOrg, repo, provider } = repoInfo;
|
||||
const alreadyLinked =
|
||||
project.link &&
|
||||
project.link.org === parsedOrg &&
|
||||
project.link.repo === repo &&
|
||||
project.link.type === provider;
|
||||
if (alreadyLinked) {
|
||||
client.output.debug('Project already linked. Skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
const replace =
|
||||
project.link &&
|
||||
(project.link.org !== parsedOrg ||
|
||||
project.link.repo !== repo ||
|
||||
project.link.type !== provider);
|
||||
const shouldConnect = await promptGitConnectSingleUrl(
|
||||
client,
|
||||
project,
|
||||
remoteName,
|
||||
remoteUrl,
|
||||
replace
|
||||
);
|
||||
return handleOptions(shouldConnect, client, org, project, settings, repoInfo);
|
||||
}
|
||||
|
||||
async function addMultipleGitRemotes(
|
||||
client: Client,
|
||||
org: Org,
|
||||
project: Project,
|
||||
remoteUrls: Dictionary<string>,
|
||||
settings: ProjectSettings
|
||||
) {
|
||||
client.output.log('Found multiple Git remote URLs in Git config.');
|
||||
const remoteUrlOrOptions = await promptGitConnectMultipleUrls(
|
||||
client,
|
||||
remoteUrls
|
||||
);
|
||||
return handleOptions(remoteUrlOrOptions, client, org, project, settings);
|
||||
}
|
||||
86
packages/cli/src/util/link/git-connect-prompts.ts
Normal file
86
packages/cli/src/util/link/git-connect-prompts.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Dictionary } from '@vercel/client';
|
||||
import chalk from 'chalk';
|
||||
import { Project } from '../../types';
|
||||
import Client from '../client';
|
||||
import { formatProvider } from '../git/connect-git-provider';
|
||||
import list from '../input/list';
|
||||
export async function promptGitConnectSingleUrl(
|
||||
client: Client,
|
||||
project: Project,
|
||||
remoteName: string,
|
||||
remoteUrl: string,
|
||||
hasDiffConnectedProvider = false
|
||||
) {
|
||||
const { output } = client;
|
||||
if (hasDiffConnectedProvider) {
|
||||
const currentRepoPath = `${project.link!.org}/${project.link!.repo}`;
|
||||
const currentProvider = project.link!.type;
|
||||
output.print('\n');
|
||||
output.log(
|
||||
`Found Git remote URL ${chalk.cyan(
|
||||
remoteUrl
|
||||
)}, which is different from the connected ${formatProvider(
|
||||
currentProvider
|
||||
)} repository ${chalk.cyan(currentRepoPath)}.`
|
||||
);
|
||||
} else {
|
||||
output.print('\n');
|
||||
output.log(
|
||||
`Found local Git remote "${remoteName}": ${chalk.cyan(remoteUrl)}`
|
||||
);
|
||||
}
|
||||
return await list(client, {
|
||||
message: hasDiffConnectedProvider
|
||||
? 'Do you want to replace it?'
|
||||
: `Do you want to connect "${remoteName}" to your Vercel project?`,
|
||||
choices: [
|
||||
{
|
||||
name: 'Yes',
|
||||
value: 'yes',
|
||||
short: 'yes',
|
||||
},
|
||||
{
|
||||
name: 'No',
|
||||
value: 'no',
|
||||
short: 'no',
|
||||
},
|
||||
{
|
||||
name: 'Do not ask again for this project',
|
||||
value: 'opt-out',
|
||||
short: 'no (opt out)',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export async function promptGitConnectMultipleUrls(
|
||||
client: Client,
|
||||
remoteUrls: Dictionary<string>
|
||||
) {
|
||||
const staticOptions = [
|
||||
{
|
||||
name: 'No',
|
||||
value: 'no',
|
||||
short: 'no',
|
||||
},
|
||||
{
|
||||
name: 'Do not ask again for this project',
|
||||
value: 'opt-out',
|
||||
short: 'no (opt out)',
|
||||
},
|
||||
];
|
||||
let choices = [];
|
||||
for (const url of Object.values(remoteUrls)) {
|
||||
choices.push({
|
||||
name: url,
|
||||
value: url,
|
||||
short: url,
|
||||
});
|
||||
}
|
||||
choices = choices.concat(staticOptions);
|
||||
|
||||
return await list(client, {
|
||||
message: 'Do you want to connect a Git repository to your Vercel project?',
|
||||
choices,
|
||||
});
|
||||
}
|
||||
98
packages/cli/src/util/link/handle-options.ts
Normal file
98
packages/cli/src/util/link/handle-options.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import chalk from 'chalk';
|
||||
import { Org, Project, ProjectSettings } from '../../types';
|
||||
import Client from '../client';
|
||||
import {
|
||||
connectGitProvider,
|
||||
disconnectGitProvider,
|
||||
formatProvider,
|
||||
RepoInfo,
|
||||
parseRepoUrl,
|
||||
} from '../git/connect-git-provider';
|
||||
import { Output } from '../output';
|
||||
import { getCommandName } from '../pkg-name';
|
||||
import updateProject from '../projects/update-project';
|
||||
|
||||
export async function handleOptions(
|
||||
option: string,
|
||||
client: Client,
|
||||
org: Org,
|
||||
project: Project,
|
||||
settings: ProjectSettings,
|
||||
repoInfo?: RepoInfo
|
||||
) {
|
||||
const { output } = client;
|
||||
if (option === 'no') {
|
||||
skip(output);
|
||||
return;
|
||||
} else if (option === 'opt-out') {
|
||||
optOut(client, project, settings);
|
||||
return;
|
||||
} else if (option !== '') {
|
||||
// Option is "yes" or a URL
|
||||
|
||||
// Ensure parsed url exists
|
||||
if (!repoInfo) {
|
||||
const _repoInfo = parseRepoUrl(option);
|
||||
if (!_repoInfo) {
|
||||
output.debug(`Could not parse repo url ${option}.`);
|
||||
return 1;
|
||||
}
|
||||
repoInfo = _repoInfo;
|
||||
}
|
||||
return connect(client, org, project, repoInfo);
|
||||
}
|
||||
}
|
||||
|
||||
async function optOut(
|
||||
client: Client,
|
||||
project: Project,
|
||||
settings: ProjectSettings
|
||||
) {
|
||||
settings.skipGitConnectDuringLink = true;
|
||||
await updateProject(client, project.name, settings);
|
||||
client.output
|
||||
.log(`Opted out. You can re-enable this prompt by visiting the Settings > Git page on the
|
||||
dashboard for this Project.`);
|
||||
}
|
||||
|
||||
function skip(output: Output) {
|
||||
output.log('Skipping...');
|
||||
output.log(
|
||||
`You can connect a Git repository in the future by running ${getCommandName(
|
||||
'git connect'
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
|
||||
async function connect(
|
||||
client: Client,
|
||||
org: Org,
|
||||
project: Project,
|
||||
repoInfo: RepoInfo
|
||||
): Promise<number | void> {
|
||||
const { output } = client;
|
||||
const { provider, org: parsedOrg, repo } = repoInfo;
|
||||
const repoPath = `${parsedOrg}/${repo}`;
|
||||
|
||||
output.log('Connecting...');
|
||||
|
||||
if (project.link) {
|
||||
await disconnectGitProvider(client, org, project.id);
|
||||
}
|
||||
const connect = await connectGitProvider(
|
||||
client,
|
||||
org,
|
||||
project.id,
|
||||
provider,
|
||||
repoPath
|
||||
);
|
||||
if (connect !== 1) {
|
||||
output.log(
|
||||
`Connected ${formatProvider(provider)} repository ${chalk.cyan(
|
||||
repoPath
|
||||
)}!`
|
||||
);
|
||||
} else {
|
||||
return connect;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,8 @@ import { EmojiLabel } from '../emoji';
|
||||
import createDeploy from '../deploy/create-deploy';
|
||||
import Now, { CreateOptions } from '../index';
|
||||
import { isAPIError } from '../errors-ts';
|
||||
import { getRemoteUrls } from '../create-git-meta';
|
||||
import { addGitConnection } from './add-git-connection';
|
||||
|
||||
export interface SetupAndLinkOptions {
|
||||
forceDelete?: boolean;
|
||||
@@ -128,6 +130,19 @@ export default async function setupAndLink(
|
||||
} else {
|
||||
const project = projectOrNewProjectName;
|
||||
|
||||
const remoteUrls = await getRemoteUrls(join(path, '.git/config'), output);
|
||||
if (remoteUrls && !project.skipGitConnectDuringLink) {
|
||||
const connectGit = await addGitConnection(
|
||||
client,
|
||||
org,
|
||||
project,
|
||||
remoteUrls
|
||||
);
|
||||
if (typeof connectGit === 'number') {
|
||||
return { status: 'error', exitCode: connectGit };
|
||||
}
|
||||
}
|
||||
|
||||
await linkFolderToProject(
|
||||
output,
|
||||
path,
|
||||
@@ -241,6 +256,21 @@ export default async function setupAndLink(
|
||||
}
|
||||
|
||||
const project = await createProject(client, newProjectName);
|
||||
|
||||
const remoteUrls = await getRemoteUrls(join(path, '.git/config'), output);
|
||||
if (remoteUrls) {
|
||||
const connectGit = await addGitConnection(
|
||||
client,
|
||||
org,
|
||||
project,
|
||||
remoteUrls,
|
||||
settings
|
||||
);
|
||||
if (typeof connectGit === 'number') {
|
||||
return { status: 'error', exitCode: connectGit };
|
||||
}
|
||||
}
|
||||
|
||||
await updateProject(client, project.id, settings);
|
||||
Object.assign(project, settings);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export default async function createProject(
|
||||
) {
|
||||
const project = await client.fetch<Project>('/v1/projects', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name: projectName }),
|
||||
body: { name: projectName },
|
||||
});
|
||||
return project;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Client from '../client';
|
||||
import { ProjectSettings } from '../../types';
|
||||
import type { JSONObject, ProjectSettings } from '../../types';
|
||||
|
||||
interface ProjectSettingsResponse extends ProjectSettings {
|
||||
id: string;
|
||||
@@ -13,11 +13,14 @@ export default async function updateProject(
|
||||
prjNameOrId: string,
|
||||
settings: ProjectSettings
|
||||
) {
|
||||
// `ProjectSettings` is technically compatible with JSONObject
|
||||
const body = settings as JSONObject;
|
||||
|
||||
const res = await client.fetch<ProjectSettingsResponse>(
|
||||
`/v2/projects/${encodeURIComponent(prjNameOrId)}`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(settings),
|
||||
body,
|
||||
}
|
||||
);
|
||||
return res;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import stripAnsi from 'strip-ansi';
|
||||
|
||||
export default function strlen(str: string) {
|
||||
return str.replace(/\u001b[^m]*m/g, '').length;
|
||||
return stripAnsi(str).length;
|
||||
}
|
||||
|
||||
7
packages/cli/test/dev/fixtures/vite-dev/.gitignore
vendored
Normal file
7
packages/cli/test/dev/fixtures/vite-dev/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.vercel
|
||||
!public
|
||||
13
packages/cli/test/dev/fixtures/vite-dev/index.html
Normal file
13
packages/cli/test/dev/fixtures/vite-dev/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
16
packages/cli/test/dev/fixtures/vite-dev/package.json
Normal file
16
packages/cli/test/dev/fixtures/vite-dev/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --port $PORT",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "3.2.37"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "1.10.2",
|
||||
"@vue/compiler-sfc": "3.2.37",
|
||||
"vite": "2.9.14"
|
||||
}
|
||||
}
|
||||
BIN
packages/cli/test/dev/fixtures/vite-dev/public/favicon.ico
Normal file
BIN
packages/cli/test/dev/fixtures/vite-dev/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
22
packages/cli/test/dev/fixtures/vite-dev/src/App.vue
Normal file
22
packages/cli/test/dev/fixtures/vite-dev/src/App.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<img alt="Vue logo" src="./assets/logo.png" />
|
||||
<HelloWorld msg="Hello Vue 3 + Vite" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
|
||||
// This starter template is using Vue 3 experimental <script setup> SFCs
|
||||
// Check out https://github.com/vuejs/rfcs/blob/script-setup-2/active-rfcs/0000-script-setup.md
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
||||
BIN
packages/cli/test/dev/fixtures/vite-dev/src/assets/logo.png
Normal file
BIN
packages/cli/test/dev/fixtures/vite-dev/src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<p>
|
||||
<a href="https://vitejs.dev/guide/features.html" target="_blank">
|
||||
Vite Documentation
|
||||
</a>
|
||||
|
|
||||
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Documentation</a>
|
||||
</p>
|
||||
|
||||
<button type="button" @click="state.count++">count is: {{ state.count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test hot module replacement.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, reactive } from 'vue'
|
||||
|
||||
defineProps({
|
||||
msg: String
|
||||
})
|
||||
|
||||
const state = reactive({ count: 0 })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
||||
4
packages/cli/test/dev/fixtures/vite-dev/src/main.js
Normal file
4
packages/cli/test/dev/fixtures/vite-dev/src/main.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
|
||||
createApp(App).mount('#app');
|
||||
7
packages/cli/test/dev/fixtures/vite-dev/vite.config.js
Normal file
7
packages/cli/test/dev/fixtures/vite-dev/vite.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
});
|
||||
359
packages/cli/test/dev/fixtures/vite-dev/yarn.lock
Normal file
359
packages/cli/test/dev/fixtures/vite-dev/yarn.lock
Normal file
@@ -0,0 +1,359 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@babel/parser@^7.16.4":
|
||||
version "7.18.11"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9"
|
||||
integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==
|
||||
|
||||
"@esbuild/linux-loong64@0.14.54":
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028"
|
||||
integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==
|
||||
|
||||
"@vitejs/plugin-vue@1.10.2":
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-1.10.2.tgz#d718479e2789d8a94b63e00f23f1898ba239253a"
|
||||
integrity sha512-/QJ0Z9qfhAFtKRY+r57ziY4BSbGUTGsPRMpB/Ron3QPwBZM4OZAZHdTa4a8PafCwU5DTatXG8TMDoP8z+oDqJw==
|
||||
|
||||
"@vue/compiler-core@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.37.tgz#b3c42e04c0e0f2c496ff1784e543fbefe91e215a"
|
||||
integrity sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/shared" "3.2.37"
|
||||
estree-walker "^2.0.2"
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@vue/compiler-dom@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz#10d2427a789e7c707c872da9d678c82a0c6582b5"
|
||||
integrity sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==
|
||||
dependencies:
|
||||
"@vue/compiler-core" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
|
||||
"@vue/compiler-sfc@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz#3103af3da2f40286edcd85ea495dcb35bc7f5ff4"
|
||||
integrity sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/compiler-core" "3.2.37"
|
||||
"@vue/compiler-dom" "3.2.37"
|
||||
"@vue/compiler-ssr" "3.2.37"
|
||||
"@vue/reactivity-transform" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.25.7"
|
||||
postcss "^8.1.10"
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@vue/compiler-ssr@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz#4899d19f3a5fafd61524a9d1aee8eb0505313cff"
|
||||
integrity sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==
|
||||
dependencies:
|
||||
"@vue/compiler-dom" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
|
||||
"@vue/reactivity-transform@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz#0caa47c4344df4ae59f5a05dde2a8758829f8eca"
|
||||
integrity sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/compiler-core" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.25.7"
|
||||
|
||||
"@vue/reactivity@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.37.tgz#5bc3847ac58828e2b78526e08219e0a1089f8848"
|
||||
integrity sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==
|
||||
dependencies:
|
||||
"@vue/shared" "3.2.37"
|
||||
|
||||
"@vue/runtime-core@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.37.tgz#7ba7c54bb56e5d70edfc2f05766e1ca8519966e3"
|
||||
integrity sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==
|
||||
dependencies:
|
||||
"@vue/reactivity" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
|
||||
"@vue/runtime-dom@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz#002bdc8228fa63949317756fb1e92cdd3f9f4bbd"
|
||||
integrity sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==
|
||||
dependencies:
|
||||
"@vue/runtime-core" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
csstype "^2.6.8"
|
||||
|
||||
"@vue/server-renderer@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.37.tgz#840a29c8dcc29bddd9b5f5ffa22b95c0e72afdfc"
|
||||
integrity sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==
|
||||
dependencies:
|
||||
"@vue/compiler-ssr" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
|
||||
"@vue/shared@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.37.tgz#8e6adc3f2759af52f0e85863dfb0b711ecc5c702"
|
||||
integrity sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==
|
||||
|
||||
csstype@^2.6.8:
|
||||
version "2.6.17"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.17.tgz#4cf30eb87e1d1a005d8b6510f95292413f6a1c0e"
|
||||
integrity sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==
|
||||
|
||||
esbuild-android-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be"
|
||||
integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==
|
||||
|
||||
esbuild-android-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771"
|
||||
integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==
|
||||
|
||||
esbuild-darwin-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25"
|
||||
integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==
|
||||
|
||||
esbuild-darwin-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73"
|
||||
integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==
|
||||
|
||||
esbuild-freebsd-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d"
|
||||
integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==
|
||||
|
||||
esbuild-freebsd-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48"
|
||||
integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==
|
||||
|
||||
esbuild-linux-32@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5"
|
||||
integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==
|
||||
|
||||
esbuild-linux-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652"
|
||||
integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==
|
||||
|
||||
esbuild-linux-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b"
|
||||
integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==
|
||||
|
||||
esbuild-linux-arm@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59"
|
||||
integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==
|
||||
|
||||
esbuild-linux-mips64le@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34"
|
||||
integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==
|
||||
|
||||
esbuild-linux-ppc64le@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e"
|
||||
integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==
|
||||
|
||||
esbuild-linux-riscv64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8"
|
||||
integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==
|
||||
|
||||
esbuild-linux-s390x@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6"
|
||||
integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==
|
||||
|
||||
esbuild-netbsd-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81"
|
||||
integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==
|
||||
|
||||
esbuild-openbsd-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b"
|
||||
integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==
|
||||
|
||||
esbuild-sunos-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da"
|
||||
integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==
|
||||
|
||||
esbuild-windows-32@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31"
|
||||
integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==
|
||||
|
||||
esbuild-windows-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4"
|
||||
integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==
|
||||
|
||||
esbuild-windows-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982"
|
||||
integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==
|
||||
|
||||
esbuild@^0.14.27:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2"
|
||||
integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==
|
||||
optionalDependencies:
|
||||
"@esbuild/linux-loong64" "0.14.54"
|
||||
esbuild-android-64 "0.14.54"
|
||||
esbuild-android-arm64 "0.14.54"
|
||||
esbuild-darwin-64 "0.14.54"
|
||||
esbuild-darwin-arm64 "0.14.54"
|
||||
esbuild-freebsd-64 "0.14.54"
|
||||
esbuild-freebsd-arm64 "0.14.54"
|
||||
esbuild-linux-32 "0.14.54"
|
||||
esbuild-linux-64 "0.14.54"
|
||||
esbuild-linux-arm "0.14.54"
|
||||
esbuild-linux-arm64 "0.14.54"
|
||||
esbuild-linux-mips64le "0.14.54"
|
||||
esbuild-linux-ppc64le "0.14.54"
|
||||
esbuild-linux-riscv64 "0.14.54"
|
||||
esbuild-linux-s390x "0.14.54"
|
||||
esbuild-netbsd-64 "0.14.54"
|
||||
esbuild-openbsd-64 "0.14.54"
|
||||
esbuild-sunos-64 "0.14.54"
|
||||
esbuild-windows-32 "0.14.54"
|
||||
esbuild-windows-64 "0.14.54"
|
||||
esbuild-windows-arm64 "0.14.54"
|
||||
|
||||
estree-walker@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||
|
||||
fsevents@~2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
|
||||
has@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
is-core-module@^2.9.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed"
|
||||
integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
magic-string@^0.25.7:
|
||||
version "0.25.7"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
|
||||
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
|
||||
dependencies:
|
||||
sourcemap-codec "^1.4.4"
|
||||
|
||||
nanoid@^3.3.4:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
|
||||
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
|
||||
|
||||
path-parse@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
picocolors@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||
|
||||
postcss@^8.1.10, postcss@^8.4.13:
|
||||
version "8.4.16"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c"
|
||||
integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==
|
||||
dependencies:
|
||||
nanoid "^3.3.4"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
resolve@^1.22.0:
|
||||
version "1.22.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
|
||||
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
|
||||
dependencies:
|
||||
is-core-module "^2.9.0"
|
||||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
rollup@^2.59.0:
|
||||
version "2.77.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.2.tgz#6b6075c55f9cc2040a5912e6e062151e42e2c4e3"
|
||||
integrity sha512-m/4YzYgLcpMQbxX3NmAqDvwLATZzxt8bIegO78FZLl+lAgKJBd1DRAOeEiZcKOIOPjxE6ewHWHNgGEalFXuz1g==
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||
|
||||
source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
sourcemap-codec@^1.4.4:
|
||||
version "1.4.8"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||
|
||||
supports-preserve-symlinks-flag@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
vite@2.9.14:
|
||||
version "2.9.14"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.14.tgz#c438324c6594afd1050df3777da981dee988bb1b"
|
||||
integrity sha512-P/UCjSpSMcE54r4mPak55hWAZPlyfS369svib/gpmz8/01L822lMPOJ/RYW6tLCe1RPvMvOsJ17erf55bKp4Hw==
|
||||
dependencies:
|
||||
esbuild "^0.14.27"
|
||||
postcss "^8.4.13"
|
||||
resolve "^1.22.0"
|
||||
rollup "^2.59.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
vue@3.2.37:
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.37.tgz#da220ccb618d78579d25b06c7c21498ca4e5452e"
|
||||
integrity sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==
|
||||
dependencies:
|
||||
"@vue/compiler-dom" "3.2.37"
|
||||
"@vue/compiler-sfc" "3.2.37"
|
||||
"@vue/runtime-dom" "3.2.37"
|
||||
"@vue/server-renderer" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
@@ -1045,3 +1045,20 @@ test('[vercel dev] validate rewrites', async () => {
|
||||
/Invalid vercel\.json - `rewrites\[0\].destination` should be string/m
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'[vercel dev] should correctly proxy to vite dev',
|
||||
testFixtureStdio(
|
||||
'vite-dev',
|
||||
async (testPath: any) => {
|
||||
const url = '/src/App.vue?vue&type=style&index=0&lang.css';
|
||||
// The first request should return the HTML template
|
||||
await testPath(200, url, /<template>/gm);
|
||||
// The second request should return the HMR JS
|
||||
await testPath(200, url, /__vite__createHotContext/gm);
|
||||
// Home page should always return HTML
|
||||
await testPath(200, '/', /<title>Vite App<\/title>/gm);
|
||||
},
|
||||
{ skipDeploy: true }
|
||||
)
|
||||
);
|
||||
|
||||
1
packages/cli/test/fixtures/unit/commands/deploy/archive/.gitignore
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/deploy/archive/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
4
packages/cli/test/fixtures/unit/commands/deploy/archive/.vercel/project.json
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/deploy/archive/.vercel/project.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "archive"
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/commands/deploy/archive/index.html
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/deploy/archive/index.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<h1>hi</h1>
|
||||
3
packages/cli/test/fixtures/unit/commands/deploy/archive/style.css
vendored
Normal file
3
packages/cli/test/fixtures/unit/commands/deploy/archive/style.css
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
body {
|
||||
background-color: red;
|
||||
}
|
||||
2
packages/cli/test/fixtures/unit/link-connect-git/multiple-remotes/.gitignore
vendored
Normal file
2
packages/cli/test/fixtures/unit/link-connect-git/multiple-remotes/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
!.vercel
|
||||
.vercel
|
||||
16
packages/cli/test/fixtures/unit/link-connect-git/multiple-remotes/git/config
generated
vendored
Normal file
16
packages/cli/test/fixtures/unit/link-connect-git/multiple-remotes/git/config
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[remote "origin"]
|
||||
url = https://github.com/user/repo.git
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
[remote "secondary"]
|
||||
url = https://github.com/user2/repo2.git
|
||||
fetch = +refs/heads/*:refs/remotes/secondary/*
|
||||
[remote "gitlab"]
|
||||
url = https://gitlab.com/user/repo.git
|
||||
fetch = +refs/heads/*:refs/remotes/gitlab/*
|
||||
2
packages/cli/test/fixtures/unit/link-connect-git/single-remote-existing-link/.gitignore
vendored
Normal file
2
packages/cli/test/fixtures/unit/link-connect-git/single-remote-existing-link/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
!.vercel
|
||||
.vercel
|
||||
13
packages/cli/test/fixtures/unit/link-connect-git/single-remote-existing-link/git/config
generated
vendored
Normal file
13
packages/cli/test/fixtures/unit/link-connect-git/single-remote-existing-link/git/config
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[remote "origin"]
|
||||
url = https://github.com/user2/repo2.git
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
[branch "master"]
|
||||
remote = origin
|
||||
merge = refs/heads/master
|
||||
2
packages/cli/test/fixtures/unit/link-connect-git/single-remote/.gitignore
vendored
Normal file
2
packages/cli/test/fixtures/unit/link-connect-git/single-remote/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
!.vercel
|
||||
.vercel
|
||||
13
packages/cli/test/fixtures/unit/link-connect-git/single-remote/git/config
generated
vendored
Normal file
13
packages/cli/test/fixtures/unit/link-connect-git/single-remote/git/config
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[remote "origin"]
|
||||
url = https://github.com/user/repo.git
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
[branch "master"]
|
||||
remote = origin
|
||||
merge = refs/heads/master
|
||||
2
packages/cli/test/fixtures/unit/vercel-env-pull-delta-quotes/.env
vendored
Normal file
2
packages/cli/test/fixtures/unit/vercel-env-pull-delta-quotes/.env
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
NEW_VAR=testvalue
|
||||
SPECIAL_FLAG=1
|
||||
2
packages/cli/test/fixtures/unit/vercel-env-pull-delta-quotes/.env.testquotes
vendored
Normal file
2
packages/cli/test/fixtures/unit/vercel-env-pull-delta-quotes/.env.testquotes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
NEW_VAR="testvalue"
|
||||
SPECIAL_FLAG=1
|
||||
1
packages/cli/test/fixtures/unit/vercel-env-pull-delta-quotes/.gitignore
vendored
Normal file
1
packages/cli/test/fixtures/unit/vercel-env-pull-delta-quotes/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
4
packages/cli/test/fixtures/unit/vercel-env-pull-delta-quotes/.vercel/project.json
vendored
Normal file
4
packages/cli/test/fixtures/unit/vercel-env-pull-delta-quotes/.vercel/project.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "env-pull-delta-quotes"
|
||||
}
|
||||
@@ -8,7 +8,9 @@ export function readOutputStream(
|
||||
let output: string = '';
|
||||
let lines = 0;
|
||||
const timeout = setTimeout(() => {
|
||||
reject();
|
||||
reject(
|
||||
new Error(`Was waiting for ${length} lines, but only received ${chunks.length}`)
|
||||
);
|
||||
}, 3000);
|
||||
|
||||
client.stderr.resume();
|
||||
|
||||
55
packages/cli/test/integration.js
vendored
55
packages/cli/test/integration.js
vendored
@@ -1538,23 +1538,6 @@ test('list the scopes', async t => {
|
||||
);
|
||||
});
|
||||
|
||||
test('list the payment methods', async t => {
|
||||
const { stdout, stderr, exitCode } = await execa(
|
||||
binaryPath,
|
||||
['billing', 'ls', ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(exitCode);
|
||||
|
||||
t.is(exitCode, 0);
|
||||
t.true(stdout.startsWith(`> 0 cards found under ${contextName}`));
|
||||
});
|
||||
|
||||
test('domains inspect', async t => {
|
||||
const domainName = `inspect-${contextName}-${Math.random()
|
||||
.toString()
|
||||
@@ -1701,44 +1684,6 @@ test('try to move an invalid domain', async t => {
|
||||
t.is(exitCode, 1);
|
||||
});
|
||||
|
||||
test('try to set default without existing payment method', async t => {
|
||||
const { stderr, stdout, exitCode } = await execa(
|
||||
binaryPath,
|
||||
['billing', 'set-default', ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(exitCode);
|
||||
|
||||
t.is(exitCode, 0);
|
||||
t.true(stderr.includes('You have no credit cards to choose from'));
|
||||
});
|
||||
|
||||
test('try to remove a non-existing payment method', async t => {
|
||||
const { stderr, stdout, exitCode } = await execa(
|
||||
binaryPath,
|
||||
['billing', 'rm', 'card_d2j32d9382jr928rd', ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(exitCode);
|
||||
|
||||
t.is(exitCode, 0);
|
||||
t.true(
|
||||
stderr.includes(
|
||||
`You have no credit cards to choose from to delete under ${contextName}`
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
/*
|
||||
* Disabled 2 tests because these temp users don't have certs
|
||||
test('create wildcard alias for deployment', async t => {
|
||||
|
||||
@@ -94,7 +94,9 @@ export class MockClient extends Client {
|
||||
this.output = new Output(this.stderr);
|
||||
|
||||
this.argv = [];
|
||||
this.authConfig = {};
|
||||
this.authConfig = {
|
||||
token: 'token_dummy',
|
||||
};
|
||||
this.config = {};
|
||||
this.localConfig = {};
|
||||
|
||||
|
||||
@@ -7,17 +7,25 @@ import { Build, User } from '../../src/types';
|
||||
let deployments = new Map<string, Deployment>();
|
||||
let deploymentBuilds = new Map<Deployment, Build[]>();
|
||||
|
||||
type State = Deployment['readyState'];
|
||||
|
||||
export function useDeployment({
|
||||
creator,
|
||||
state = 'READY',
|
||||
}: {
|
||||
creator: Pick<User, 'id' | 'email' | 'name'>;
|
||||
creator: Pick<User, 'id' | 'email' | 'name' | 'username'>;
|
||||
state?: State;
|
||||
}) {
|
||||
const createdAt = Date.now();
|
||||
const url = new URL(chance().url());
|
||||
const name = chance().name();
|
||||
const id = `dpl_${chance().guid()}`;
|
||||
|
||||
const deployment: Deployment = {
|
||||
id: `dpl_${chance().guid()}`,
|
||||
id,
|
||||
url: url.hostname,
|
||||
name: '',
|
||||
inspectorUrl: `https://vercel.com/team/project/${id.replace('dpl_', '')}`,
|
||||
name,
|
||||
meta: {},
|
||||
regions: [],
|
||||
routes: [],
|
||||
@@ -26,19 +34,24 @@ export function useDeployment({
|
||||
version: 2,
|
||||
createdAt,
|
||||
createdIn: 'sfo1',
|
||||
buildingAt: Date.now(),
|
||||
ownerId: creator.id,
|
||||
creator: {
|
||||
uid: creator.id,
|
||||
email: creator.email,
|
||||
username: creator.name,
|
||||
name: creator.name,
|
||||
username: creator.username,
|
||||
},
|
||||
readyState: 'READY',
|
||||
readyState: state,
|
||||
state: state,
|
||||
ready: createdAt + 30000,
|
||||
env: {},
|
||||
build: { env: {} },
|
||||
target: 'production',
|
||||
alias: [],
|
||||
aliasAssigned: true,
|
||||
aliasError: null,
|
||||
inspectorUrl: `https://vercel.com/${creator.name}/${id}`,
|
||||
};
|
||||
|
||||
deployments.set(deployment.id, deployment);
|
||||
@@ -47,6 +60,43 @@ export function useDeployment({
|
||||
return deployment;
|
||||
}
|
||||
|
||||
export function useDeploymentMissingProjectSettings() {
|
||||
client.scenario.post('/:version/deployments', (_req, res) => {
|
||||
res.status(400).json({
|
||||
error: {
|
||||
code: 'missing_project_settings',
|
||||
message:
|
||||
'The `projectSettings` object is required for new projects, but is missing in the deployment payload',
|
||||
framework: {
|
||||
name: 'Other',
|
||||
slug: null,
|
||||
logo: 'https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/other.svg',
|
||||
description: 'No framework or an unoptimized framework.',
|
||||
settings: {
|
||||
installCommand: {
|
||||
placeholder: '`yarn install`, `pnpm install`, or `npm install`',
|
||||
},
|
||||
buildCommand: {
|
||||
placeholder: '`npm run vercel-build` or `npm run build`',
|
||||
value: null,
|
||||
},
|
||||
devCommand: { placeholder: 'None', value: null },
|
||||
outputDirectory: { placeholder: '`public` if it exists, or `.`' },
|
||||
},
|
||||
},
|
||||
projectSettings: {
|
||||
devCommand: null,
|
||||
installCommand: null,
|
||||
buildCommand: null,
|
||||
outputDirectory: null,
|
||||
rootDirectory: null,
|
||||
framework: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
deployments = new Map();
|
||||
deploymentBuilds = new Map();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { client } from './client';
|
||||
import { Project } from '../../src/types';
|
||||
import { formatProvider } from '../../src/util/projects/connect-git-provider';
|
||||
import { formatProvider } from '../../src/util/git/connect-git-provider';
|
||||
|
||||
const envs = [
|
||||
{
|
||||
@@ -123,8 +123,78 @@ export const defaultProject = {
|
||||
userId: 'K4amb7K9dAt5R2vBJWF32bmY',
|
||||
},
|
||||
],
|
||||
alias: [
|
||||
{
|
||||
domain: 'foobar.com',
|
||||
target: 'PRODUCTION' as const,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Responds to any GET for a project with a 404.
|
||||
* `useUnknownProject` should always come after `useProject`, if any,
|
||||
* to allow `useProject` responses to still happen.
|
||||
*/
|
||||
export function useUnknownProject() {
|
||||
let project: Project;
|
||||
client.scenario.get(`/v8/projects/:projectNameOrId`, (_req, res) => {
|
||||
res.status(404).send();
|
||||
});
|
||||
client.scenario.post(`/:version/projects`, (req, res) => {
|
||||
const { name } = req.body;
|
||||
project = {
|
||||
...defaultProject,
|
||||
name,
|
||||
id: name,
|
||||
};
|
||||
res.json(project);
|
||||
});
|
||||
client.scenario.post(`/v9/projects/:projectNameOrId/link`, (req, res) => {
|
||||
const { type, repo, org } = req.body;
|
||||
const projName = req.params.projectNameOrId;
|
||||
if (projName !== project.name && projName !== project.id) {
|
||||
return res.status(404).send('Invalid Project name or ID');
|
||||
}
|
||||
if (
|
||||
(type === 'github' || type === 'gitlab' || type === 'bitbucket') &&
|
||||
(repo === 'user/repo' || repo === 'user2/repo2')
|
||||
) {
|
||||
project.link = {
|
||||
type,
|
||||
repo,
|
||||
repoId: 1010,
|
||||
org,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
};
|
||||
res.json(project);
|
||||
} else {
|
||||
if (type === 'github') {
|
||||
res.status(400).json({
|
||||
message: `To link a GitHub repository, you need to install the GitHub integration first. (400)\nInstall GitHub App: https://github.com/apps/vercel`,
|
||||
action: 'Install GitHub App',
|
||||
link: 'https://github.com/apps/vercel',
|
||||
repo,
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
code: 'repo_not_found',
|
||||
message: `The repository "${repo}" couldn't be found in your linked ${formatProvider(
|
||||
type
|
||||
)} account.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
client.scenario.patch(`/:version/projects/:projectNameOrId`, (req, res) => {
|
||||
Object.assign(project, req.body);
|
||||
res.json(project);
|
||||
});
|
||||
}
|
||||
|
||||
export function useProject(project: Partial<Project> = defaultProject) {
|
||||
client.scenario.get(`/v8/projects/${project.name}`, (_req, res) => {
|
||||
res.json(project);
|
||||
@@ -132,6 +202,10 @@ export function useProject(project: Partial<Project> = defaultProject) {
|
||||
client.scenario.get(`/v8/projects/${project.id}`, (_req, res) => {
|
||||
res.json(project);
|
||||
});
|
||||
client.scenario.patch(`/:version/projects/${project.id}`, (req, res) => {
|
||||
Object.assign(project, req.body);
|
||||
res.json(project);
|
||||
});
|
||||
client.scenario.get(
|
||||
`/v6/projects/${project.id}/system-env-values`,
|
||||
(_req, res) => {
|
||||
@@ -177,11 +251,11 @@ export function useProject(project: Partial<Project> = defaultProject) {
|
||||
res.json(envs);
|
||||
}
|
||||
);
|
||||
client.scenario.post(`/v4/projects/${project.id}/link`, (req, res) => {
|
||||
client.scenario.post(`/v9/projects/${project.id}/link`, (req, res) => {
|
||||
const { type, repo, org } = req.body;
|
||||
if (
|
||||
(type === 'github' || type === 'gitlab' || type === 'bitbucket') &&
|
||||
(repo === 'user/repo' || repo === 'user2/repo2')
|
||||
(repo === 'user/repo' || repo === 'user2/repo2' || repo === 'user3/repo3')
|
||||
) {
|
||||
project.link = {
|
||||
type,
|
||||
@@ -198,11 +272,9 @@ export function useProject(project: Partial<Project> = defaultProject) {
|
||||
if (type === 'github') {
|
||||
res.status(400).json({
|
||||
message: `To link a GitHub repository, you need to install the GitHub integration first. (400)\nInstall GitHub App: https://github.com/apps/vercel`,
|
||||
meta: {
|
||||
action: 'Install GitHub App',
|
||||
link: 'https://github.com/apps/vercel',
|
||||
repo,
|
||||
},
|
||||
action: 'Install GitHub App',
|
||||
link: 'https://github.com/apps/vercel',
|
||||
repo,
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
@@ -214,7 +286,7 @@ export function useProject(project: Partial<Project> = defaultProject) {
|
||||
}
|
||||
}
|
||||
});
|
||||
client.scenario.delete(`/v4/projects/${project.id}/link`, (_req, res) => {
|
||||
client.scenario.delete(`/v9/projects/${project.id}/link`, (_req, res) => {
|
||||
if (project.link) {
|
||||
project.link = undefined;
|
||||
}
|
||||
|
||||
@@ -136,4 +136,67 @@ describe('deploy', () => {
|
||||
);
|
||||
await expect(exitCodePromise).resolves.toEqual(1);
|
||||
});
|
||||
|
||||
it('should send a tgz file when `--archive=tgz`', async () => {
|
||||
const cwd = setupFixture('commands/deploy/archive');
|
||||
const originalCwd = process.cwd();
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
|
||||
const user = useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
name: 'archive',
|
||||
id: 'archive',
|
||||
});
|
||||
|
||||
let body: any;
|
||||
client.scenario.post(`/v13/deployments`, (req, res) => {
|
||||
body = req.body;
|
||||
res.json({
|
||||
creator: {
|
||||
uid: user.id,
|
||||
username: user.username,
|
||||
},
|
||||
id: 'dpl_archive_test',
|
||||
});
|
||||
});
|
||||
client.scenario.get(`/v13/deployments/dpl_archive_test`, (req, res) => {
|
||||
res.json({
|
||||
creator: {
|
||||
uid: user.id,
|
||||
username: user.username,
|
||||
},
|
||||
id: 'dpl_archive_test',
|
||||
readyState: 'READY',
|
||||
aliasAssigned: true,
|
||||
alias: [],
|
||||
});
|
||||
});
|
||||
client.scenario.get(
|
||||
`/v10/now/deployments/dpl_archive_test`,
|
||||
(req, res) => {
|
||||
res.json({
|
||||
creator: {
|
||||
uid: user.id,
|
||||
username: user.username,
|
||||
},
|
||||
id: 'dpl_archive_test',
|
||||
readyState: 'READY',
|
||||
aliasAssigned: true,
|
||||
alias: [],
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
client.setArgv('deploy', '--archive=tgz');
|
||||
const exitCode = await deploy(client);
|
||||
expect(exitCode).toEqual(0);
|
||||
expect(body?.files?.length).toEqual(1);
|
||||
expect(body?.files?.[0].file).toEqual('.vercel/source.tgz');
|
||||
} finally {
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -222,5 +222,81 @@ describe('env', () => {
|
||||
await expect(client.stderr).toOutput('Updated .env file');
|
||||
await expect(pullPromise).resolves.toEqual(0);
|
||||
});
|
||||
|
||||
it('should correctly render delta string when env variable has quotes', async () => {
|
||||
const cwd = setupFixture('vercel-env-pull-delta-quotes');
|
||||
try {
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
defaultProject.env.push({
|
||||
type: 'encrypted',
|
||||
id: '781dt89g8r2h789g',
|
||||
key: 'NEW_VAR',
|
||||
value: '"testvalue"',
|
||||
target: ['development'],
|
||||
gitBranch: null,
|
||||
configurationId: null,
|
||||
updatedAt: 1557241361455,
|
||||
createdAt: 1557241361455,
|
||||
});
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'env-pull-delta-quotes',
|
||||
name: 'env-pull-delta-quotes',
|
||||
});
|
||||
|
||||
client.setArgv('env', 'pull', '--yes', '--cwd', cwd);
|
||||
const pullPromise = env(client);
|
||||
await expect(client.stderr).toOutput(
|
||||
'Downloading `development` Environment Variables for Project env-pull-delta'
|
||||
);
|
||||
await expect(client.stderr).toOutput('No changes found.\n');
|
||||
await expect(client.stderr).toOutput('Updated .env file');
|
||||
|
||||
await expect(pullPromise).resolves.toEqual(0);
|
||||
} finally {
|
||||
client.setArgv('env', 'rm', 'NEW_VAR', '--yes', '--cwd', cwd);
|
||||
await env(client);
|
||||
defaultProject.env.pop();
|
||||
}
|
||||
});
|
||||
|
||||
it('should correctly render delta string when local env variable has quotes', async () => {
|
||||
const cwd = setupFixture('vercel-env-pull-delta-quotes');
|
||||
try {
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
defaultProject.env.push({
|
||||
type: 'encrypted',
|
||||
id: '781dt89g8r2h789g',
|
||||
key: 'NEW_VAR',
|
||||
value: 'testvalue',
|
||||
target: ['development'],
|
||||
gitBranch: null,
|
||||
configurationId: null,
|
||||
updatedAt: 1557241361455,
|
||||
createdAt: 1557241361455,
|
||||
});
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'env-pull-delta-quotes',
|
||||
name: 'env-pull-delta-quotes',
|
||||
});
|
||||
|
||||
client.setArgv('env', 'pull', '.env.testquotes', '--yes', '--cwd', cwd);
|
||||
const pullPromise = env(client);
|
||||
await expect(client.stderr).toOutput(
|
||||
'Downloading `development` Environment Variables for Project env-pull-delta'
|
||||
);
|
||||
await expect(client.stderr).toOutput('No changes found.\n');
|
||||
await expect(client.stderr).toOutput('Updated .env.testquotes file');
|
||||
|
||||
await expect(pullPromise).resolves.toEqual(0);
|
||||
} finally {
|
||||
client.setArgv('env', 'rm', 'NEW_VAR', '--yes', '--cwd', cwd);
|
||||
await env(client);
|
||||
defaultProject.env.pop();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,6 +39,11 @@ describe('git', () => {
|
||||
await expect(client.stderr).toOutput('Found project');
|
||||
client.stdin.write('y\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
'Do you want to connect "origin" to your Vercel project?'
|
||||
);
|
||||
client.stdin.write('n\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Connecting Git remote: https://github.com/user/repo.git`
|
||||
);
|
||||
@@ -80,7 +85,7 @@ describe('git', () => {
|
||||
const exitCode = await git(client);
|
||||
expect(exitCode).toEqual(1);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Error! No local git repo found. Run \`git clone <url>\` to clone a remote Git repository first.\n`
|
||||
`Error! No local Git repository found. Run \`git clone <url>\` to clone a remote Git repository first.\n`
|
||||
);
|
||||
} finally {
|
||||
process.chdir(originalCwd);
|
||||
@@ -426,5 +431,188 @@ describe('git', () => {
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should connect a given repository', async () => {
|
||||
const cwd = fixture('no-remote-url');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'no-remote-url',
|
||||
name: 'no-remote-url',
|
||||
});
|
||||
|
||||
client.setArgv('git', 'connect', 'https://github.com/user2/repo2');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Connecting Git remote: https://github.com/user2/repo2`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Connected GitHub repository user2/repo2!`
|
||||
);
|
||||
|
||||
const newProjectData: Project = await client.fetch(
|
||||
`/v8/projects/no-remote-url`
|
||||
);
|
||||
expect(newProjectData.link).toMatchObject({
|
||||
type: 'github',
|
||||
repo: 'user2/repo2',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
});
|
||||
|
||||
await expect(gitPromise).resolves.toEqual(0);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should prompt when it finds a repository', async () => {
|
||||
const cwd = fixture('new-connection');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'new-connection',
|
||||
name: 'new-connection',
|
||||
});
|
||||
|
||||
client.setArgv('git', 'connect', 'https://github.com/user2/repo2');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`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]`
|
||||
);
|
||||
client.stdin.write('y\n');
|
||||
await expect(client.stderr).toOutput(
|
||||
`Connecting Git remote: https://github.com/user2/repo2`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Connected GitHub repository user2/repo2!`
|
||||
);
|
||||
|
||||
const newProjectData: Project = await client.fetch(
|
||||
`/v8/projects/new-connection`
|
||||
);
|
||||
expect(newProjectData.link).toMatchObject({
|
||||
type: 'github',
|
||||
repo: 'user2/repo2',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
});
|
||||
|
||||
await expect(gitPromise).resolves.toEqual(0);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should prompt when it finds multiple remotes', async () => {
|
||||
const cwd = fixture('multiple-remotes');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'multiple-remotes',
|
||||
name: 'multiple-remotes',
|
||||
});
|
||||
|
||||
client.setArgv('git', 'connect', 'https://github.com/user3/repo3');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`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]`
|
||||
);
|
||||
client.stdin.write('y\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Connecting Git remote: https://github.com/user3/repo3`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Connected GitHub repository user3/repo3!`
|
||||
);
|
||||
|
||||
const newProjectData: Project = await client.fetch(
|
||||
`/v8/projects/multiple-remotes`
|
||||
);
|
||||
expect(newProjectData.link).toMatchObject({
|
||||
type: 'github',
|
||||
repo: 'user3/repo3',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
});
|
||||
|
||||
await expect(gitPromise).resolves.toEqual(0);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should continue as normal when input matches single git remote', async () => {
|
||||
const cwd = fixture('new-connection');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'new-connection',
|
||||
name: 'new-connection',
|
||||
});
|
||||
|
||||
client.setArgv('git', 'connect', 'https://github.com/user/repo');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Connecting Git remote: https://github.com/user/repo`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Connected GitHub repository user/repo!`
|
||||
);
|
||||
|
||||
const newProjectData: Project = await client.fetch(
|
||||
`/v8/projects/new-connection`
|
||||
);
|
||||
expect(newProjectData.link).toMatchObject({
|
||||
type: 'github',
|
||||
repo: 'user/repo',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
});
|
||||
|
||||
await expect(gitPromise).resolves.toEqual(0);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
310
packages/cli/test/unit/commands/link.test.ts
Normal file
310
packages/cli/test/unit/commands/link.test.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import { join } from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import link from '../../../src/commands/link';
|
||||
import { useUser } from '../../mocks/user';
|
||||
import { useTeams } from '../../mocks/team';
|
||||
import {
|
||||
defaultProject,
|
||||
useUnknownProject,
|
||||
useProject,
|
||||
} from '../../mocks/project';
|
||||
import { client } from '../../mocks/client';
|
||||
import { useDeploymentMissingProjectSettings } from '../../mocks/deployment';
|
||||
import { Project } from '../../../src/types';
|
||||
|
||||
describe('link', () => {
|
||||
describe('git prompt', () => {
|
||||
const originalCwd = process.cwd();
|
||||
const fixture = (name: string) =>
|
||||
join(__dirname, '../../fixtures/unit/link-connect-git', name);
|
||||
|
||||
it('should prompt to connect a new project with a single remote', async () => {
|
||||
const cwd = fixture('single-remote');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useUnknownProject();
|
||||
useDeploymentMissingProjectSettings();
|
||||
useTeams('team_dummy');
|
||||
const linkPromise = link(client);
|
||||
|
||||
await expect(client.stderr).toOutput('Set up');
|
||||
client.stdin.write('y\n');
|
||||
await expect(client.stderr).toOutput('Which scope');
|
||||
client.stdin.write('\r');
|
||||
await expect(client.stderr).toOutput('Link to existing project?');
|
||||
client.stdin.write('n\n');
|
||||
await expect(client.stderr).toOutput('What’s your project’s name?');
|
||||
client.stdin.write('\r');
|
||||
await expect(client.stderr).toOutput(
|
||||
'In which directory is your code located?'
|
||||
);
|
||||
client.stdin.write('\r');
|
||||
await expect(client.stderr).toOutput('Want to modify these settings?');
|
||||
client.stdin.write('n\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
'Found local Git remote "origin": https://github.com/user/repo.git'
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
'Do you want to connect "origin" to your Vercel project?'
|
||||
);
|
||||
client.stdin.write('\r');
|
||||
await expect(client.stderr).toOutput(
|
||||
'Connected GitHub repository user/repo!'
|
||||
);
|
||||
await expect(client.stderr).toOutput('Linked to');
|
||||
await expect(linkPromise).resolves.toEqual(0);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
|
||||
it('should prompt to connect an existing project with a single remote to git', async () => {
|
||||
const cwd = fixture('single-remote');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useProject({
|
||||
...defaultProject,
|
||||
name: 'single-remote',
|
||||
id: 'single-remote',
|
||||
});
|
||||
useTeams('team_dummy');
|
||||
const linkPromise = link(client);
|
||||
|
||||
await expect(client.stderr).toOutput('Set up');
|
||||
client.stdin.write('y\n');
|
||||
await expect(client.stderr).toOutput('Which scope');
|
||||
client.stdin.write('\r');
|
||||
await expect(client.stderr).toOutput('Found project');
|
||||
client.stdin.write('y\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
'Found local Git remote "origin": https://github.com/user/repo.git'
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
'Do you want to connect "origin" to your Vercel project?'
|
||||
);
|
||||
client.stdin.write('\r');
|
||||
await expect(client.stderr).toOutput(
|
||||
'Connected GitHub repository user/repo!'
|
||||
);
|
||||
await expect(client.stderr).toOutput('Linked to');
|
||||
|
||||
await expect(linkPromise).resolves.toEqual(0);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should prompt to replace a connected repository if there is one remote', async () => {
|
||||
const cwd = fixture('single-remote-existing-link');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
const project = useProject({
|
||||
...defaultProject,
|
||||
name: 'single-remote-existing-link',
|
||||
id: 'single-remote-existing-link',
|
||||
});
|
||||
useTeams('team_dummy');
|
||||
project.project.link = {
|
||||
type: 'github',
|
||||
org: 'user',
|
||||
repo: 'repo',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
};
|
||||
|
||||
const linkPromise = link(client);
|
||||
|
||||
await expect(client.stderr).toOutput('Set up');
|
||||
client.stdin.write('y\n');
|
||||
await expect(client.stderr).toOutput('Which scope');
|
||||
client.stdin.write('\r');
|
||||
await expect(client.stderr).toOutput('Found project');
|
||||
client.stdin.write('y\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Found Git remote URL https://github.com/user2/repo2.git, which is different from the connected GitHub repository user/repo.`
|
||||
);
|
||||
await expect(client.stderr).toOutput('Do you want to replace it?');
|
||||
client.stdin.write('\r');
|
||||
await expect(client.stderr).toOutput(
|
||||
'Connected GitHub repository user2/repo2!'
|
||||
);
|
||||
await expect(client.stderr).toOutput('Linked to');
|
||||
|
||||
await expect(linkPromise).resolves.toEqual(0);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should prompt to connect an existing project with multiple remotes', async () => {
|
||||
const cwd = fixture('multiple-remotes');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
|
||||
useUser();
|
||||
useProject({
|
||||
...defaultProject,
|
||||
name: 'multiple-remotes',
|
||||
id: 'multiple-remotes',
|
||||
});
|
||||
useTeams('team_dummy');
|
||||
|
||||
const linkPromise = link(client);
|
||||
|
||||
await expect(client.stderr).toOutput('Set up');
|
||||
client.stdin.write('y\n');
|
||||
await expect(client.stderr).toOutput('Which scope');
|
||||
client.stdin.write('\r');
|
||||
await expect(client.stderr).toOutput('Found project');
|
||||
client.stdin.write('y\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`> Do you want to connect a Git repository to your Vercel project?`
|
||||
);
|
||||
client.stdin.write('\r');
|
||||
await expect(client.stderr).toOutput(
|
||||
'Connected GitHub repository user/repo!'
|
||||
);
|
||||
await expect(client.stderr).toOutput('Linked to');
|
||||
|
||||
await expect(linkPromise).resolves.toEqual(0);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should not prompt to replace a connected repository if there is more than one remote', async () => {
|
||||
const cwd = fixture('multiple-remotes');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
|
||||
useUser();
|
||||
const project = useProject({
|
||||
...defaultProject,
|
||||
name: 'multiple-remotes',
|
||||
id: 'multiple-remotes',
|
||||
});
|
||||
useTeams('team_dummy');
|
||||
project.project.link = {
|
||||
type: 'github',
|
||||
org: 'user',
|
||||
repo: 'repo',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
};
|
||||
|
||||
const linkPromise = link(client);
|
||||
|
||||
await expect(client.stderr).toOutput('Set up');
|
||||
client.stdin.write('y\n');
|
||||
await expect(client.stderr).toOutput('Which scope');
|
||||
client.stdin.write('\r');
|
||||
await expect(client.stderr).toOutput('Found project');
|
||||
client.stdin.write('y\n');
|
||||
|
||||
expect(client.stderr).not.toOutput('Found multiple Git remote URLs');
|
||||
await expect(client.stderr).toOutput('Linked to');
|
||||
|
||||
await expect(linkPromise).resolves.toEqual(0);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should set a project setting if user opts out', async () => {
|
||||
const cwd = fixture('single-remote');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
|
||||
useUser();
|
||||
useProject({
|
||||
...defaultProject,
|
||||
name: 'single-remote',
|
||||
id: 'single-remote',
|
||||
});
|
||||
useTeams('team_dummy');
|
||||
const linkPromise = link(client);
|
||||
|
||||
await expect(client.stderr).toOutput('Set up');
|
||||
client.stdin.write('y\n');
|
||||
await expect(client.stderr).toOutput('Which scope');
|
||||
client.stdin.write('\r');
|
||||
await expect(client.stderr).toOutput('Found project');
|
||||
client.stdin.write('y\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
'Found local Git remote "origin": https://github.com/user/repo.git'
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
'Do you want to connect "origin" to your Vercel project?'
|
||||
);
|
||||
client.stdin.write('\x1B[B'); // Down arrow
|
||||
client.stdin.write('\x1B[B');
|
||||
client.stdin.write('\r'); // Opt out
|
||||
|
||||
await expect(client.stderr).toOutput(`Opted out.`);
|
||||
await expect(client.stderr).toOutput('Linked to');
|
||||
await expect(linkPromise).resolves.toEqual(0);
|
||||
|
||||
const newProjectData: Project = await client.fetch(
|
||||
`/v8/projects/single-remote`
|
||||
);
|
||||
expect(newProjectData.skipGitConnectDuringLink).toBeTruthy();
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should not prompt to connect git if the project has skipGitConnectDuringLink property', async () => {
|
||||
const cwd = fixture('single-remote');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
|
||||
useUser();
|
||||
const project = useProject({
|
||||
...defaultProject,
|
||||
name: 'single-remote',
|
||||
id: 'single-remote',
|
||||
});
|
||||
useTeams('team_dummy');
|
||||
project.project.skipGitConnectDuringLink = true;
|
||||
const linkPromise = link(client);
|
||||
|
||||
await expect(client.stderr).toOutput('Set up');
|
||||
client.stdin.write('y\n');
|
||||
await expect(client.stderr).toOutput('Which scope');
|
||||
client.stdin.write('\r');
|
||||
await expect(client.stderr).toOutput('Found project');
|
||||
client.stdin.write('y\n');
|
||||
|
||||
expect(client.stderr).not.toOutput('Found local Git remote "origin"');
|
||||
|
||||
await expect(client.stderr).toOutput('Linked to');
|
||||
await expect(linkPromise).resolves.toEqual(0);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,9 @@
|
||||
import { client } from '../../mocks/client';
|
||||
import { useUser } from '../../mocks/user';
|
||||
import list, { stateString } from '../../../src/commands/list';
|
||||
import list, {
|
||||
getDeploymentDuration,
|
||||
stateString,
|
||||
} from '../../../src/commands/list';
|
||||
import { join } from 'path';
|
||||
import { useTeams } from '../../mocks/team';
|
||||
import { defaultProject, useProject } from '../../mocks/project';
|
||||
@@ -16,7 +19,7 @@ const fixture = (name: string) =>
|
||||
|
||||
describe('list', () => {
|
||||
const originalCwd = process.cwd();
|
||||
let teamSlug: string = '';
|
||||
let teamSlug: string;
|
||||
|
||||
it('should get deployments from a project linked by a directory', async () => {
|
||||
const cwd = fixture('with-team');
|
||||
@@ -35,32 +38,33 @@ describe('list', () => {
|
||||
|
||||
await list(client);
|
||||
|
||||
const output = await readOutputStream(client);
|
||||
const output = await readOutputStream(client, 4);
|
||||
|
||||
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[0]);
|
||||
const header: string[] = parseSpacedTableRow(output.split('\n')[2]);
|
||||
const data: string[] = parseSpacedTableRow(output.split('\n')[3]);
|
||||
data.splice(2, 1);
|
||||
const header: string[] = parseSpacedTableRow(output.split('\n')[3]);
|
||||
const data: string[] = parseSpacedTableRow(output.split('\n')[4]);
|
||||
data.shift();
|
||||
|
||||
expect(org).toEqual(team[0].slug);
|
||||
expect(header).toEqual([
|
||||
'project',
|
||||
'latest deployment',
|
||||
'state',
|
||||
'age',
|
||||
'username',
|
||||
'Age',
|
||||
'Deployment',
|
||||
'Status',
|
||||
'Duration',
|
||||
'Username',
|
||||
]);
|
||||
|
||||
expect(data).toEqual([
|
||||
deployment.url,
|
||||
`https://${deployment.url}`,
|
||||
stateString(deployment.state || ''),
|
||||
user.name,
|
||||
getDeploymentDuration(deployment),
|
||||
user.username,
|
||||
]);
|
||||
} finally {
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should get the deployments for a specified project', async () => {
|
||||
it('should get deployments for linked project where the scope is a user', async () => {
|
||||
const cwd = fixture('with-team');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
@@ -74,29 +78,65 @@ describe('list', () => {
|
||||
});
|
||||
const deployment = useDeployment({ creator: user });
|
||||
|
||||
client.setArgv('-S', user.username);
|
||||
await list(client);
|
||||
|
||||
const output = await readOutputStream(client, 4);
|
||||
|
||||
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[0]);
|
||||
const header: string[] = parseSpacedTableRow(output.split('\n')[3]);
|
||||
const data: string[] = parseSpacedTableRow(output.split('\n')[4]);
|
||||
data.shift();
|
||||
|
||||
expect(org).toEqual(user.username);
|
||||
expect(header).toEqual(['Age', 'Deployment', 'Status', 'Duration']);
|
||||
expect(data).toEqual([
|
||||
'https://' + deployment.url,
|
||||
stateString(deployment.state || ''),
|
||||
getDeploymentDuration(deployment),
|
||||
]);
|
||||
} finally {
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should get the deployments for a specified project', async () => {
|
||||
const cwd = fixture('with-team');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
|
||||
const user = useUser();
|
||||
const team = useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'with-team',
|
||||
name: 'with-team',
|
||||
});
|
||||
const deployment = useDeployment({ creator: user });
|
||||
|
||||
client.setArgv(deployment.name);
|
||||
await list(client);
|
||||
|
||||
const output = await readOutputStream(client);
|
||||
const output = await readOutputStream(client, 4);
|
||||
|
||||
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[0]);
|
||||
const header: string[] = parseSpacedTableRow(output.split('\n')[2]);
|
||||
const data: string[] = parseSpacedTableRow(output.split('\n')[3]);
|
||||
data.splice(2, 1);
|
||||
const header: string[] = parseSpacedTableRow(output.split('\n')[3]);
|
||||
const data: string[] = parseSpacedTableRow(output.split('\n')[4]);
|
||||
data.shift();
|
||||
|
||||
expect(org).toEqual(teamSlug);
|
||||
expect(org).toEqual(teamSlug || team[0].slug);
|
||||
|
||||
expect(header).toEqual([
|
||||
'project',
|
||||
'latest deployment',
|
||||
'state',
|
||||
'age',
|
||||
'username',
|
||||
'Age',
|
||||
'Deployment',
|
||||
'Status',
|
||||
'Duration',
|
||||
'Username',
|
||||
]);
|
||||
expect(data).toEqual([
|
||||
deployment.url,
|
||||
`https://${deployment.url}`,
|
||||
stateString(deployment.state || ''),
|
||||
user.name,
|
||||
getDeploymentDuration(deployment),
|
||||
user.username,
|
||||
]);
|
||||
} finally {
|
||||
process.chdir(originalCwd);
|
||||
|
||||
@@ -12,8 +12,9 @@ import {
|
||||
|
||||
describe('project', () => {
|
||||
describe('list', () => {
|
||||
it('should list deployments under a user', async () => {
|
||||
it('should list projects', async () => {
|
||||
const user = useUser();
|
||||
useTeams('team_dummy');
|
||||
const project = useProject({
|
||||
...defaultProject,
|
||||
});
|
||||
@@ -28,17 +29,21 @@ describe('project', () => {
|
||||
data.pop();
|
||||
|
||||
expect(org).toEqual(user.username);
|
||||
expect(header).toEqual(['name', 'updated']);
|
||||
expect(data).toEqual([project.project.name]);
|
||||
expect(header).toEqual([
|
||||
'Project Name',
|
||||
'Latest Production URL',
|
||||
'Updated',
|
||||
]);
|
||||
expect(data).toEqual([project.project.name, 'https://foobar.com']);
|
||||
});
|
||||
it('should list deployments for a team', async () => {
|
||||
useUser();
|
||||
const team = useTeams('team_dummy');
|
||||
it('should list projects when there is no production deployment', async () => {
|
||||
const user = useUser();
|
||||
useTeams('team_dummy');
|
||||
defaultProject.alias = [];
|
||||
const project = useProject({
|
||||
...defaultProject,
|
||||
});
|
||||
|
||||
client.config.currentTeam = team[0].id;
|
||||
client.setArgv('project', 'ls');
|
||||
await projects(client);
|
||||
|
||||
@@ -48,9 +53,13 @@ describe('project', () => {
|
||||
const data: string[] = parseSpacedTableRow(output.split('\n')[3]);
|
||||
data.pop();
|
||||
|
||||
expect(org).toEqual(team[0].slug);
|
||||
expect(header).toEqual(['name', 'updated']);
|
||||
expect(data).toEqual([project.project.name]);
|
||||
expect(org).toEqual(user.username);
|
||||
expect(header).toEqual([
|
||||
'Project Name',
|
||||
'Latest Production URL',
|
||||
'Updated',
|
||||
]);
|
||||
expect(data).toEqual([project.project.name, '--']);
|
||||
});
|
||||
});
|
||||
describe('add', () => {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
isDirty,
|
||||
} from '../../../../src/util/create-git-meta';
|
||||
import { client } from '../../../mocks/client';
|
||||
import { parseRepoUrl } from '../../../../src/util/projects/connect-git-provider';
|
||||
import { parseRepoUrl } from '../../../../src/util/git/connect-git-provider';
|
||||
import { readOutputStream } from '../../../helpers/read-output-stream';
|
||||
import { useUser } from '../../../mocks/user';
|
||||
import { defaultProject, useProject } from '../../../mocks/project';
|
||||
@@ -58,92 +58,104 @@ describe('getRemoteUrls', () => {
|
||||
|
||||
describe('parseRepoUrl', () => {
|
||||
it('should be null when a url does not match the regex', () => {
|
||||
const parsedUrl = parseRepoUrl('https://examplecom/foo');
|
||||
expect(parsedUrl).toBeNull();
|
||||
const repoInfo = parseRepoUrl('https://examplecom/foo');
|
||||
expect(repoInfo).toBeNull();
|
||||
});
|
||||
it('should be null when a url does not contain org and repo data', () => {
|
||||
const parsedUrl = parseRepoUrl('https://github.com/borked');
|
||||
expect(parsedUrl).toBeNull();
|
||||
const repoInfo = parseRepoUrl('https://github.com/borked');
|
||||
expect(repoInfo).toBeNull();
|
||||
});
|
||||
it('should parse url with a period in the repo name', () => {
|
||||
const parsedUrl = parseRepoUrl('https://github.com/vercel/next.js');
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('vercel');
|
||||
expect(parsedUrl?.repo).toEqual('next.js');
|
||||
const repoInfo = parseRepoUrl('https://github.com/vercel/next.js');
|
||||
expect(repoInfo).toBeDefined();
|
||||
expect(repoInfo?.provider).toEqual('github');
|
||||
expect(repoInfo?.org).toEqual('vercel');
|
||||
expect(repoInfo?.repo).toEqual('next.js');
|
||||
});
|
||||
it('should parse url that ends with .git', () => {
|
||||
const parsedUrl = parseRepoUrl('https://github.com/vercel/next.js.git');
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('vercel');
|
||||
expect(parsedUrl?.repo).toEqual('next.js');
|
||||
const repoInfo = parseRepoUrl('https://github.com/vercel/next.js.git');
|
||||
expect(repoInfo).toBeDefined();
|
||||
expect(repoInfo?.provider).toEqual('github');
|
||||
expect(repoInfo?.org).toEqual('vercel');
|
||||
expect(repoInfo?.repo).toEqual('next.js');
|
||||
});
|
||||
it('should parse github https url', () => {
|
||||
const parsedUrl = parseRepoUrl('https://github.com/vercel/vercel.git');
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('vercel');
|
||||
expect(parsedUrl?.repo).toEqual('vercel');
|
||||
const repoInfo = parseRepoUrl('https://github.com/vercel/vercel.git');
|
||||
expect(repoInfo).toBeDefined();
|
||||
expect(repoInfo?.provider).toEqual('github');
|
||||
expect(repoInfo?.org).toEqual('vercel');
|
||||
expect(repoInfo?.repo).toEqual('vercel');
|
||||
});
|
||||
it('should parse github https url without the .git suffix', () => {
|
||||
const parsedUrl = parseRepoUrl('https://github.com/vercel/vercel');
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('vercel');
|
||||
expect(parsedUrl?.repo).toEqual('vercel');
|
||||
const repoInfo = parseRepoUrl('https://github.com/vercel/vercel');
|
||||
expect(repoInfo).toBeDefined();
|
||||
expect(repoInfo?.provider).toEqual('github');
|
||||
expect(repoInfo?.org).toEqual('vercel');
|
||||
expect(repoInfo?.repo).toEqual('vercel');
|
||||
});
|
||||
it('should parse github git url', () => {
|
||||
const parsedUrl = parseRepoUrl('git://github.com/vercel/vercel.git');
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('vercel');
|
||||
expect(parsedUrl?.repo).toEqual('vercel');
|
||||
const repoInfo = parseRepoUrl('git://github.com/vercel/vercel.git');
|
||||
expect(repoInfo).toBeDefined();
|
||||
expect(repoInfo?.provider).toEqual('github');
|
||||
expect(repoInfo?.org).toEqual('vercel');
|
||||
expect(repoInfo?.repo).toEqual('vercel');
|
||||
});
|
||||
it('should parse github ssh url', () => {
|
||||
const parsedUrl = parseRepoUrl('git@github.com:vercel/vercel.git');
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('vercel');
|
||||
expect(parsedUrl?.repo).toEqual('vercel');
|
||||
const repoInfo = parseRepoUrl('git@github.com:vercel/vercel.git');
|
||||
expect(repoInfo).toBeDefined();
|
||||
expect(repoInfo?.provider).toEqual('github');
|
||||
expect(repoInfo?.org).toEqual('vercel');
|
||||
expect(repoInfo?.repo).toEqual('vercel');
|
||||
});
|
||||
|
||||
it('should parse gitlab https url', () => {
|
||||
const parsedUrl = parseRepoUrl(
|
||||
const repoInfo = parseRepoUrl(
|
||||
'https://gitlab.com/gitlab-examples/knative-kotlin-app.git'
|
||||
);
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('gitlab');
|
||||
expect(parsedUrl?.org).toEqual('gitlab-examples');
|
||||
expect(parsedUrl?.repo).toEqual('knative-kotlin-app');
|
||||
expect(repoInfo).toBeDefined();
|
||||
expect(repoInfo?.provider).toEqual('gitlab');
|
||||
expect(repoInfo?.org).toEqual('gitlab-examples');
|
||||
expect(repoInfo?.repo).toEqual('knative-kotlin-app');
|
||||
});
|
||||
it('should parse gitlab ssh url', () => {
|
||||
const parsedUrl = parseRepoUrl(
|
||||
const repoInfo = parseRepoUrl(
|
||||
'git@gitlab.com:gitlab-examples/knative-kotlin-app.git'
|
||||
);
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('gitlab');
|
||||
expect(parsedUrl?.org).toEqual('gitlab-examples');
|
||||
expect(parsedUrl?.repo).toEqual('knative-kotlin-app');
|
||||
expect(repoInfo).toBeDefined();
|
||||
expect(repoInfo?.provider).toEqual('gitlab');
|
||||
expect(repoInfo?.org).toEqual('gitlab-examples');
|
||||
expect(repoInfo?.repo).toEqual('knative-kotlin-app');
|
||||
});
|
||||
|
||||
it('should parse bitbucket https url', () => {
|
||||
const parsedUrl = parseRepoUrl(
|
||||
const repoInfo = parseRepoUrl(
|
||||
'https://bitbucket.org/atlassianlabs/maven-project-example.git'
|
||||
);
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('bitbucket');
|
||||
expect(parsedUrl?.org).toEqual('atlassianlabs');
|
||||
expect(parsedUrl?.repo).toEqual('maven-project-example');
|
||||
expect(repoInfo).toBeDefined();
|
||||
expect(repoInfo?.provider).toEqual('bitbucket');
|
||||
expect(repoInfo?.org).toEqual('atlassianlabs');
|
||||
expect(repoInfo?.repo).toEqual('maven-project-example');
|
||||
});
|
||||
it('should parse bitbucket ssh url', () => {
|
||||
const parsedUrl = parseRepoUrl(
|
||||
const repoInfo = parseRepoUrl(
|
||||
'git@bitbucket.org:atlassianlabs/maven-project-example.git'
|
||||
);
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('bitbucket');
|
||||
expect(parsedUrl?.org).toEqual('atlassianlabs');
|
||||
expect(parsedUrl?.repo).toEqual('maven-project-example');
|
||||
expect(repoInfo).toBeDefined();
|
||||
expect(repoInfo?.provider).toEqual('bitbucket');
|
||||
expect(repoInfo?.org).toEqual('atlassianlabs');
|
||||
expect(repoInfo?.repo).toEqual('maven-project-example');
|
||||
});
|
||||
it('should parse url without a scheme', () => {
|
||||
const parsedUrl = parseRepoUrl('github.com/user/repo');
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('user');
|
||||
expect(parsedUrl?.repo).toEqual('repo');
|
||||
});
|
||||
it('should parse a url that includes www.', () => {
|
||||
const parsedUrl = parseRepoUrl('www.github.com/user/repo');
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('user');
|
||||
expect(parsedUrl?.repo).toEqual('repo');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
46
packages/cli/test/unit/util/dev/parse-query-string.test.ts
Normal file
46
packages/cli/test/unit/util/dev/parse-query-string.test.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
parseQueryString,
|
||||
formatQueryString,
|
||||
} from '../../../../src/util/dev/parse-query-string';
|
||||
|
||||
describe('parseQueryString', () => {
|
||||
it('should parse to Map and format back to original String', async () => {
|
||||
const querystring =
|
||||
'?a&a=&a&b=1&c=2&c=3&d=&d&d=&space%20bar=4&html=%3Ch1%3E';
|
||||
const parsed = parseQueryString(querystring);
|
||||
expect(parsed).toEqual({
|
||||
a: [undefined, '', undefined],
|
||||
b: ['1'],
|
||||
c: ['2', '3'],
|
||||
d: ['', undefined, ''],
|
||||
'space bar': ['4'],
|
||||
html: ['<h1>'],
|
||||
});
|
||||
const format = formatQueryString(parsed);
|
||||
expect(format).toEqual(querystring);
|
||||
});
|
||||
it('should work with empty string', async () => {
|
||||
const parsed = parseQueryString('');
|
||||
expect(parsed).toEqual({});
|
||||
const format = formatQueryString(parsed);
|
||||
expect(format).toEqual(undefined);
|
||||
});
|
||||
it('should work with question mark', async () => {
|
||||
const parsed = parseQueryString('?');
|
||||
expect(parsed).toEqual({});
|
||||
const format = formatQueryString(parsed);
|
||||
expect(format).toEqual(undefined);
|
||||
});
|
||||
it('should work without question mark', async () => {
|
||||
const parsed = parseQueryString('blarg');
|
||||
expect(parsed).toEqual({});
|
||||
const format = formatQueryString(parsed);
|
||||
expect(format).toEqual(undefined);
|
||||
});
|
||||
it('should work with undefined', async () => {
|
||||
const parsed = parseQueryString(undefined);
|
||||
expect(parsed).toEqual({});
|
||||
const format = formatQueryString(parsed);
|
||||
expect(format).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -17,7 +17,7 @@ describe('devRouter', () => {
|
||||
continue: false,
|
||||
status: 301,
|
||||
headers: { location: 'https://vercel.com' },
|
||||
uri_args: {},
|
||||
query: {},
|
||||
matched_route: routesConfig[0],
|
||||
matched_route_idx: 0,
|
||||
userDest: false,
|
||||
@@ -36,7 +36,7 @@ describe('devRouter', () => {
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
query: {},
|
||||
matched_route: routesConfig[0],
|
||||
matched_route_idx: 0,
|
||||
userDest: true,
|
||||
@@ -55,7 +55,7 @@ describe('devRouter', () => {
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: { id: '123' },
|
||||
query: { id: ['123'] },
|
||||
matched_route: routesConfig[0],
|
||||
matched_route_idx: 0,
|
||||
userDest: true,
|
||||
@@ -79,7 +79,7 @@ describe('devRouter', () => {
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: { name: '' },
|
||||
query: { name: [''] },
|
||||
matched_route: routesConfig[0],
|
||||
matched_route_idx: 0,
|
||||
userDest: true,
|
||||
@@ -99,7 +99,7 @@ describe('devRouter', () => {
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
query: {},
|
||||
matched_route: routesConfig[0],
|
||||
matched_route_idx: 0,
|
||||
userDest: false,
|
||||
@@ -121,7 +121,7 @@ describe('devRouter', () => {
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
query: {},
|
||||
matched_route: routesConfig[1],
|
||||
matched_route_idx: 1,
|
||||
userDest: true,
|
||||
@@ -136,7 +136,7 @@ describe('devRouter', () => {
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
query: {},
|
||||
matched_route: routesConfig[0],
|
||||
matched_route_idx: 0,
|
||||
userDest: true,
|
||||
@@ -155,7 +155,7 @@ describe('devRouter', () => {
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
query: {},
|
||||
matched_route: routesConfig[0],
|
||||
matched_route_idx: 0,
|
||||
userDest: true,
|
||||
@@ -182,7 +182,7 @@ describe('devRouter', () => {
|
||||
phase: undefined,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
query: {},
|
||||
matched_route: {
|
||||
src: '^\\/([^\\/]+?)\\/comments(?:\\/)?$',
|
||||
dest: '/some/dest',
|
||||
@@ -214,7 +214,7 @@ describe('devRouter', () => {
|
||||
isDestUrl: false,
|
||||
phase: undefined,
|
||||
status: undefined,
|
||||
uri_args: {},
|
||||
query: {},
|
||||
headers: {
|
||||
'cache-control': 'immutable,max-age=31536000',
|
||||
},
|
||||
@@ -249,7 +249,7 @@ describe('devRouter', () => {
|
||||
userDest: true,
|
||||
isDestUrl: false,
|
||||
phase: undefined,
|
||||
uri_args: {},
|
||||
query: {},
|
||||
headers: {
|
||||
'cache-control': 'immutable,max-age=31536000',
|
||||
},
|
||||
@@ -274,7 +274,7 @@ describe('devRouter', () => {
|
||||
phase: undefined,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
query: {},
|
||||
matched_route: { src: '/(.*)', dest: '/www/$1' },
|
||||
matched_route_idx: 0,
|
||||
});
|
||||
@@ -293,7 +293,7 @@ describe('devRouter', () => {
|
||||
phase: undefined,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
query: {},
|
||||
matched_route: { src: '(.*)', dest: '/www$1' },
|
||||
matched_route_idx: 0,
|
||||
});
|
||||
@@ -315,7 +315,7 @@ describe('devRouter', () => {
|
||||
continue: false,
|
||||
status: undefined,
|
||||
headers: {},
|
||||
uri_args: {},
|
||||
query: {},
|
||||
matched_route: routesConfig[1],
|
||||
matched_route_idx: 1,
|
||||
userDest: false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/client",
|
||||
"version": "12.1.11",
|
||||
"version": "12.2.0",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://vercel.com",
|
||||
@@ -31,6 +31,7 @@
|
||||
"@types/node": "12.0.4",
|
||||
"@types/node-fetch": "2.5.4",
|
||||
"@types/recursive-readdir": "2.2.0",
|
||||
"@types/tar-fs": "^2.0.1",
|
||||
"typescript": "4.3.4"
|
||||
},
|
||||
"jest": {
|
||||
@@ -42,7 +43,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "5.3.0",
|
||||
"@vercel/build-utils": "5.3.1",
|
||||
"@vercel/routing-utils": "2.0.2",
|
||||
"@zeit/fetch": "5.2.0",
|
||||
"async-retry": "1.2.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { lstatSync } from 'fs-extra';
|
||||
import { isAbsolute } from 'path';
|
||||
import { hashes, mapToObject } from './utils/hashes';
|
||||
import { isAbsolute, join, relative } from 'path';
|
||||
import { hash, hashes, mapToObject } from './utils/hashes';
|
||||
import { upload } from './upload';
|
||||
import { buildFileTree, createDebug } from './utils';
|
||||
import { DeploymentError } from './errors';
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
DeploymentOptions,
|
||||
DeploymentEventType,
|
||||
} from './types';
|
||||
import { streamToBuffer } from '@vercel/build-utils';
|
||||
import tar from 'tar-fs';
|
||||
import { createGzip } from 'zlib';
|
||||
|
||||
export default function buildCreateDeployment() {
|
||||
return async function* createDeployment(
|
||||
@@ -70,7 +73,7 @@ export default function buildCreateDeployment() {
|
||||
debug(`Provided 'path' is a single file`);
|
||||
}
|
||||
|
||||
const { fileList } = await buildFileTree(path, clientOptions, debug);
|
||||
let { fileList } = await buildFileTree(path, clientOptions, debug);
|
||||
|
||||
// This is a useful warning because it prevents people
|
||||
// from getting confused about a deployment that renders 404.
|
||||
@@ -82,7 +85,33 @@ export default function buildCreateDeployment() {
|
||||
};
|
||||
}
|
||||
|
||||
const files = await hashes(fileList);
|
||||
// Populate Files -> FileFsRef mapping
|
||||
const workPath = typeof path === 'string' ? path : path[0];
|
||||
|
||||
let files;
|
||||
|
||||
if (clientOptions.archive === 'tgz') {
|
||||
debug('Packing tarball');
|
||||
const tarStream = tar
|
||||
.pack(workPath, {
|
||||
entries: fileList.map(file => relative(workPath, file)),
|
||||
})
|
||||
.pipe(createGzip());
|
||||
const tarBuffer = await streamToBuffer(tarStream);
|
||||
debug('Packed tarball');
|
||||
files = new Map([
|
||||
[
|
||||
hash(tarBuffer),
|
||||
{
|
||||
names: [join(workPath, '.vercel/source.tgz')],
|
||||
data: tarBuffer,
|
||||
mode: 0o666,
|
||||
},
|
||||
],
|
||||
]);
|
||||
} else {
|
||||
files = await hashes(fileList);
|
||||
}
|
||||
|
||||
debug(`Yielding a 'hashes-calculated' event with ${files.size} hashes`);
|
||||
yield { type: 'hashes-calculated', payload: mapToObject(files) };
|
||||
|
||||
@@ -11,6 +11,9 @@ export interface Dictionary<T> {
|
||||
[key: string]: T;
|
||||
}
|
||||
|
||||
export const VALID_ARCHIVE_FORMATS = ['tgz'] as const;
|
||||
export type ArchiveFormat = typeof VALID_ARCHIVE_FORMATS[number];
|
||||
|
||||
export interface VercelClientOptions {
|
||||
token: string;
|
||||
path: string | string[];
|
||||
@@ -25,6 +28,7 @@ export interface VercelClientOptions {
|
||||
defaultName?: string;
|
||||
isDirectory?: boolean;
|
||||
skipAutoDetectionConfirmation?: boolean;
|
||||
archive?: ArchiveFormat;
|
||||
}
|
||||
|
||||
/** @deprecated Use VercelClientOptions instead. */
|
||||
@@ -37,6 +41,7 @@ export interface Deployment {
|
||||
id: string;
|
||||
deploymentId?: string;
|
||||
url: string;
|
||||
inspectorUrl: string;
|
||||
name: string;
|
||||
meta: Dictionary<string | number | boolean>;
|
||||
version: 2;
|
||||
@@ -65,12 +70,14 @@ export interface Deployment {
|
||||
| 'QUEUED'
|
||||
| 'CANCELED'
|
||||
| 'ERROR';
|
||||
ready?: number;
|
||||
createdAt: number;
|
||||
createdIn: string;
|
||||
buildingAt?: number;
|
||||
creator?: {
|
||||
uid?: string;
|
||||
email?: string;
|
||||
name?: string;
|
||||
username?: string;
|
||||
};
|
||||
env: Dictionary<string>;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Agent } from 'https';
|
||||
import { Readable } from 'stream';
|
||||
import { EventEmitter } from 'events';
|
||||
import retry from 'async-retry';
|
||||
import { Sema } from 'async-sema';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { DeploymentFile } from './utils/hashes';
|
||||
import { fetch, API_FILES, createDebug } from './utils';
|
||||
@@ -9,7 +10,7 @@ import { DeploymentError } from './errors';
|
||||
import { deploy } from './deploy';
|
||||
import { VercelClientOptions, DeploymentOptions } from './types';
|
||||
|
||||
const isClientNetworkError = (err: Error | DeploymentError) => {
|
||||
const isClientNetworkError = (err: Error) => {
|
||||
if (err.message) {
|
||||
// These are common network errors that may happen occasionally and we should retry if we encounter these
|
||||
return (
|
||||
@@ -39,16 +40,16 @@ export async function* upload(
|
||||
return;
|
||||
}
|
||||
|
||||
let missingFiles = [];
|
||||
let shas: string[] = [];
|
||||
|
||||
debug('Determining necessary files for upload...');
|
||||
|
||||
for await (const event of deploy(files, clientOptions, deploymentOptions)) {
|
||||
if (event.type === 'error') {
|
||||
if (event.payload.code === 'missing_files') {
|
||||
missingFiles = event.payload.missing;
|
||||
shas = event.payload.missing;
|
||||
|
||||
debug(`${missingFiles.length} files are required to upload`);
|
||||
debug(`${shas.length} files are required to upload`);
|
||||
} else {
|
||||
return yield event;
|
||||
}
|
||||
@@ -64,9 +65,14 @@ export async function* upload(
|
||||
}
|
||||
}
|
||||
|
||||
const shas = missingFiles;
|
||||
const uploads = shas.map(sha => {
|
||||
return new UploadProgress(sha, files.get(sha)!);
|
||||
});
|
||||
|
||||
yield { type: 'file-count', payload: { total: files, missing: shas } };
|
||||
yield {
|
||||
type: 'file-count',
|
||||
payload: { total: files, missing: shas, uploads },
|
||||
};
|
||||
|
||||
const uploadList: { [key: string]: Promise<any> } = {};
|
||||
debug('Building an upload list...');
|
||||
@@ -74,7 +80,9 @@ export async function* upload(
|
||||
const semaphore = new Sema(50, { capacity: 50 });
|
||||
const agent = new Agent({ keepAlive: true });
|
||||
|
||||
shas.map((sha: string): void => {
|
||||
shas.forEach((sha, index) => {
|
||||
const uploadProgress = uploads[index];
|
||||
|
||||
uploadList[sha] = retry(
|
||||
async (bail): Promise<any> => {
|
||||
const file = files.get(sha);
|
||||
@@ -86,19 +94,29 @@ export async function* upload(
|
||||
|
||||
await semaphore.acquire();
|
||||
|
||||
const fPath = file.names[0];
|
||||
|
||||
let body: fs.ReadStream | string | null = null;
|
||||
|
||||
const stat = await fs.lstat(fPath);
|
||||
if (stat.isSymbolicLink()) {
|
||||
body = await fs.readlink(fPath);
|
||||
} else {
|
||||
body = fs.createReadStream(fPath);
|
||||
}
|
||||
|
||||
const { data } = file;
|
||||
|
||||
uploadProgress.bytesUploaded = 0;
|
||||
|
||||
// Split out into chunks
|
||||
const body = new Readable();
|
||||
const originalRead = body.read.bind(body);
|
||||
body.read = function (...args) {
|
||||
const chunk = originalRead(...args);
|
||||
if (chunk) {
|
||||
uploadProgress.bytesUploaded += chunk.length;
|
||||
uploadProgress.emit('progress');
|
||||
}
|
||||
return chunk;
|
||||
};
|
||||
|
||||
const chunkSize = 16384; /* 16kb - default Node.js `highWaterMark` */
|
||||
for (let i = 0; i < data.length; i += chunkSize) {
|
||||
const chunk = data.slice(i, i + chunkSize);
|
||||
body.push(chunk);
|
||||
}
|
||||
body.push(null);
|
||||
|
||||
let err;
|
||||
let result;
|
||||
|
||||
@@ -152,11 +170,6 @@ export async function* upload(
|
||||
} catch (e: any) {
|
||||
debug(`An unexpected error occurred in upload promise:\n${e}`);
|
||||
err = new Error(e);
|
||||
} finally {
|
||||
if (body && typeof body !== 'string') {
|
||||
body.close();
|
||||
body.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
semaphore.release();
|
||||
@@ -216,3 +229,15 @@ export async function* upload(
|
||||
yield { type: 'error', payload: e };
|
||||
}
|
||||
}
|
||||
|
||||
class UploadProgress extends EventEmitter {
|
||||
sha: string;
|
||||
file: DeploymentFile;
|
||||
bytesUploaded: number;
|
||||
constructor(sha: string, file: DeploymentFile) {
|
||||
super();
|
||||
this.sha = sha;
|
||||
this.file = file;
|
||||
this.bytesUploaded = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface DeploymentFile {
|
||||
* @param {Buffer} file data
|
||||
* @return {String} hex digest
|
||||
*/
|
||||
function hash(buf: Buffer): string {
|
||||
export function hash(buf: Buffer): string {
|
||||
return createHash('sha1').update(buf).digest('hex');
|
||||
}
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@ export async function build({
|
||||
}
|
||||
}
|
||||
|
||||
const mainModGoFileName = 'main.go';
|
||||
const mainModGoFileName = 'main__mod__.go';
|
||||
const modMainGoContents = await readFile(
|
||||
join(__dirname, mainModGoFileName),
|
||||
'utf8'
|
||||
@@ -449,13 +449,14 @@ export async function build({
|
||||
},
|
||||
false
|
||||
);
|
||||
const originalMainGoContents = await readFile(
|
||||
const origianlMainGoContents = await readFile(
|
||||
join(__dirname, 'main.go'),
|
||||
'utf8'
|
||||
);
|
||||
const mainGoContents = originalMainGoContents
|
||||
.replace('"__VC_HANDLER_PACKAGE_NAME"', '')
|
||||
.replace('__VC_HANDLER_FUNC_NAME', handlerFunctionName);
|
||||
const mainGoContents = origianlMainGoContents.replace(
|
||||
'__VC_HANDLER_FUNC_NAME',
|
||||
handlerFunctionName
|
||||
);
|
||||
|
||||
// in order to allow the user to have `main.go`,
|
||||
// we need our `main.go` to be called something else
|
||||
@@ -497,7 +498,6 @@ export async function build({
|
||||
files: { ...(await glob('**', outDir)), ...includedFiles },
|
||||
handler: handlerFileName,
|
||||
runtime: 'go1.x',
|
||||
supportsWrapper: true,
|
||||
environment: {},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,31 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"__VC_HANDLER_PACKAGE_NAME"
|
||||
vc "github.com/vercel/go-bridge/go/bridge"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func checkForLambdaWrapper() {
|
||||
wrapper := os.Getenv("AWS_LAMBDA_EXEC_WRAPPER")
|
||||
if wrapper == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Removing the env var doesn't work
|
||||
// Set it to empty string to override the previous value
|
||||
os.Setenv("AWS_LAMBDA_EXEC_WRAPPER", "")
|
||||
argv := append([]string{wrapper}, os.Args...)
|
||||
err := syscall.Exec(wrapper, argv, os.Environ())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
checkForLambdaWrapper()
|
||||
vc.Start(http.HandlerFunc(__VC_HANDLER_FUNC_NAME))
|
||||
}
|
||||
|
||||
12
packages/go/main__mod__.go
Normal file
12
packages/go/main__mod__.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"__VC_HANDLER_PACKAGE_NAME"
|
||||
"net/http"
|
||||
|
||||
vc "github.com/vercel/go-bridge/go/bridge"
|
||||
)
|
||||
|
||||
func main() {
|
||||
vc.Start(http.HandlerFunc(__VC_HANDLER_FUNC_NAME))
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/go",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
|
||||
@@ -35,7 +35,7 @@
|
||||
"@types/jest": "28.1.6",
|
||||
"@types/node-fetch": "^2.3.0",
|
||||
"@types/tar": "^4.0.0",
|
||||
"@vercel/build-utils": "5.3.0",
|
||||
"@vercel/build-utils": "5.3.1",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"async-retry": "1.3.1",
|
||||
"execa": "^1.0.0",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
module env
|
||||
@@ -1,19 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Handler function
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
rdm := os.Getenv("RANDOMNESS_ENV")
|
||||
if rdm == "" {
|
||||
fmt.Println("No env received")
|
||||
}
|
||||
|
||||
fmt.Fprintln(w, rdm)
|
||||
fmt.Fprintln(w, os.Getenv("LOREM"))
|
||||
fmt.Fprintln(w, os.Getenv("IPSUM"))
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [
|
||||
{
|
||||
"src": "env/index.go",
|
||||
"use": "@vercel/go"
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
"RANDOMNESS_ENV": "RANDOMNESS_PLACEHOLDER",
|
||||
"LOREM": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean turpis nisl, porta vel dictum id, placerat eu massa. Curabitur id diam at urna elementum condimentum a eget augue. Sed vehicula, mauris quis tincidunt iaculis, lacus quam dictum nulla, eu pellentesque justo lectus a erat. Integer volutpat magna tortor, non mollis tortor rhoncus quis. Donec id urna ligula. Praesent et ligula id ligula blandit rhoncus. Proin consequat, justo id maximus lacinia, tortor dui facilisis nunc, at aliquet odio orci nec tellus. Vestibulum sagittis nec sem id mollis. Donec eleifend risus eget lectus mattis convallis. Nam ac urna commodo, venenatis massa ut, varius magna. Aliquam erat volutpat. Ut ac lacinia erat. Mauris finibus vehicula elementum. Proin mauris neque, fringilla a erat fermentum, convallis elementum urna. Pellentesque bibendum nisl eget nisi sodales, a faucibus felis scelerisque. Fusce blandit imperdiet nunc, ac hendrerit ante placerat sed. Cras metus dolor, cursus non orci sed, iaculis tempor nunc. Quisque vitae enim pharetra, viverra massa non, mollis magna. Vivamus sit amet ultricies ligula, in vulputate sapien. Praesent ullamcorper justo in elit vulputate, et varius augue egestas. Donec quis rutrum mauris. Suspendisse placerat volutpat gravida. Nunc laoreet velit a accumsan faucibus. Nunc eu lorem sem. Sed id nunc a metus gravida accumsan. Morbi aliquet purus id ipsum dictum, nec finibus quam ullamcorper. Quisque sapien nulla, laoreet a accumsan non, luctus quis ante. Sed sit amet pellentesque magna. Aenean pulvinar porta est sed posuere. Aenean id nisl dictum, varius diam vel, facilisis ex. Praesent quis justo id mi eleifend eleifend. Aliquam imperdiet purus non ligula lobortis laoreet. Sed mollis aliquet dui et luctus. Donec ut lacus vel tellus porta feugiat. Nam lacinia euismod libero. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi facilisis quam nec nisl pretium, id blandit sapien pretium. Donec id sapien varius, ornare mi sed, pretium magna. Phasellus tortor ligula, porttitor sit amet magna in, semper condimentum elit.",
|
||||
"IPSUM": "Phasellus ac orci eleifend, dignissim turpis et, aliquet libero. Praesent aliquet justo augue, vel vulputate ex dictum ut. Donec eu interdum ex, sit amet hendrerit felis. Maecenas eget iaculis orci, eget porta eros. Pellentesque vitae neque in velit dapibus luctus. Pellentesque ornare et tellus eu congue. Aliquam eu sem vel neque varius faucibus. Ut eget tortor ornare, fermentum enim nec, pellentesque massa. Phasellus rhoncus aliquet nunc nec semper. Nullam sed iaculis tellus. Mauris a sollicitudin velit, id egestas odio. Suspendisse commodo commodo turpis, et sollicitudin sem commodo a. Vivamus condimentum, arcu ac tempus blandit, lectus ligula pulvinar est, in congue mi nunc et lacus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi sodales ipsum quis scelerisque vehicula. Quisque gravida nibh vitae mattis sollicitudin. Donec fringilla dapibus urna non gravida. Phasellus et eros id magna tristique consequat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In lacus neque, auctor sed arcu at, varius volutpat est. Maecenas eget ante sed ipsum sagittis laoreet ut nec nisi. Quisque scelerisque, risus ut efficitur sollicitudin, neque est faucibus lacus, vitae eleifend nulla sem a magna. Integer viverra, diam eget venenatis pretium, augue ex pulvinar justo, ac ultrices neque nisl laoreet risus. Pellentesque commodo ultrices laoreet. Nulla nec ipsum non augue hendrerit vulputate sed eget diam. Maecenas semper rutrum ligula. Sed egestas, orci sed volutpat varius, eros mi lacinia magna, tincidunt aliquet nibh lacus eget dui. Integer vestibulum velit in interdum ultrices. Mauris porta vitae quam non placerat. In nisi risus, hendrerit rhoncus hendrerit at, lacinia vel mauris. Curabitur tempus mattis eros nec consequat. Sed posuere elit lobortis libero porta, sed pharetra tortor ornare. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse pulvinar ante vitae metus ullamcorper euismod. Nulla facilisi. Donec quam nulla, eleifend vel consequat sed, maximus et nisi. Donec molestie euismod semper. Fusce eget arcu feugiat, efficitur lectus sed, feugiat justo. Mauris ultricies pretium ante non faucibus. Aenean egestas ante nunc, id pellentesque metus blandit eu. Nullam faucibus fringilla lectus, quis dapibus turpis elementum eu. Nunc eget dolor in velit molestie interdum id eu justo. Aliquam ornare arcu quis tincidunt posuere. Mauris sed porttitor ligula. Vestibulum tincidunt non lacus id lacinia. Donec ex augue, convallis vel justo vel, faucibus ultricies tortor."
|
||||
},
|
||||
"probes": [
|
||||
{
|
||||
"path": "/env",
|
||||
"mustContain": "RANDOMNESS_PLACEHOLDER"
|
||||
},
|
||||
{
|
||||
"path": "/env",
|
||||
"mustContain": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean turpis nisl, porta vel dictum id, placerat eu massa. Curabitur id diam at urna elementum condimentum a eget augue. Sed vehicula, mauris quis tincidunt iaculis, lacus quam dictum nulla, eu pellentesque justo lectus a erat. Integer volutpat magna tortor, non mollis tortor rhoncus quis. Donec id urna ligula. Praesent et ligula id ligula blandit rhoncus. Proin consequat, justo id maximus lacinia, tortor dui facilisis nunc, at aliquet odio orci nec tellus. Vestibulum sagittis nec sem id mollis. Donec eleifend risus eget lectus mattis convallis. Nam ac urna commodo, venenatis massa ut, varius magna. Aliquam erat volutpat. Ut ac lacinia erat. Mauris finibus vehicula elementum. Proin mauris neque, fringilla a erat fermentum, convallis elementum urna. Pellentesque bibendum nisl eget nisi sodales, a faucibus felis scelerisque. Fusce blandit imperdiet nunc, ac hendrerit ante placerat sed. Cras metus dolor, cursus non orci sed, iaculis tempor nunc. Quisque vitae enim pharetra, viverra massa non, mollis magna. Vivamus sit amet ultricies ligula, in vulputate sapien. Praesent ullamcorper justo in elit vulputate, et varius augue egestas. Donec quis rutrum mauris. Suspendisse placerat volutpat gravida. Nunc laoreet velit a accumsan faucibus. Nunc eu lorem sem. Sed id nunc a metus gravida accumsan. Morbi aliquet purus id ipsum dictum, nec finibus quam ullamcorper. Quisque sapien nulla, laoreet a accumsan non, luctus quis ante. Sed sit amet pellentesque magna. Aenean pulvinar porta est sed posuere. Aenean id nisl dictum, varius diam vel, facilisis ex. Praesent quis justo id mi eleifend eleifend. Aliquam imperdiet purus non ligula lobortis laoreet. Sed mollis aliquet dui et luctus. Donec ut lacus vel tellus porta feugiat. Nam lacinia euismod libero. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi facilisis quam nec nisl pretium, id blandit sapien pretium. Donec id sapien varius, ornare mi sed, pretium magna. Phasellus tortor ligula, porttitor sit amet magna in, semper condimentum elit."
|
||||
},
|
||||
{
|
||||
"path": "/env",
|
||||
"mustContain": "Phasellus ac orci eleifend, dignissim turpis et, aliquet libero. Praesent aliquet justo augue, vel vulputate ex dictum ut. Donec eu interdum ex, sit amet hendrerit felis. Maecenas eget iaculis orci, eget porta eros. Pellentesque vitae neque in velit dapibus luctus. Pellentesque ornare et tellus eu congue. Aliquam eu sem vel neque varius faucibus. Ut eget tortor ornare, fermentum enim nec, pellentesque massa. Phasellus rhoncus aliquet nunc nec semper. Nullam sed iaculis tellus. Mauris a sollicitudin velit, id egestas odio. Suspendisse commodo commodo turpis, et sollicitudin sem commodo a. Vivamus condimentum, arcu ac tempus blandit, lectus ligula pulvinar est, in congue mi nunc et lacus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi sodales ipsum quis scelerisque vehicula. Quisque gravida nibh vitae mattis sollicitudin. Donec fringilla dapibus urna non gravida. Phasellus et eros id magna tristique consequat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In lacus neque, auctor sed arcu at, varius volutpat est. Maecenas eget ante sed ipsum sagittis laoreet ut nec nisi. Quisque scelerisque, risus ut efficitur sollicitudin, neque est faucibus lacus, vitae eleifend nulla sem a magna. Integer viverra, diam eget venenatis pretium, augue ex pulvinar justo, ac ultrices neque nisl laoreet risus. Pellentesque commodo ultrices laoreet. Nulla nec ipsum non augue hendrerit vulputate sed eget diam. Maecenas semper rutrum ligula. Sed egestas, orci sed volutpat varius, eros mi lacinia magna, tincidunt aliquet nibh lacus eget dui. Integer vestibulum velit in interdum ultrices. Mauris porta vitae quam non placerat. In nisi risus, hendrerit rhoncus hendrerit at, lacinia vel mauris. Curabitur tempus mattis eros nec consequat. Sed posuere elit lobortis libero porta, sed pharetra tortor ornare. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse pulvinar ante vitae metus ullamcorper euismod. Nulla facilisi. Donec quam nulla, eleifend vel consequat sed, maximus et nisi. Donec molestie euismod semper. Fusce eget arcu feugiat, efficitur lectus sed, feugiat justo. Mauris ultricies pretium ante non faucibus. Aenean egestas ante nunc, id pellentesque metus blandit eu. Nullam faucibus fringilla lectus, quis dapibus turpis elementum eu. Nunc eget dolor in velit molestie interdum id eu justo. Aliquam ornare arcu quis tincidunt posuere. Mauris sed porttitor ligula. Vestibulum tincidunt non lacus id lacinia. Donec ex augue, convallis vel justo vel, faucibus ultricies tortor."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/hydrogen",
|
||||
"version": "0.0.13",
|
||||
"version": "0.0.14",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"homepage": "https://vercel.com/docs",
|
||||
@@ -21,7 +21,7 @@
|
||||
"devDependencies": {
|
||||
"@types/jest": "27.5.1",
|
||||
"@types/node": "*",
|
||||
"@vercel/build-utils": "5.3.0",
|
||||
"@vercel/build-utils": "5.3.1",
|
||||
"typescript": "4.6.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/next",
|
||||
"version": "3.1.17",
|
||||
"version": "3.1.18",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
|
||||
@@ -44,7 +44,7 @@
|
||||
"@types/semver": "6.0.0",
|
||||
"@types/text-table": "0.2.1",
|
||||
"@types/webpack-sources": "3.2.0",
|
||||
"@vercel/build-utils": "5.3.0",
|
||||
"@vercel/build-utils": "5.3.1",
|
||||
"@vercel/nft": "0.21.0",
|
||||
"@vercel/routing-utils": "2.0.2",
|
||||
"async-sema": "3.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/node",
|
||||
"version": "2.5.7",
|
||||
"version": "2.5.8",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
|
||||
@@ -31,7 +31,7 @@
|
||||
"dependencies": {
|
||||
"@edge-runtime/vm": "1.1.0-beta.23",
|
||||
"@types/node": "*",
|
||||
"@vercel/build-utils": "5.3.0",
|
||||
"@vercel/build-utils": "5.3.1",
|
||||
"@vercel/node-bridge": "3.0.0",
|
||||
"@vercel/static-config": "2.0.3",
|
||||
"edge-runtime": "1.1.0-beta.23",
|
||||
|
||||
@@ -8,10 +8,15 @@ export function getRegExpFromMatchers(matcherOrMatchers: unknown): string {
|
||||
const matchers = Array.isArray(matcherOrMatchers)
|
||||
? matcherOrMatchers
|
||||
: [matcherOrMatchers];
|
||||
return matchers.map(getRegExpFromMatcher).join('|');
|
||||
const regExps = matchers.flatMap(getRegExpFromMatcher).join('|');
|
||||
return regExps;
|
||||
}
|
||||
|
||||
function getRegExpFromMatcher(matcher: unknown): string {
|
||||
function getRegExpFromMatcher(
|
||||
matcher: unknown,
|
||||
index: number,
|
||||
allMatchers: unknown[]
|
||||
): string[] {
|
||||
if (typeof matcher !== 'string') {
|
||||
throw new Error(
|
||||
"Middleware's `config.matcher` must be a path matcher (string) or an array of path matchers (string[])"
|
||||
@@ -24,8 +29,11 @@ function getRegExpFromMatcher(matcher: unknown): string {
|
||||
);
|
||||
}
|
||||
|
||||
const re = pathToRegexp(matcher);
|
||||
return re.source;
|
||||
const regExps = [pathToRegexp(matcher).source];
|
||||
if (matcher === '/' && !allMatchers.includes('/index')) {
|
||||
regExps.push(pathToRegexp('/index').source);
|
||||
}
|
||||
return regExps;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user