mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 21:07:47 +00:00
Compare commits
83 Commits
@vercel/py
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebb5e2b208 | ||
|
|
e34858d082 | ||
|
|
f03c947f91 | ||
|
|
0d13fe7e34 | ||
|
|
4afec9d373 | ||
|
|
09c85f63d2 | ||
|
|
9963965e9a | ||
|
|
f3ed279007 | ||
|
|
4fe489edad | ||
|
|
e2911aac0b | ||
|
|
f3cbc5d746 | ||
|
|
40df88b483 | ||
|
|
75c4f45b73 | ||
|
|
05a236f944 | ||
|
|
4b7383f521 | ||
|
|
c263c31e48 | ||
|
|
c80530f9b1 | ||
|
|
16fd4396ef | ||
|
|
4e7138f400 | ||
|
|
5273279cf1 | ||
|
|
37290039d6 | ||
|
|
b52d01f809 | ||
|
|
ffefaf82a1 | ||
|
|
6d8dbfc7d6 | ||
|
|
551cd7f688 | ||
|
|
2dfb6b45cd | ||
|
|
65ae2a289e | ||
|
|
72ea3532b1 | ||
|
|
78fac00823 | ||
|
|
9e255afa37 | ||
|
|
e4be68270f | ||
|
|
9c636dc1ba | ||
|
|
c98c9996bf | ||
|
|
0fcf172a10 | ||
|
|
99e5c4a6db | ||
|
|
b8269b0111 | ||
|
|
decac0fe3f | ||
|
|
591d1686d0 | ||
|
|
5e1d5c921c | ||
|
|
603b1256c6 | ||
|
|
6957c72828 | ||
|
|
9be3650cb7 | ||
|
|
6e1ee7a7d6 | ||
|
|
767ce2cff1 | ||
|
|
bb1d0ce1b7 | ||
|
|
31f79c7de1 | ||
|
|
4c230c8436 | ||
|
|
7941f5a104 | ||
|
|
5b931afbf3 | ||
|
|
15080364b8 | ||
|
|
47e3381c6d | ||
|
|
33aefdc029 | ||
|
|
30fe76a0cf | ||
|
|
97ef88dc28 | ||
|
|
f679098d7a | ||
|
|
2b57e12ad3 | ||
|
|
c4e94ad03f | ||
|
|
32afd67d29 | ||
|
|
7523e39f18 | ||
|
|
99f2f2f1ba | ||
|
|
63830d38ce | ||
|
|
f3428dd212 | ||
|
|
5eb8b16cbd | ||
|
|
226bf02be2 | ||
|
|
8505872f55 | ||
|
|
7db6436797 | ||
|
|
e2d76e9c92 | ||
|
|
337cb21d67 | ||
|
|
6bfff3e9eb | ||
|
|
ac5b259c11 | ||
|
|
bfc553db11 | ||
|
|
2b101d4692 | ||
|
|
3316f38cb4 | ||
|
|
7837387127 | ||
|
|
f478200dd3 | ||
|
|
c29de8206a | ||
|
|
a2df3b5463 | ||
|
|
73446e544a | ||
|
|
21ff4a58c3 | ||
|
|
2b9eb02b8c | ||
|
|
4ef4722460 | ||
|
|
be5308b137 | ||
|
|
08a83a94f8 |
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'
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# Runtime Developer Reference
|
||||
|
||||
The following page is a reference for how to create a Runtime by implementing
|
||||
the Runtime API interface.
|
||||
the Runtime API interface. It's a way to add support for a new programming language to Vercel.
|
||||
|
||||
> Note: If you're the author of a web framework, please use the [Build Output API](https://vercel.com/docs/build-output-api/v3) instead to make your framework compatible with Vercel.
|
||||
|
||||
A Runtime is an npm module that implements the following interface:
|
||||
|
||||
@@ -61,9 +63,6 @@ export async function build(options: BuildOptions) {
|
||||
const lambda = createLambda(/* … */);
|
||||
return {
|
||||
output: lambda,
|
||||
watch: [
|
||||
// Dependent files to trigger a rebuild in `vercel dev` go here…
|
||||
],
|
||||
routes: [
|
||||
// If your Runtime needs to define additional routing, define it here…
|
||||
],
|
||||
@@ -113,7 +112,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()`
|
||||
|
||||
@@ -187,7 +187,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`.
|
||||
|
||||
@@ -195,6 +196,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`.
|
||||
@@ -302,6 +374,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`
|
||||
|
||||
|
||||
@@ -35,6 +35,6 @@ For details on how to use Vercel, check out our [documentation](https://vercel.c
|
||||
|
||||
## Contributing
|
||||
|
||||
- [Code of Conduct](https://github.com/vercel/vercel/blob/main/.github/CODE_OF_CONDUCT.md)
|
||||
- [Contributing Guidelines](https://github.com/vercel/vercel/blob/main/.github/CONTRIBUTING.md)
|
||||
- [MIT License](https://github.com/vercel/vercel/blob/main/LICENSE)
|
||||
- [Code of Conduct](./.github/CODE_OF_CONDUCT.md)
|
||||
- [Contributing Guidelines](./.github/CONTRIBUTING.md)
|
||||
- [MIT License](./LICENSE)
|
||||
|
||||
1
examples/astro/.gitignore
vendored
1
examples/astro/.gitignore
vendored
@@ -18,3 +18,4 @@ pnpm-debug.log*
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
.vercel
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
README.md
|
||||
@@ -1,10 +1,16 @@
|
||||
# Welcome to [Astro](https://astro.build)
|
||||
# Astro
|
||||
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/starter)
|
||||
This directory is a brief example of an [Astro](https://astro.build/) site that can be deployed to Vercel with zero configuration.
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
## Deploy Your Own
|
||||
|
||||
## 🚀 Project Structure
|
||||
Deploy your own Astro project with Vercel.
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/vercel/vercel/tree/main/examples/astro&template=astro)
|
||||
|
||||
_Live Example: https://astro-template.vercel.app_
|
||||
|
||||
## Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
@@ -26,17 +32,15 @@ There's nothing special about `src/components/`, but that's where we like to put
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
## Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :---------------- | :------------------------------------------- |
|
||||
| :--------------------- | :------------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://github.com/withastro/astro) or jump into our [Discord server](https://astro.build/chat).
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` |
|
||||
| `npm run astro --help` | Get help using the Astro CLI |
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
{
|
||||
"name": "@example/basics",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview"
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^1.0.0-beta.20"
|
||||
"astro": "^1.0.0-rc.8"
|
||||
}
|
||||
}
|
||||
|
||||
76
examples/astro/src/components/Card.astro
Normal file
76
examples/astro/src/components/Card.astro
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
body: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
const { href, title, body } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<li class="link-card">
|
||||
<a href={href}>
|
||||
<h2>
|
||||
{title}
|
||||
<span>→</span>
|
||||
</h2>
|
||||
<p>
|
||||
{body}
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<style>
|
||||
:root {
|
||||
--link-gradient: linear-gradient(45deg, #4f39fa, #da62c4 30%, var(--color-border) 60%);
|
||||
}
|
||||
|
||||
.link-card {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
padding: 0.15rem;
|
||||
background-image: var(--link-gradient);
|
||||
background-size: 400%;
|
||||
border-radius: 0.5rem;
|
||||
background-position: 100%;
|
||||
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.link-card > a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
line-height: 1.4;
|
||||
padding: 1em 1.3em;
|
||||
border-radius: 0.35rem;
|
||||
color: var(--text-color);
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h2 span {
|
||||
display: inline-block;
|
||||
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) {
|
||||
background-position: 0;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) h2 {
|
||||
color: #4f39fa;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) h2 span {
|
||||
will-change: transform;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
</style>
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { title } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
|
||||
--font-size-lg: clamp(1.2rem, 0.7vw + 1.2rem, 1.5rem);
|
||||
--font-size-xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
|
||||
|
||||
--color-text: hsl(12, 5%, 4%);
|
||||
--color-bg: hsl(10, 21%, 95%);
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: system-ui, sans-serif;
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:global(h1) {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
:global(h2) {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
:global(code) {
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
</style>
|
||||
1
examples/astro/src/env.d.ts
vendored
Normal file
1
examples/astro/src/env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="astro/client" />
|
||||
56
examples/astro/src/layouts/Layout.astro
Normal file
56
examples/astro/src/layouts/Layout.astro
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { title } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
<style>
|
||||
:root {
|
||||
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
|
||||
--font-size-lg: clamp(1.2rem, 0.7vw + 1.2rem, 1.5rem);
|
||||
--font-size-xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
|
||||
|
||||
--color-text: hsl(12, 5%, 4%);
|
||||
--color-bg: hsl(10, 21%, 95%);
|
||||
--color-border: hsl(17, 24%, 90%);
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: system-ui, sans-serif;
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:global(h1) {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
:global(h2) {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
:global(code) {
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
</style>
|
||||
@@ -1,76 +1,47 @@
|
||||
---
|
||||
import Layout from '../components/Layout.astro';
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import Card from '../components/Card.astro';
|
||||
---
|
||||
|
||||
<Layout title="Welcome to Astro.">
|
||||
<main>
|
||||
<h1>Welcome to <span class="text-gradient">Astro</span></h1>
|
||||
<p class="instructions"><strong>Your first mission:</strong> tweak this message to try our hot module reloading. Check the <code>src/pages</code> directory!</p>
|
||||
<ul role="list" class="link-card-grid">
|
||||
<li class="link-card">
|
||||
<a href="https://astro.build/integrations/">
|
||||
<h2>Integrations <span>→</span></h2>
|
||||
<p>Add component frameworks, Tailwind, Partytown, and more!</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="link-card">
|
||||
<a href="https://astro.build/themes/">
|
||||
<h2>Themes <span>→</span></h2>
|
||||
<p>Explore a galaxy of community-built starters.</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="link-card">
|
||||
<a href="https://docs.astro.build/">
|
||||
<h2>Docs <span>→</span></h2>
|
||||
<p>Learn our complete feature set and explore the API.</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="link-card">
|
||||
<a href="https://astro.build/chat/">
|
||||
<h2>Chat <span>→</span></h2>
|
||||
<p>
|
||||
Ask, contribute, and have fun on our community Discord
|
||||
<svg
|
||||
class="heart"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
>
|
||||
<title>heart</title>
|
||||
<path d="M256 448l-30.164-27.211C118.718 322.442 48 258.61 48 179.095 48 114.221 97.918 64 162.4 64c36.399 0 70.717 16.742 93.6 43.947C278.882 80.742 313.199 64 349.6 64 414.082 64 464 114.221 464 179.095c0 79.516-70.719 143.348-177.836 241.694L256 448z" />
|
||||
</svg>
|
||||
<p class="instructions">
|
||||
Check out the <code>src/pages</code> directory to get started.<br />
|
||||
<strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above.
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<ul role="list" class="link-card-grid">
|
||||
<Card
|
||||
href="https://docs.astro.build/"
|
||||
title="Documentation"
|
||||
body="Learn how Astro works and explore the official API docs."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/integrations/"
|
||||
title="Integrations"
|
||||
body="Supercharge your project with new frameworks and libraries."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/themes/"
|
||||
title="Themes"
|
||||
body="Explore a galaxy of community-built starter themes."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/chat/"
|
||||
title="Chat"
|
||||
body="Come say hi to our amazing Discord community. ❤️"
|
||||
/>
|
||||
</ul>
|
||||
</main>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--color-border: hsl(17, 24%, 90%);
|
||||
--astro-gradient: linear-gradient(0deg,#4F39FA, #DA62C4);
|
||||
--link-gradient: linear-gradient(45deg, #4F39FA, #DA62C4 30%, var(--color-border) 60%);
|
||||
--night-sky-gradient: linear-gradient(0deg, #392362 -33%, #431f69 10%, #30216b 50%, #1f1638 100%);
|
||||
--astro-gradient: linear-gradient(0deg, #4f39fa, #da62c4);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
h2 span {
|
||||
display: inline-block;
|
||||
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
border: 0.1em solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
padding: 0.15em 0.25em;
|
||||
h1 {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
main {
|
||||
@@ -91,7 +62,8 @@ import Layout from '../components/Layout.astro';
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
0%,
|
||||
100% {
|
||||
background-position-y: 0%;
|
||||
}
|
||||
50% {
|
||||
@@ -100,75 +72,25 @@ import Layout from '../components/Layout.astro';
|
||||
}
|
||||
|
||||
.instructions {
|
||||
line-height: 1.8;
|
||||
margin-bottom: 2rem;
|
||||
background-image: var(--night-sky-gradient);
|
||||
padding: 1.5rem;
|
||||
line-height: 1.6;
|
||||
margin: 1rem 0;
|
||||
background: #4f39fa;
|
||||
padding: 1rem;
|
||||
border-radius: 0.4rem;
|
||||
color: var(--color-bg);
|
||||
}
|
||||
|
||||
.instructions code {
|
||||
font-size: 0.875em;
|
||||
border: 0.1em solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
padding: 0.15em 0.25em;
|
||||
}
|
||||
|
||||
.link-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
|
||||
gap: 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.link-card {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
padding: 0.15rem;
|
||||
background-image: var(--link-gradient);
|
||||
background-size: 400%;
|
||||
border-radius: 0.5rem;
|
||||
background-position: 100%;
|
||||
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.link-card > a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
line-height: 1.4;
|
||||
padding: 1em 1.3em;
|
||||
border-radius: 0.35rem;
|
||||
color: var(--text-color);
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) {
|
||||
background-position: 0;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) h2 {
|
||||
color: #4F39FA;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) h2 span {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.heart {
|
||||
display: inline-block;
|
||||
color: #DA62C4;
|
||||
animation: heartbeat 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes heartbeat {
|
||||
0%,
|
||||
50%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
5% {
|
||||
transform: scale(1.125);
|
||||
}
|
||||
10% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
15% {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable top-level await, and other modern ESM features.
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
// Enable node-style module resolution, for things like npm package imports.
|
||||
"moduleResolution": "node",
|
||||
// Enable JSON imports.
|
||||
"resolveJsonModule": true,
|
||||
// Enable stricter transpilation for better output.
|
||||
"isolatedModules": true,
|
||||
// Add type definitions for our Vite runtime.
|
||||
"types": ["vite/client"]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -99,6 +99,7 @@
|
||||
"selector": "MemberExpression > Identifier[name='substr']"
|
||||
}
|
||||
],
|
||||
"no-dupe-keys": 2,
|
||||
"require-atomic-updates": 0,
|
||||
"@typescript-eslint/ban-ts-comment": 0,
|
||||
"@typescript-eslint/camelcase": 0,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "5.0.8",
|
||||
"version": "5.3.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
@@ -14,8 +14,7 @@
|
||||
"build": "node build",
|
||||
"test": "jest --env node --verbose --runInBand --bail",
|
||||
"test-unit": "yarn test test/unit.*test.*",
|
||||
"test-integration-once": "yarn test test/integration.test.ts",
|
||||
"prepublishOnly": "node build"
|
||||
"test-integration-once": "yarn test test/integration.test.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iarna/toml": "2.2.3",
|
||||
|
||||
@@ -27,9 +27,7 @@ async function prepareSymlinkTarget(
|
||||
}
|
||||
|
||||
if (file.type === 'FileRef' || file.type === 'FileBlob') {
|
||||
const targetPathBufferPromise = await streamToBuffer(
|
||||
await file.toStreamAsync()
|
||||
);
|
||||
const targetPathBufferPromise = streamToBuffer(await file.toStreamAsync());
|
||||
const [targetPathBuffer] = await Promise.all([
|
||||
targetPathBufferPromise,
|
||||
mkdirPromise,
|
||||
@@ -42,9 +40,15 @@ async function prepareSymlinkTarget(
|
||||
);
|
||||
}
|
||||
|
||||
async function downloadFile(file: File, fsPath: string): Promise<FileFsRef> {
|
||||
export async function downloadFile(
|
||||
file: File,
|
||||
fsPath: string
|
||||
): Promise<FileFsRef> {
|
||||
const { mode } = file;
|
||||
|
||||
// If the source is a symlink, try to create it instead of copying the file.
|
||||
// Note: creating symlinks on Windows requires admin priviliges or symlinks
|
||||
// enabled in the group policy. We may want to improve the error message.
|
||||
if (isSymbolicLink(mode)) {
|
||||
const target = await prepareSymlinkTarget(file, fsPath);
|
||||
|
||||
|
||||
@@ -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})`
|
||||
|
||||
@@ -10,7 +10,7 @@ const allOptions = [
|
||||
major: 12,
|
||||
range: '12.x',
|
||||
runtime: 'nodejs12.x',
|
||||
discontinueDate: new Date('2022-08-09'),
|
||||
discontinueDate: new Date('2022-10-01'),
|
||||
},
|
||||
{
|
||||
major: 10,
|
||||
|
||||
32
packages/build-utils/src/get-prefixed-env-vars.ts
Normal file
32
packages/build-utils/src/get-prefixed-env-vars.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
type Envs = { [key: string]: string | undefined };
|
||||
|
||||
/**
|
||||
* Get the framework-specific prefixed System Environment Variables.
|
||||
* See https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables
|
||||
* @param envPrefix - Prefix, typically from `@vercel/frameworks`
|
||||
* @param envs - Environment Variables, typically from `process.env`
|
||||
*/
|
||||
export function getPrefixedEnvVars({
|
||||
envPrefix,
|
||||
envs,
|
||||
}: {
|
||||
envPrefix: string | undefined;
|
||||
envs: Envs;
|
||||
}): Envs {
|
||||
const vercelSystemEnvPrefix = 'VERCEL_';
|
||||
const newEnvs: Envs = {};
|
||||
if (envPrefix && envs.VERCEL_URL) {
|
||||
Object.keys(envs)
|
||||
.filter(key => key.startsWith(vercelSystemEnvPrefix))
|
||||
.forEach(key => {
|
||||
const newKey = `${envPrefix}${key}`;
|
||||
if (!(newKey in envs)) {
|
||||
newEnvs[newKey] = envs[key];
|
||||
}
|
||||
});
|
||||
// Tell turbo to exclude all Vercel System Env Vars
|
||||
// See https://github.com/vercel/turborepo/pull/1622
|
||||
newEnvs.TURBO_CI_VENDOR_ENV_KEY = `${envPrefix}${vercelSystemEnvPrefix}`;
|
||||
}
|
||||
return newEnvs;
|
||||
}
|
||||
@@ -4,7 +4,11 @@ import FileRef from './file-ref';
|
||||
import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
|
||||
import { NodejsLambda } from './nodejs-lambda';
|
||||
import { Prerender } from './prerender';
|
||||
import download, { DownloadedFiles, isSymbolicLink } from './fs/download';
|
||||
import download, {
|
||||
downloadFile,
|
||||
DownloadedFiles,
|
||||
isSymbolicLink,
|
||||
} from './fs/download';
|
||||
import getWriteableDirectory from './fs/get-writable-directory';
|
||||
import glob, { GlobOptions } from './fs/glob';
|
||||
import rename from './fs/rename';
|
||||
@@ -36,6 +40,7 @@ import streamToBuffer from './fs/stream-to-buffer';
|
||||
import debug from './debug';
|
||||
import getIgnoreFilter from './get-ignore-filter';
|
||||
import { getPlatformEnv } from './get-platform-env';
|
||||
import { getPrefixedEnvVars } from './get-prefixed-env-vars';
|
||||
|
||||
export {
|
||||
FileBlob,
|
||||
@@ -46,6 +51,7 @@ export {
|
||||
createLambda,
|
||||
Prerender,
|
||||
download,
|
||||
downloadFile,
|
||||
DownloadedFiles,
|
||||
getWriteableDirectory,
|
||||
glob,
|
||||
@@ -71,6 +77,7 @@ export {
|
||||
getDiscontinuedNodeVersions,
|
||||
getSpawnOptions,
|
||||
getPlatformEnv,
|
||||
getPrefixedEnvVars,
|
||||
streamToBuffer,
|
||||
debug,
|
||||
isSymbolicLink,
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface LambdaOptionsBase {
|
||||
allowQuery?: string[];
|
||||
regions?: string[];
|
||||
supportsMultiPayloads?: boolean;
|
||||
supportsWrapper?: boolean;
|
||||
}
|
||||
|
||||
export interface LambdaOptionsWithFiles extends LambdaOptionsBase {
|
||||
@@ -58,6 +59,7 @@ export class Lambda {
|
||||
*/
|
||||
zipBuffer?: Buffer;
|
||||
supportsMultiPayloads?: boolean;
|
||||
supportsWrapper?: boolean;
|
||||
|
||||
constructor(opts: LambdaOptions) {
|
||||
const {
|
||||
@@ -69,6 +71,7 @@ export class Lambda {
|
||||
allowQuery,
|
||||
regions,
|
||||
supportsMultiPayloads,
|
||||
supportsWrapper,
|
||||
} = opts;
|
||||
if ('files' in opts) {
|
||||
assert(typeof opts.files === 'object', '"files" must be an object');
|
||||
@@ -103,6 +106,13 @@ export class Lambda {
|
||||
);
|
||||
}
|
||||
|
||||
if (supportsWrapper !== undefined) {
|
||||
assert(
|
||||
typeof supportsWrapper === 'boolean',
|
||||
'"supportsWrapper" is not a boolean'
|
||||
);
|
||||
}
|
||||
|
||||
if (regions !== undefined) {
|
||||
assert(Array.isArray(regions), '"regions" is not an Array');
|
||||
assert(
|
||||
@@ -121,6 +131,7 @@ export class Lambda {
|
||||
this.regions = regions;
|
||||
this.zipBuffer = 'zipBuffer' in opts ? opts.zipBuffer : undefined;
|
||||
this.supportsMultiPayloads = supportsMultiPayloads;
|
||||
this.supportsWrapper = supportsWrapper;
|
||||
}
|
||||
|
||||
async createZip(): Promise<Buffer> {
|
||||
|
||||
@@ -335,6 +335,7 @@ export interface ProjectSettings {
|
||||
directoryListing?: boolean;
|
||||
gitForkProtection?: boolean;
|
||||
commandForIgnoringBuildStep?: string | null;
|
||||
skipGitConnectDuringLink?: boolean;
|
||||
}
|
||||
|
||||
export interface BuilderV2 {
|
||||
|
||||
87
packages/build-utils/test/unit.get-prefixed-env-vars.test.ts
vendored
Normal file
87
packages/build-utils/test/unit.get-prefixed-env-vars.test.ts
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
import { getPrefixedEnvVars } from '../src';
|
||||
|
||||
describe('Test `getPrefixedEnvVars()`', () => {
|
||||
const cases: Array<{
|
||||
name: string;
|
||||
args: Parameters<typeof getPrefixedEnvVars>[0];
|
||||
want: ReturnType<typeof getPrefixedEnvVars>;
|
||||
}> = [
|
||||
{
|
||||
name: 'should work with NEXT_PUBLIC_',
|
||||
args: {
|
||||
envPrefix: 'NEXT_PUBLIC_',
|
||||
envs: {
|
||||
VERCEL: '1',
|
||||
VERCEL_URL: 'example.vercel.sh',
|
||||
USER_ENV_VAR_NOT_VERCEL: 'example.com',
|
||||
FOO: 'bar',
|
||||
},
|
||||
},
|
||||
want: {
|
||||
NEXT_PUBLIC_VERCEL_URL: 'example.vercel.sh',
|
||||
TURBO_CI_VENDOR_ENV_KEY: 'NEXT_PUBLIC_VERCEL_',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should work with GATSBY_',
|
||||
args: {
|
||||
envPrefix: 'GATSBY_',
|
||||
envs: {
|
||||
USER_ENV_VAR_NOT_VERCEL: 'example.com',
|
||||
FOO: 'bar',
|
||||
VERCEL_URL: 'example.vercel.sh',
|
||||
VERCEL_ENV: 'production',
|
||||
VERCEL_REGION: 'iad1',
|
||||
VERCEL_GIT_COMMIT_AUTHOR_LOGIN: 'rauchg',
|
||||
},
|
||||
},
|
||||
want: {
|
||||
GATSBY_VERCEL_URL: 'example.vercel.sh',
|
||||
GATSBY_VERCEL_ENV: 'production',
|
||||
GATSBY_VERCEL_REGION: 'iad1',
|
||||
GATSBY_VERCEL_GIT_COMMIT_AUTHOR_LOGIN: 'rauchg',
|
||||
TURBO_CI_VENDOR_ENV_KEY: 'GATSBY_VERCEL_',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should not return anything if no system env vars detected',
|
||||
args: {
|
||||
envPrefix: 'GATSBY_',
|
||||
envs: {
|
||||
USER_ENV_VAR_NOT_VERCEL: 'example.com',
|
||||
FOO: 'bar',
|
||||
BLARG_VERCEL_THING: 'fake',
|
||||
},
|
||||
},
|
||||
want: {},
|
||||
},
|
||||
{
|
||||
name: 'should not return anything if envPrefix is empty string',
|
||||
args: {
|
||||
envPrefix: '',
|
||||
envs: {
|
||||
VERCEL: '1',
|
||||
VERCEL_URL: 'example.vercel.sh',
|
||||
},
|
||||
},
|
||||
want: {},
|
||||
},
|
||||
{
|
||||
name: 'should not return anything if envPrefix is undefined',
|
||||
args: {
|
||||
envPrefix: undefined,
|
||||
envs: {
|
||||
VERCEL: '1',
|
||||
VERCEL_URL: 'example.vercel.sh',
|
||||
},
|
||||
},
|
||||
want: {},
|
||||
},
|
||||
];
|
||||
|
||||
for (const { name, args, want } of cases) {
|
||||
it(name, () => {
|
||||
expect(getPrefixedEnvVars(args)).toEqual(want);
|
||||
});
|
||||
}
|
||||
});
|
||||
6
packages/build-utils/test/unit.test.ts
vendored
6
packages/build-utils/test/unit.test.ts
vendored
@@ -394,7 +394,7 @@ it('should get latest node version', async () => {
|
||||
it('should throw for discontinued versions', async () => {
|
||||
// Mock a future date so that Node 8 and 10 become discontinued
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => new Date('2022-09-01').getTime();
|
||||
global.Date.now = () => new Date('2022-10-15').getTime();
|
||||
|
||||
expect(getSupportedNodeVersion('8.10.x', false)).rejects.toThrow();
|
||||
expect(getSupportedNodeVersion('8.10.x', true)).rejects.toThrow();
|
||||
@@ -436,8 +436,8 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
|
||||
expect(warningMessages).toStrictEqual([
|
||||
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
||||
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-01 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-01 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
||||
]);
|
||||
|
||||
global.Date.now = realDateNow;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "27.3.4",
|
||||
"version": "28.1.1",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -16,7 +16,6 @@
|
||||
"test-unit": "yarn test test/unit/",
|
||||
"test-integration-cli": "rimraf test/fixtures/integration && ava test/integration.js --serial --fail-fast --verbose",
|
||||
"test-integration-dev": "yarn test test/dev/",
|
||||
"prepublishOnly": "yarn build",
|
||||
"coverage": "codecov",
|
||||
"build": "ts-node ./scripts/build.ts",
|
||||
"dev": "ts-node ./src/index.ts"
|
||||
@@ -42,16 +41,16 @@
|
||||
"node": ">= 14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "5.0.8",
|
||||
"@vercel/go": "2.0.12",
|
||||
"@vercel/hydrogen": "0.0.9",
|
||||
"@vercel/next": "3.1.12",
|
||||
"@vercel/node": "2.5.3",
|
||||
"@vercel/python": "3.1.4",
|
||||
"@vercel/redwood": "1.0.13",
|
||||
"@vercel/remix": "1.0.14",
|
||||
"@vercel/ruby": "1.3.20",
|
||||
"@vercel/static-build": "1.0.13",
|
||||
"@vercel/build-utils": "5.3.1",
|
||||
"@vercel/go": "2.2.0",
|
||||
"@vercel/hydrogen": "0.0.14",
|
||||
"@vercel/next": "3.1.20",
|
||||
"@vercel/node": "2.5.8",
|
||||
"@vercel/python": "3.1.10",
|
||||
"@vercel/redwood": "1.0.18",
|
||||
"@vercel/remix": "1.0.19",
|
||||
"@vercel/ruby": "1.3.27",
|
||||
"@vercel/static-build": "1.0.18",
|
||||
"update-notifier": "5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -97,9 +96,9 @@
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@types/yauzl-promise": "2.1.0",
|
||||
"@vercel/client": "12.1.7",
|
||||
"@vercel/frameworks": "1.1.1",
|
||||
"@vercel/fs-detectors": "2.0.3",
|
||||
"@vercel/client": "12.2.0",
|
||||
"@vercel/frameworks": "1.1.3",
|
||||
"@vercel/fs-detectors": "2.0.5",
|
||||
"@vercel/fun": "1.0.4",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
@@ -119,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",
|
||||
|
||||
@@ -37,6 +37,7 @@ const help = () => {
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
-N, --next Show next page of results
|
||||
-y, --yes Skip the confirmation prompt when removing an alias
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export default async function rm(
|
||||
|
||||
const removeStamp = stamp();
|
||||
if (!opts['--yes'] && !(await confirmAliasRemove(client, alias))) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ function handleSetupDomainError<T>(
|
||||
}
|
||||
|
||||
if (error instanceof ERRORS.UserAborted) {
|
||||
output.error(`User aborted`);
|
||||
output.error(`User canceled.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -116,8 +116,6 @@ export default async function main(client: Client): Promise<number> {
|
||||
}
|
||||
}
|
||||
|
||||
const badDeploymentPromise = getDeployment(client, bad).catch(err => err);
|
||||
|
||||
good = normalizeURL(good);
|
||||
parsed = parse(good);
|
||||
if (!parsed.hostname) {
|
||||
@@ -138,8 +136,6 @@ export default async function main(client: Client): Promise<number> {
|
||||
);
|
||||
}
|
||||
|
||||
const goodDeploymentPromise = getDeployment(client, good).catch(err => err);
|
||||
|
||||
if (!subpath) {
|
||||
subpath = await prompt(
|
||||
client,
|
||||
@@ -148,10 +144,9 @@ export default async function main(client: Client): Promise<number> {
|
||||
}
|
||||
|
||||
output.spinner('Retrieving deployments…');
|
||||
const [badDeployment, goodDeployment] = await Promise.all([
|
||||
badDeploymentPromise,
|
||||
goodDeploymentPromise,
|
||||
]);
|
||||
|
||||
// `getDeployment` cannot be parallelized because it might prompt for login
|
||||
const badDeployment = await getDeployment(client, bad).catch(err => err);
|
||||
|
||||
if (badDeployment) {
|
||||
if (badDeployment instanceof Error) {
|
||||
@@ -165,7 +160,8 @@ export default async function main(client: Client): Promise<number> {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { projectId } = badDeployment;
|
||||
// `getDeployment` cannot be parallelized because it might prompt for login
|
||||
const goodDeployment = await getDeployment(client, good).catch(err => err);
|
||||
|
||||
if (goodDeployment) {
|
||||
if (goodDeployment instanceof Error) {
|
||||
@@ -181,6 +177,8 @@ export default async function main(client: Client): Promise<number> {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { projectId } = badDeployment;
|
||||
|
||||
if (projectId !== goodDeployment.projectId) {
|
||||
output.error(`Good and Bad deployments must be from the same Project`);
|
||||
return 1;
|
||||
|
||||
@@ -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 Skip the confirmation prompt
|
||||
-y, --yes Skip the confirmation prompt about pulling environment variables and project settings when not found locally
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -157,7 +157,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
client.output.print(
|
||||
`No Project Settings found locally. Run ${cli.getCommandName(
|
||||
'pull --yes'
|
||||
)} to retreive them.`
|
||||
)} to retrieve them.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -171,7 +171,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
);
|
||||
}
|
||||
if (!confirmed) {
|
||||
client.output.print(`Aborted. No Project Settings retrieved.\n`);
|
||||
client.output.print(`Canceled. No Project Settings retrieved.\n`);
|
||||
return 0;
|
||||
}
|
||||
const { argv: originalArgv } = client;
|
||||
@@ -469,6 +469,8 @@ async function doBuild(
|
||||
)
|
||||
);
|
||||
} catch (err: any) {
|
||||
output.prettyError(err);
|
||||
|
||||
const writeConfigJsonPromise = fs.writeJSON(
|
||||
join(outputDir, 'config.json'),
|
||||
{ version: 3 },
|
||||
|
||||
@@ -9,7 +9,6 @@ export const help = () => `
|
||||
|
||||
${chalk.dim('Basic')}
|
||||
|
||||
billing Manages the account payment methods
|
||||
deploy [path] Performs a deployment ${chalk.bold(
|
||||
'(default)'
|
||||
)}
|
||||
@@ -73,7 +72,7 @@ export const help = () => `
|
||||
-S, --scope Set a custom scope
|
||||
--regions Set default regions to enable the deployment on
|
||||
--prod Create a production deployment
|
||||
-c, --confirm Confirm default options and skip questions
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
|
||||
@@ -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,20 +92,28 @@ export default async (client: Client) => {
|
||||
'--regions': String,
|
||||
'--prebuilt': Boolean,
|
||||
'--prod': Boolean,
|
||||
'--confirm': Boolean,
|
||||
'--archive': String,
|
||||
'--yes': Boolean,
|
||||
'-f': '--force',
|
||||
'-p': '--public',
|
||||
'-e': '--env',
|
||||
'-b': '--build-env',
|
||||
'-m': '--meta',
|
||||
'-c': '--confirm',
|
||||
'-y': '--yes',
|
||||
|
||||
// deprecated
|
||||
'--name': String,
|
||||
'-n': '--name',
|
||||
'--no-clipboard': Boolean,
|
||||
'--target': String,
|
||||
'--confirm': Boolean,
|
||||
'-c': '--confirm',
|
||||
});
|
||||
|
||||
if ('--confirm' in argv) {
|
||||
output.warn('`--confirm` is deprecated, please use `--yes` instead');
|
||||
argv['--yes'] = argv['--confirm'];
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
return 1;
|
||||
@@ -173,7 +186,7 @@ export default async (client: Client) => {
|
||||
}
|
||||
|
||||
const { path } = pathValidation;
|
||||
const autoConfirm = argv['--confirm'];
|
||||
const autoConfirm = argv['--yes'];
|
||||
|
||||
// deprecate --name
|
||||
if (argv['--name']) {
|
||||
@@ -254,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);
|
||||
|
||||
@@ -277,7 +296,7 @@ export default async (client: Client) => {
|
||||
));
|
||||
|
||||
if (!shouldStartSetup) {
|
||||
output.print(`Aborted. Project not set up.\n`);
|
||||
output.print(`Canceled. Project not set up.\n`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -538,7 +557,8 @@ export default async (client: Client) => {
|
||||
createArgs,
|
||||
org,
|
||||
!project,
|
||||
path
|
||||
path,
|
||||
archive
|
||||
);
|
||||
|
||||
if (deployment.code === 'missing_project_settings') {
|
||||
@@ -911,4 +931,6 @@ const printDeploymentStatus = async (
|
||||
) + newline;
|
||||
output.print(message + link);
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ import { OUTPUT_DIR } from '../../util/build/write-build-result';
|
||||
|
||||
type Options = {
|
||||
'--listen': string;
|
||||
'--confirm': boolean;
|
||||
'--yes': boolean;
|
||||
};
|
||||
|
||||
export default async function dev(
|
||||
@@ -38,7 +38,7 @@ export default async function dev(
|
||||
|
||||
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
|
||||
link = await setupAndLink(client, cwd, {
|
||||
autoConfirm: opts['--confirm'],
|
||||
autoConfirm: opts['--yes'],
|
||||
successEmoji: 'link',
|
||||
setupMsg: 'Set up and develop',
|
||||
});
|
||||
@@ -54,14 +54,13 @@ export default async function dev(
|
||||
client.output.error(
|
||||
`Command ${getCommandName(
|
||||
'dev'
|
||||
)} requires confirmation. Use option ${param('--confirm')} to confirm.`
|
||||
)} requires confirmation. Use option ${param('--yes')} to confirm.`
|
||||
);
|
||||
}
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
let devCommand: string | undefined;
|
||||
let frameworkSlug: string | undefined;
|
||||
let projectSettings: ProjectSettings | undefined;
|
||||
let projectEnvs: ProjectEnvVariable[] = [];
|
||||
let systemEnvValues: string[] = [];
|
||||
@@ -77,10 +76,6 @@ export default async function dev(
|
||||
const framework = frameworks.find(f => f.slug === project.framework);
|
||||
|
||||
if (framework) {
|
||||
if (framework.slug) {
|
||||
frameworkSlug = framework.slug;
|
||||
}
|
||||
|
||||
const defaults = framework.settings.devCommand.value;
|
||||
if (defaults) {
|
||||
devCommand = defaults;
|
||||
@@ -120,7 +115,6 @@ export default async function dev(
|
||||
const devServer = new DevServer(cwd, {
|
||||
output,
|
||||
devCommand,
|
||||
frameworkSlug,
|
||||
projectSettings,
|
||||
projectEnvs,
|
||||
systemEnvValues,
|
||||
|
||||
@@ -33,7 +33,7 @@ const help = () => {
|
||||
-d, --debug Debug mode [off]
|
||||
-l, --listen [uri] Specify a URI endpoint on which to listen [0.0.0.0:3000]
|
||||
-t, --token [token] Specify an Authorization Token
|
||||
--confirm Skip questions and use defaults when setting up a new project
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -74,14 +74,22 @@ export default async function main(client: Client) {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--listen': String,
|
||||
'-l': '--listen',
|
||||
'--confirm': Boolean,
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
|
||||
// Deprecated
|
||||
'--port': Number,
|
||||
'-p': '--port',
|
||||
'--confirm': Boolean,
|
||||
'-c': '--confirm',
|
||||
});
|
||||
args = getSubcommand(argv._.slice(1), COMMAND_CONFIG).args;
|
||||
|
||||
if ('--confirm' in argv) {
|
||||
output.warn('`--confirm` is deprecated, please use `--yes` instead');
|
||||
argv['--yes'] = argv['--confirm'];
|
||||
}
|
||||
|
||||
if ('--port' in argv) {
|
||||
output.warn('`--port` is deprecated, please use `--listen` instead');
|
||||
argv['--listen'] = String(argv['--port']);
|
||||
|
||||
@@ -37,7 +37,7 @@ export default async function add(
|
||||
const { domain, data: argData } = parsedParams;
|
||||
const data = await getDNSData(client, argData);
|
||||
if (!data) {
|
||||
output.log(`Aborted`);
|
||||
output.log(`Canceled`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export default async function rm(
|
||||
);
|
||||
|
||||
if (!yes) {
|
||||
output.error(`User aborted.`);
|
||||
output.error(`User canceled.`);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ const help = () => {
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
-N, --next Show next page of results
|
||||
-y, --yes Skip the confirmation prompt when removing a domain
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -92,6 +93,7 @@ export default async function main(client: Client) {
|
||||
'--force': Boolean,
|
||||
'--next': Number,
|
||||
'-N': '--next',
|
||||
'-y': '--yes',
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
|
||||
@@ -68,7 +68,7 @@ export default async function move(
|
||||
client
|
||||
))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export default async function move(
|
||||
client
|
||||
))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ export default async function rm(
|
||||
client
|
||||
))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ async function removeDomain(
|
||||
!skipConfirmation &&
|
||||
!(await promptBool(`Remove conflicts associated with domain?`, client))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
1
packages/cli/src/commands/env/index.ts
vendored
1
packages/cli/src/commands/env/index.ts
vendored
@@ -42,6 +42,7 @@ const help = () => {
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-y, --yes Skip the confirmation prompt when overwriting env file on pull or removing an env variable
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
|
||||
22
packages/cli/src/commands/env/pull.ts
vendored
22
packages/cli/src/commands/env/pull.ts
vendored
@@ -84,7 +84,7 @@ export default async function pull(
|
||||
false
|
||||
))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +135,12 @@ export default async function pull(
|
||||
|
||||
await outputFile(fullPath, contents, 'utf8');
|
||||
|
||||
if (deltaString) {
|
||||
output.print('\n' + deltaString);
|
||||
} else if (oldEnv && exists) {
|
||||
output.log('No changes found.');
|
||||
}
|
||||
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`${exists ? 'Updated' : 'Created'} ${chalk.bold(
|
||||
@@ -139,13 +150,6 @@ export default async function pull(
|
||||
)}\n`
|
||||
);
|
||||
|
||||
output.print('\n');
|
||||
if (deltaString) {
|
||||
output.print(deltaString);
|
||||
} else if (oldEnv && exists) {
|
||||
output.log('No changes found.');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
7
packages/cli/src/commands/env/rm.ts
vendored
7
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?`,
|
||||
@@ -112,7 +111,7 @@ export default async function rm(
|
||||
false
|
||||
))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -24,9 +57,10 @@ export default async function connect(
|
||||
org: Org | undefined
|
||||
) {
|
||||
const { output } = client;
|
||||
const confirm = Boolean(argv['--confirm']);
|
||||
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"
|
||||
@@ -86,14 +149,14 @@ export default async function connect(
|
||||
}
|
||||
|
||||
if (remoteUrl === '') {
|
||||
output.log('Aborted.');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
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('Canceled. 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('Canceled. 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,
|
||||
@@ -43,7 +43,7 @@ export default async function disconnect(
|
||||
await disconnectGitProvider(client, org, project.id);
|
||||
output.log(`Disconnected ${chalk.cyan(`${linkOrg}/${repo}`)}.`);
|
||||
} else {
|
||||
output.log('Aborted.');
|
||||
output.log('Canceled');
|
||||
}
|
||||
} else {
|
||||
output.error(
|
||||
|
||||
@@ -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:')}
|
||||
@@ -25,13 +25,22 @@ const help = () => {
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${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
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} git disconnect`)}
|
||||
@@ -49,7 +58,12 @@ export default async function main(client: Client) {
|
||||
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--confirm': Boolean,
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
|
||||
// deprecated
|
||||
'-c': '--yes',
|
||||
'--confirm': '--yes',
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
@@ -64,7 +78,7 @@ export default async function main(client: Client) {
|
||||
argv._ = argv._.slice(1);
|
||||
subcommand = argv._[0];
|
||||
const args = argv._.slice(1);
|
||||
const confirm = Boolean(argv['--confirm']);
|
||||
const confirm = Boolean(argv['--yes']);
|
||||
const { output } = client;
|
||||
|
||||
let paths = [process.cwd()];
|
||||
|
||||
@@ -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'],
|
||||
]);
|
||||
|
||||
@@ -53,7 +53,7 @@ export default async function init(
|
||||
);
|
||||
|
||||
if (!chosen) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Build } from '../types';
|
||||
import title from 'title';
|
||||
import { isErrnoException } from '../util/is-error';
|
||||
import { isAPIError } from '../util/errors-ts';
|
||||
import { URL } from 'url';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -66,7 +67,7 @@ export default async function main(client: Client) {
|
||||
const { print, log, error } = client.output;
|
||||
|
||||
// extract the first parameter
|
||||
const [, deploymentIdOrHost] = argv._;
|
||||
let [, deploymentIdOrHost] = argv._;
|
||||
|
||||
if (argv._.length !== 2) {
|
||||
error(`${getCommandName('inspect <url>')} expects exactly one argument`);
|
||||
@@ -90,12 +91,16 @@ export default async function main(client: Client) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// resolve the deployment, since we might have been given an alias
|
||||
const depFetchStart = Date.now();
|
||||
|
||||
try {
|
||||
deploymentIdOrHost = new URL(deploymentIdOrHost).hostname;
|
||||
} catch {}
|
||||
client.output.spinner(
|
||||
`Fetching deployment "${deploymentIdOrHost}" in ${chalk.bold(contextName)}`
|
||||
);
|
||||
|
||||
// resolve the deployment, since we might have been given an alias
|
||||
try {
|
||||
deployment = await getDeployment(client, deploymentIdOrHost);
|
||||
} catch (err: unknown) {
|
||||
|
||||
@@ -27,7 +27,7 @@ const help = () => {
|
||||
-p ${chalk.bold.underline('NAME')}, --project=${chalk.bold.underline(
|
||||
'NAME'
|
||||
)} Project name
|
||||
--confirm Confirm default options and skip questions
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -39,7 +39,7 @@ const help = () => {
|
||||
'–'
|
||||
)} Link current directory with default options and skip questions
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} link --confirm`)}
|
||||
${chalk.cyan(`$ ${getPkgName()} link --yes`)}
|
||||
|
||||
${chalk.gray('–')} Link a specific directory to a Vercel Project
|
||||
|
||||
@@ -49,9 +49,14 @@ const help = () => {
|
||||
|
||||
export default async function main(client: Client) {
|
||||
const argv = getArgs(client.argv.slice(2), {
|
||||
'--confirm': Boolean,
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
'--project': String,
|
||||
'-p': '--project',
|
||||
|
||||
// deprecated
|
||||
'--confirm': Boolean,
|
||||
'-c': '--confirm',
|
||||
});
|
||||
|
||||
if (argv['--help']) {
|
||||
@@ -59,10 +64,15 @@ export default async function main(client: Client) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if ('--confirm' in argv) {
|
||||
client.output.warn('`--confirm` is deprecated, please use `--yes` instead');
|
||||
argv['--yes'] = argv['--confirm'];
|
||||
}
|
||||
|
||||
const cwd = argv._[1] || process.cwd();
|
||||
const link = await setupAndLink(client, cwd, {
|
||||
forceDelete: true,
|
||||
autoConfirm: argv['--confirm'],
|
||||
autoConfirm: argv['--yes'],
|
||||
projectName: argv['--project'],
|
||||
successEmoji: 'success',
|
||||
setupMsg: 'Set up',
|
||||
@@ -73,7 +83,7 @@ export default async function main(client: Client) {
|
||||
client.output.error(
|
||||
`Command ${getCommandName(
|
||||
'link'
|
||||
)} requires confirmation. Use option ${param('--confirm')} to confirm.`
|
||||
)} requires confirmation. Use option ${param('--yes')} to confirm.`
|
||||
);
|
||||
}
|
||||
return link.exitCode;
|
||||
|
||||
@@ -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(`
|
||||
@@ -35,7 +36,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--confirm Skip the confirmation prompt
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
@@ -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,7 +80,13 @@ export default async function main(client: Client) {
|
||||
'-m': '--meta',
|
||||
'--next': Number,
|
||||
'-N': '--next',
|
||||
'--prod': Boolean,
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
|
||||
// deprecated
|
||||
'--confirm': Boolean,
|
||||
'-c': '--confirm',
|
||||
});
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
@@ -87,6 +95,11 @@ export default async function main(client: Client) {
|
||||
|
||||
const { output, config } = client;
|
||||
|
||||
if ('--confirm' in argv) {
|
||||
output.warn('`--confirm` is deprecated, please use `--yes` instead');
|
||||
argv['--yes'] = argv['--confirm'];
|
||||
}
|
||||
|
||||
const { print, log, error, note, debug, spinner } = output;
|
||||
|
||||
if (argv._.length > 2) {
|
||||
@@ -99,10 +112,10 @@ export default async function main(client: Client) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
const yes = argv['--confirm'] || false;
|
||||
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);
|
||||
@@ -136,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']) {
|
||||
@@ -198,11 +225,15 @@ export default async function main(client: Client) {
|
||||
|
||||
debug('Fetching deployments');
|
||||
|
||||
const response = await now.list(app, {
|
||||
const response = await now.list(
|
||||
app,
|
||||
{
|
||||
version: 6,
|
||||
meta,
|
||||
nextTimestamp,
|
||||
});
|
||||
},
|
||||
prod
|
||||
);
|
||||
|
||||
let {
|
||||
deployments,
|
||||
@@ -212,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'
|
||||
@@ -247,37 +286,34 @@ 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]')}`
|
||||
)}`
|
||||
`To list 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
|
||||
@@ -290,8 +326,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`
|
||||
@@ -300,34 +336,43 @@ export default async function main(client: Client) {
|
||||
if (pagination && pagination.count === 20) {
|
||||
const flags = getCommandFlags(argv, ['_', '--next']);
|
||||
log(
|
||||
`To display the next page run ${getCommandName(
|
||||
`To display the next page, run ${getCommandName(
|
||||
`ls${app ? ' ' + app : ''}${flags} --next ${pagination.next}`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 '--';
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ const help = () => {
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--environment [environment] Deployment environment [development]
|
||||
-y, --yes Skip the confirmation prompt
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ export default async function main(client: Client) {
|
||||
).toLowerCase();
|
||||
|
||||
if (confirmation !== 'y' && confirmation !== 'yes') {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,9 +226,10 @@ async function run({ output, contextName, currentTeam, client }) {
|
||||
|
||||
if (theSecret) {
|
||||
const yes =
|
||||
argv.yes || (await readConfirmation(output, theSecret, contextName));
|
||||
argv.yes ||
|
||||
(await readConfirmation(client, output, theSecret, contextName));
|
||||
if (!yes) {
|
||||
output.print(`Aborted. Secret not deleted.\n`);
|
||||
output.print(`Canceled. Secret not deleted.\n`);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
@@ -353,7 +354,7 @@ async function run({ output, contextName, currentTeam, client }) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
async function readConfirmation(output, secret, contextName) {
|
||||
async function readConfirmation(client, output, secret, contextName) {
|
||||
const time = chalk.gray(`${ms(new Date() - new Date(secret.created))} ago`);
|
||||
const tbl = table([[chalk.bold(secret.name), time]], {
|
||||
align: ['r', 'l'],
|
||||
@@ -367,5 +368,5 @@ async function readConfirmation(output, secret, contextName) {
|
||||
);
|
||||
output.print(` ${tbl}\n`);
|
||||
|
||||
return confirm(`${chalk.bold.red('Are you sure?')}`, false);
|
||||
return confirm(client, `${chalk.bold.red('Are you sure?')}`, false);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export default async function add(client: Client): Promise<number> {
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
if (isError(err) && err.message === 'USER_ABORT') {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ try {
|
||||
// Test to see if cwd has been deleted before
|
||||
// importing 3rd party packages that might need cwd.
|
||||
process.cwd();
|
||||
} catch (err) {
|
||||
} catch (err: unknown) {
|
||||
if (isError(err) && err.message.includes('uv_cwd')) {
|
||||
console.error('Error! The current working directory does not exist.');
|
||||
process.exit(1);
|
||||
@@ -40,8 +40,8 @@ import getConfig from './util/get-config';
|
||||
import * as configFiles from './util/config/files';
|
||||
import getGlobalPathConfig from './util/config/global-path';
|
||||
import {
|
||||
getDefaultConfig,
|
||||
getDefaultAuthConfig,
|
||||
defaultAuthConfig,
|
||||
defaultGlobalConfig,
|
||||
} from './util/config/get-default';
|
||||
import * as ERRORS from './util/errors-ts';
|
||||
import { APIError } from './util/errors-ts';
|
||||
@@ -50,7 +50,7 @@ import getUpdateCommand from './util/get-update-command';
|
||||
import { metrics, shouldCollectMetrics } from './util/metrics';
|
||||
import { getCommandName, getTitleName } from './util/pkg-name';
|
||||
import doLoginPrompt from './util/login/prompt';
|
||||
import { GlobalConfig } from './types';
|
||||
import { AuthConfig, GlobalConfig } from './types';
|
||||
import { VercelConfig } from '@vercel/client';
|
||||
|
||||
const isCanary = pkg.version.includes('canary');
|
||||
@@ -208,160 +208,59 @@ const main = async () => {
|
||||
VERCEL_DIR
|
||||
)}" ${errorToString(err)}`
|
||||
);
|
||||
}
|
||||
|
||||
let migrated = false;
|
||||
let configExists;
|
||||
|
||||
try {
|
||||
configExists = existsSync(VERCEL_CONFIG_PATH);
|
||||
} catch (err: unknown) {
|
||||
console.error(
|
||||
error(
|
||||
`${
|
||||
'An unexpected error occurred while trying to find the ' +
|
||||
`config file "${hp(VERCEL_CONFIG_PATH)}" `
|
||||
}${errorToString(err)}`
|
||||
)
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
let config: GlobalConfig | null = null;
|
||||
|
||||
if (configExists) {
|
||||
try {
|
||||
config = configFiles.readConfigFile();
|
||||
} catch (err) {
|
||||
console.error(
|
||||
error(
|
||||
`${
|
||||
'An unexpected error occurred while trying to read the ' +
|
||||
`config in "${hp(VERCEL_CONFIG_PATH)}" `
|
||||
}${errorToString(err)}`
|
||||
)
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// This is from when Vercel CLI supported
|
||||
// multiple providers. In that case, we really
|
||||
// need to migrate.
|
||||
if (
|
||||
// @ts-ignore
|
||||
config.sh ||
|
||||
// @ts-ignore
|
||||
config.user ||
|
||||
// @ts-ignore
|
||||
typeof config.user === 'object' ||
|
||||
typeof config.currentTeam === 'object'
|
||||
) {
|
||||
configExists = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!configExists) {
|
||||
const results = await getDefaultConfig(config);
|
||||
|
||||
config = results.config;
|
||||
migrated = results.migrated;
|
||||
|
||||
let config: GlobalConfig;
|
||||
try {
|
||||
config = configFiles.readConfigFile();
|
||||
} catch (err: unknown) {
|
||||
if (isErrnoException(err) && err.code === 'ENOENT') {
|
||||
config = defaultGlobalConfig;
|
||||
try {
|
||||
configFiles.writeToConfigFile(config);
|
||||
} catch (err: unknown) {
|
||||
console.error(
|
||||
error(
|
||||
`${
|
||||
'An unexpected error occurred while trying to write the ' +
|
||||
`default config to "${hp(VERCEL_CONFIG_PATH)}" `
|
||||
}${errorToString(err)}`
|
||||
)
|
||||
output.error(
|
||||
`An unexpected error occurred while trying to save the config to "${hp(
|
||||
VERCEL_CONFIG_PATH
|
||||
)}" ${errorToString(err)}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
output.error(
|
||||
`An unexpected error occurred while trying to read the config in "${hp(
|
||||
VERCEL_CONFIG_PATH
|
||||
)}" ${errorToString(err)}`
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
let authConfigExists;
|
||||
|
||||
try {
|
||||
authConfigExists = existsSync(VERCEL_AUTH_CONFIG_PATH);
|
||||
} catch (err: unknown) {
|
||||
console.error(
|
||||
error(
|
||||
`${
|
||||
'An unexpected error occurred while trying to find the ' +
|
||||
`auth file "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
||||
}${errorToString(err)}`
|
||||
)
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
let authConfig = null;
|
||||
|
||||
const subcommandsWithoutToken = [
|
||||
'login',
|
||||
'logout',
|
||||
'help',
|
||||
'init',
|
||||
'update',
|
||||
'build',
|
||||
];
|
||||
|
||||
if (authConfigExists) {
|
||||
let authConfig: AuthConfig;
|
||||
try {
|
||||
authConfig = configFiles.readAuthConfigFile();
|
||||
} catch (err: unknown) {
|
||||
console.error(
|
||||
error(
|
||||
`${
|
||||
'An unexpected error occurred while trying to read the ' +
|
||||
`auth config in "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
||||
}${errorToString(err)}`
|
||||
)
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// This is from when Vercel CLI supported
|
||||
// multiple providers. In that case, we really
|
||||
// need to migrate.
|
||||
// @ts-ignore
|
||||
if (authConfig.credentials) {
|
||||
authConfigExists = false;
|
||||
}
|
||||
} else {
|
||||
const results = await getDefaultAuthConfig(authConfig);
|
||||
|
||||
authConfig = results.config;
|
||||
migrated = results.migrated;
|
||||
|
||||
if (isErrnoException(err) && err.code === 'ENOENT') {
|
||||
authConfig = defaultAuthConfig;
|
||||
try {
|
||||
configFiles.writeToAuthConfigFile(authConfig);
|
||||
} catch (err: unknown) {
|
||||
console.error(
|
||||
error(
|
||||
`${
|
||||
'An unexpected error occurred while trying to write the ' +
|
||||
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
||||
}${errorToString(err)}`
|
||||
)
|
||||
output.error(
|
||||
`An unexpected error occurred while trying to write the auth config to "${hp(
|
||||
VERCEL_AUTH_CONFIG_PATH
|
||||
)}" ${errorToString(err)}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Let the user know we migrated the config
|
||||
if (migrated) {
|
||||
const directory = param(hp(VERCEL_DIR));
|
||||
debug(
|
||||
`The credentials and configuration within the ${directory} directory were upgraded`
|
||||
} else {
|
||||
output.error(
|
||||
`An unexpected error occurred while trying to read the auth config in "${hp(
|
||||
VERCEL_AUTH_CONFIG_PATH
|
||||
)}" ${errorToString(err)}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof argv['--api'] === 'string') {
|
||||
@@ -371,18 +270,12 @@ const main = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(apiUrl);
|
||||
} catch (err) {
|
||||
} catch (err: unknown) {
|
||||
output.error(`Please provide a valid URL instead of ${highlight(apiUrl)}.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
output.error(`Vercel global config was not loaded.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Shared API `Client` instance for all sub-commands to utilize
|
||||
client = new Client({
|
||||
apiUrl,
|
||||
@@ -430,6 +323,8 @@ const main = async () => {
|
||||
client.argv.push('-h');
|
||||
}
|
||||
|
||||
const subcommandsWithoutToken = ['login', 'logout', 'help', 'init', 'build'];
|
||||
|
||||
// Prompt for login if there is no current token
|
||||
if (
|
||||
(!authConfig || !authConfig.token) &&
|
||||
@@ -603,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;
|
||||
@@ -669,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;
|
||||
|
||||
@@ -20,15 +20,16 @@ export interface JSONObject {
|
||||
}
|
||||
|
||||
export interface AuthConfig {
|
||||
_?: string;
|
||||
'// Note'?: string;
|
||||
'// Docs'?: string;
|
||||
token?: string;
|
||||
skipWrite?: boolean;
|
||||
}
|
||||
|
||||
export interface GlobalConfig {
|
||||
_?: string;
|
||||
'// Note'?: string;
|
||||
'// Docs'?: string;
|
||||
currentTeam?: string;
|
||||
includeScheme?: string;
|
||||
collectMetrics?: boolean;
|
||||
api?: string;
|
||||
|
||||
@@ -140,6 +141,7 @@ export type Deployment = {
|
||||
meta: {
|
||||
[key: string]: any;
|
||||
};
|
||||
alias?: string[];
|
||||
};
|
||||
|
||||
export type Alias = {
|
||||
@@ -209,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"
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
PackageJson,
|
||||
Prerender,
|
||||
download,
|
||||
downloadFile,
|
||||
EdgeFunction,
|
||||
BuildResultBuildOutput,
|
||||
getLambdaOptionsFromFunction,
|
||||
@@ -266,9 +267,7 @@ async function writeStaticFile(
|
||||
const dest = join(outputDir, 'static', fsPath);
|
||||
await fs.mkdirp(dirname(dest));
|
||||
|
||||
// TODO: handle (or skip) symlinks?
|
||||
const stream = file.toStream();
|
||||
await pipe(stream, fs.createWriteStream(dest, { mode: file.mode }));
|
||||
await downloadFile(file, dest);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,75 +1,15 @@
|
||||
import { AuthConfig, GlobalConfig } from '../../types';
|
||||
|
||||
export const getDefaultConfig = async (existingCopy?: GlobalConfig | null) => {
|
||||
let migrated = false;
|
||||
|
||||
const config: GlobalConfig = {
|
||||
_: 'This is your Vercel config file. For more information see the global configuration documentation: https://vercel.com/docs/configuration#global',
|
||||
export const defaultGlobalConfig: GlobalConfig = {
|
||||
'// Note':
|
||||
'This is your Vercel config file. For more information see the global configuration documentation.',
|
||||
'// Docs':
|
||||
'https://vercel.com/docs/project-configuration#global-configuration/config-json',
|
||||
collectMetrics: true,
|
||||
};
|
||||
|
||||
if (existingCopy) {
|
||||
const keep = [
|
||||
'_',
|
||||
'currentTeam',
|
||||
'desktop',
|
||||
'updateChannel',
|
||||
'collectMetrics',
|
||||
'api',
|
||||
// This is deleted later in the code
|
||||
];
|
||||
|
||||
try {
|
||||
const existing = Object.assign({}, existingCopy);
|
||||
// @ts-ignore
|
||||
const sh = Object.assign({}, existing.sh || {});
|
||||
|
||||
Object.assign(config, existing, sh);
|
||||
|
||||
for (const key of Object.keys(config)) {
|
||||
if (!keep.includes(key)) {
|
||||
// @ts-ignore
|
||||
delete config[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof config.currentTeam === 'object') {
|
||||
// @ts-ignore
|
||||
config.currentTeam = config.currentTeam.id;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (typeof config.user === 'object') {
|
||||
// @ts-ignore
|
||||
config.user = config.user.uid || config.user.id;
|
||||
}
|
||||
|
||||
migrated = true;
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
return { config, migrated };
|
||||
};
|
||||
|
||||
export const getDefaultAuthConfig = async (existing?: AuthConfig | null) => {
|
||||
let migrated = false;
|
||||
|
||||
const config: AuthConfig = {
|
||||
_: 'This is your Vercel credentials file. DO NOT SHARE! More: https://vercel.com/docs/configuration#global',
|
||||
};
|
||||
|
||||
if (existing) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
const sh = existing.credentials.find(item => item.provider === 'sh');
|
||||
|
||||
if (sh) {
|
||||
config.token = sh.token;
|
||||
}
|
||||
|
||||
migrated = true;
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
return { config, migrated };
|
||||
export const defaultAuthConfig: AuthConfig = {
|
||||
'// Note': 'This is your Vercel credentials file. DO NOT SHARE!',
|
||||
'// Docs':
|
||||
'https://vercel.com/docs/project-configuration#global-configuration/auth-json',
|
||||
};
|
||||
|
||||
@@ -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
|
||||
const deployingSpinnerVal = isSettingUpProject
|
||||
? 'Setting up project'
|
||||
: `Deploying ${chalk.bold(`${org.slug}/${projectName}`)}`,
|
||||
0
|
||||
);
|
||||
: `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,
|
||||
@@ -131,7 +132,6 @@ export default class DevServer {
|
||||
public output: Output;
|
||||
public proxy: httpProxy;
|
||||
public envConfigs: EnvConfigs;
|
||||
public frameworkSlug?: string;
|
||||
public files: BuilderInputs;
|
||||
public address: string;
|
||||
public devCacheDir: string;
|
||||
@@ -175,7 +175,6 @@ export default class DevServer {
|
||||
this.address = '';
|
||||
this.devCommand = options.devCommand;
|
||||
this.projectSettings = options.projectSettings;
|
||||
this.frameworkSlug = options.frameworkSlug;
|
||||
this.caseSensitive = false;
|
||||
this.apiDir = null;
|
||||
this.apiExtensions = new Set();
|
||||
@@ -1398,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;
|
||||
@@ -1535,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}"`
|
||||
@@ -1556,6 +1556,8 @@ export default class DevServer {
|
||||
(err as any).link = 'https://vercel.link/command-not-found';
|
||||
}
|
||||
|
||||
this.output.prettyError(err);
|
||||
|
||||
await this.sendError(
|
||||
req,
|
||||
res,
|
||||
@@ -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);
|
||||
@@ -2208,7 +2217,10 @@ export default class DevServer {
|
||||
// Because of child process 'pipe' below, isTTY will be false.
|
||||
// Most frameworks use `chalk`/`supports-color` so we enable it anyway.
|
||||
FORCE_COLOR: process.stdout.isTTY ? '1' : '0',
|
||||
...(this.frameworkSlug === 'create-react-app' ? { BROWSER: 'none' } : {}),
|
||||
// Prevent framework dev servers from automatically opening a web
|
||||
// browser window, since it will not be the port that `vc dev`
|
||||
// is listening on and thus will be missing Vercel features.
|
||||
BROWSER: 'none',
|
||||
...process.env,
|
||||
...this.envConfigs.allEnv,
|
||||
PORT: `${port}`,
|
||||
|
||||
@@ -24,7 +24,6 @@ export { VercelConfig };
|
||||
export interface DevServerOptions {
|
||||
output: Output;
|
||||
devCommand?: string;
|
||||
frameworkSlug?: string;
|
||||
projectSettings?: ProjectSettings;
|
||||
systemEnvValues?: string[];
|
||||
projectEnvs?: ProjectEnvVariable[];
|
||||
@@ -146,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>
|
||||
|
||||
@@ -9,6 +9,7 @@ type LinkResult = {
|
||||
org: Org;
|
||||
project: Project;
|
||||
};
|
||||
|
||||
export async function ensureLink(
|
||||
commandName: string,
|
||||
client: Client,
|
||||
|
||||
14
packages/cli/src/util/env/diff-env-files.ts
vendored
14
packages/cli/src/util/env/diff-env-files.ts
vendored
@@ -63,19 +63,25 @@ export function buildDeltaString(
|
||||
const { added, changed, removed } = findChanges(oldEnv, newEnv);
|
||||
|
||||
let deltaString = '';
|
||||
deltaString += chalk.green(addDeltaSection('+', changed, true));
|
||||
deltaString += chalk.green(addDeltaSection('+', added));
|
||||
deltaString += chalk.yellow(addDeltaSection('~', changed));
|
||||
deltaString += chalk.red(addDeltaSection('-', removed));
|
||||
|
||||
return deltaString ? chalk.gray('Changes:\n') + deltaString : deltaString;
|
||||
return deltaString
|
||||
? chalk.gray('Changes:\n') + deltaString + '\n'
|
||||
: deltaString;
|
||||
}
|
||||
|
||||
function addDeltaSection(prefix: string, arr: string[]): string {
|
||||
function addDeltaSection(
|
||||
prefix: string,
|
||||
arr: string[],
|
||||
changed: boolean = false
|
||||
): string {
|
||||
if (arr.length === 0) return '';
|
||||
return (
|
||||
arr
|
||||
.sort()
|
||||
.map(item => `${prefix} ${item}`)
|
||||
.map(item => `${prefix} ${item}${changed ? ' (Updated)' : ''}`)
|
||||
.join('\n') + '\n'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -428,7 +426,7 @@ export class UserAborted extends NowError<'USER_ABORTED', {}> {
|
||||
super({
|
||||
code: 'USER_ABORTED',
|
||||
meta: {},
|
||||
message: `The user aborted the operation.`,
|
||||
message: `The user canceled the operation.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
const apiError = isAPIError(err);
|
||||
if (
|
||||
err.meta?.action === 'Install GitHub App' ||
|
||||
err.code === 'repo_not_found'
|
||||
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 (err.action === 'Add a Login Connection') {
|
||||
} 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`);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export default function handleError(error: unknown, { debug = false } = {}) {
|
||||
} else if (status === 500) {
|
||||
console.error(errorOutput('Unexpected server error. Please retry.'));
|
||||
} else if (code === 'USER_ABORT') {
|
||||
info('Aborted');
|
||||
info('Canceled');
|
||||
} else {
|
||||
console.error(
|
||||
errorOutput(`Unexpected error. Please try again later. (${message})`)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -21,7 +21,7 @@ interface ListOptions {
|
||||
choices: ListChoice[];
|
||||
pageSize?: number;
|
||||
separator?: boolean;
|
||||
abort?: 'start' | 'end';
|
||||
cancel?: 'start' | 'end';
|
||||
eraseFinalAnswer?: boolean;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export default async function list(
|
||||
],
|
||||
pageSize = 15, // Show 15 lines without scrolling (~4 credit cards)
|
||||
separator = false, // Puts a blank separator between each choice
|
||||
abort = 'end', // Whether the `abort` option will be at the `start` or the `end`,
|
||||
cancel = 'end', // Whether the `cancel` option will be at the `start` or the `end`,
|
||||
eraseFinalAnswer = false, // If true, the line with the final answer that inquirer prints will be erased before returning
|
||||
}: ListOptions
|
||||
): Promise<string> {
|
||||
@@ -97,17 +97,17 @@ export default async function list(
|
||||
}
|
||||
}
|
||||
|
||||
const abortSeparator = new inquirer.Separator('─'.repeat(biggestLength));
|
||||
const _abort = {
|
||||
name: 'Abort',
|
||||
const cancelSeparator = new inquirer.Separator('─'.repeat(biggestLength));
|
||||
const _cancel = {
|
||||
name: 'Cancel',
|
||||
value: '',
|
||||
short: '',
|
||||
};
|
||||
|
||||
if (abort === 'start') {
|
||||
choices.unshift(_abort, abortSeparator);
|
||||
if (cancel === 'start') {
|
||||
choices.unshift(_cancel, cancelSeparator);
|
||||
} else {
|
||||
choices.push(abortSeparator, _abort);
|
||||
choices.push(cancelSeparator, _cancel);
|
||||
}
|
||||
|
||||
const answer = await client.prompt({
|
||||
|
||||
127
packages/cli/src/util/link/add-git-connection.ts
Normal file
127
packages/cli/src/util/link/add-git-connection.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
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>,
|
||||
autoConfirm: Boolean,
|
||||
settings?: ProjectSettings
|
||||
): Promise<number | void> {
|
||||
if (!settings) {
|
||||
settings = getProjectSettings(project);
|
||||
}
|
||||
if (Object.keys(remoteUrls).length === 1) {
|
||||
return addSingleGitRemote(
|
||||
client,
|
||||
org,
|
||||
project,
|
||||
remoteUrls,
|
||||
settings || project,
|
||||
autoConfirm
|
||||
);
|
||||
} else if (Object.keys(remoteUrls).length > 1 && !project.link) {
|
||||
return addMultipleGitRemotes(
|
||||
client,
|
||||
org,
|
||||
project,
|
||||
remoteUrls,
|
||||
settings || project,
|
||||
autoConfirm
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function addSingleGitRemote(
|
||||
client: Client,
|
||||
org: Org,
|
||||
project: Project,
|
||||
remoteUrls: Dictionary<string>,
|
||||
settings: ProjectSettings,
|
||||
autoConfirm: Boolean
|
||||
) {
|
||||
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);
|
||||
|
||||
let shouldConnectOption: string | undefined;
|
||||
if (autoConfirm) {
|
||||
shouldConnectOption = 'yes';
|
||||
} else {
|
||||
shouldConnectOption = await promptGitConnectSingleUrl(
|
||||
client,
|
||||
project,
|
||||
remoteName,
|
||||
remoteUrl,
|
||||
replace
|
||||
);
|
||||
}
|
||||
return handleOptions(
|
||||
shouldConnectOption,
|
||||
client,
|
||||
org,
|
||||
project,
|
||||
settings,
|
||||
repoInfo
|
||||
);
|
||||
}
|
||||
|
||||
async function addMultipleGitRemotes(
|
||||
client: Client,
|
||||
org: Org,
|
||||
project: Project,
|
||||
remoteUrls: Dictionary<string>,
|
||||
settings: ProjectSettings,
|
||||
autoConfirm: Boolean
|
||||
) {
|
||||
let remoteUrl: string | undefined;
|
||||
if (autoConfirm) {
|
||||
remoteUrl = remoteUrls['origin'] || Object.values(remoteUrls)[0];
|
||||
} else {
|
||||
client.output.log('Found multiple Git remote URLs in Git config.');
|
||||
remoteUrl = await promptGitConnectMultipleUrls(client, remoteUrls);
|
||||
}
|
||||
return handleOptions(remoteUrl, 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;
|
||||
@@ -87,7 +89,7 @@ export default async function setupAndLink(
|
||||
));
|
||||
|
||||
if (!shouldStartSetup) {
|
||||
output.print(`Aborted. Project not set up.\n`);
|
||||
output.print(`Canceled. Project not set up.\n`);
|
||||
return { status: 'not_linked', org: null, project: null };
|
||||
}
|
||||
|
||||
@@ -128,6 +130,20 @@ 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,
|
||||
autoConfirm
|
||||
);
|
||||
if (typeof connectGit === 'number') {
|
||||
return { status: 'error', exitCode: connectGit };
|
||||
}
|
||||
}
|
||||
|
||||
await linkFolderToProject(
|
||||
output,
|
||||
path,
|
||||
@@ -241,6 +257,22 @@ 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,
|
||||
autoConfirm,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export default async function validatePaths(
|
||||
);
|
||||
|
||||
if (!shouldDeployHomeDirectory) {
|
||||
output.print(`Aborted\n`);
|
||||
output.print(`Canceled\n`);
|
||||
return { valid: false, exitCode: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
10
packages/cli/test/dev/fixtures/go/api/[segement].go
Normal file
10
packages/cli/test/dev/fixtures/go/api/[segement].go
Normal file
@@ -0,0 +1,10 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Req Path: %s", r.URL.Path)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package handler
|
||||
package another
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Another(w http.ResponseWriter, r *http.Request) {
|
||||
func HandlerAnother(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "This is another page")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export const config = {
|
||||
matcher: 'not-a-valid-matcher',
|
||||
};
|
||||
|
||||
export default function middleware(request, _event) {
|
||||
return new Response(null);
|
||||
}
|
||||
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 |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user