mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 21:07:47 +00:00
Compare commits
149 Commits
api@0.0.0
...
@vercel-in
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f43e413ba5 | ||
|
|
0945d24cbe | ||
|
|
a8ecf40d6f | ||
|
|
2995781a58 | ||
|
|
e2096c268d | ||
|
|
663d8e6437 | ||
|
|
153ca440fa | ||
|
|
c5e0cb4812 | ||
|
|
5bf1fe4c74 | ||
|
|
011f836aa7 | ||
|
|
ec107d7c91 | ||
|
|
0d27ae3b1a | ||
|
|
08da4b9c92 | ||
|
|
877f09ff5c | ||
|
|
5cca9b6c5c | ||
|
|
4af242af86 | ||
|
|
0cbdae1411 | ||
|
|
85dd667781 | ||
|
|
7d3dda7341 | ||
|
|
2144d0b2a9 | ||
|
|
976e6aedf9 | ||
|
|
6328751e14 | ||
|
|
8cb49a5136 | ||
|
|
3fa4f344cc | ||
|
|
27610896ed | ||
|
|
b9dae36e37 | ||
|
|
1537ff9c38 | ||
|
|
7e0317775f | ||
|
|
2dd27976b3 | ||
|
|
25e2b7efba | ||
|
|
3d23d1270c | ||
|
|
fde40e731a | ||
|
|
f353527421 | ||
|
|
c1cdfb3e75 | ||
|
|
fc413707d0 | ||
|
|
e842a8870e | ||
|
|
d1b0dbe3a7 | ||
|
|
d614709308 | ||
|
|
d5b588bc06 | ||
|
|
4a8622a10d | ||
|
|
6469ef1b8c | ||
|
|
b8d42a521b | ||
|
|
b1e8c9cb6e | ||
|
|
7c30b13ccb | ||
|
|
e5e757de34 | ||
|
|
2661f56347 | ||
|
|
b1c14cde03 | ||
|
|
8dd6d021df | ||
|
|
88ec6e69d6 | ||
|
|
a6f2e7b136 | ||
|
|
e906365909 | ||
|
|
7b01a07394 | ||
|
|
ce4633fe4d | ||
|
|
fdf86fda03 | ||
|
|
56178e6a46 | ||
|
|
5439d7c0c9 | ||
|
|
c670e51712 | ||
|
|
b56639b624 | ||
|
|
12bbae098c | ||
|
|
9969f0ba18 | ||
|
|
24e1e3c3be | ||
|
|
b61674cb2d | ||
|
|
cae60155f3 | ||
|
|
a91bde5287 | ||
|
|
0750517af9 | ||
|
|
70f6782954 | ||
|
|
5f1e37ee16 | ||
|
|
680d666fdc | ||
|
|
06a5dccfed | ||
|
|
f85df894c0 | ||
|
|
e9ec779f1c | ||
|
|
4333d1e6b2 | ||
|
|
493a31091d | ||
|
|
8d7206f5b6 | ||
|
|
4bf2ca55ff | ||
|
|
5e5332fbc9 | ||
|
|
281ec776a5 | ||
|
|
ee8f9292b4 | ||
|
|
027bce00b3 | ||
|
|
ca1f41200a | ||
|
|
834b3e652b | ||
|
|
5c6941d18c | ||
|
|
761ede2482 | ||
|
|
1d01703dc3 | ||
|
|
7021279b28 | ||
|
|
28acf50bdf | ||
|
|
718bbd365a | ||
|
|
7e791ee080 | ||
|
|
13769106cb | ||
|
|
aa734efc6c | ||
|
|
acc10e47c7 | ||
|
|
6a0e1e0b66 | ||
|
|
3468922108 | ||
|
|
e4895d979b | ||
|
|
733ff5ed85 | ||
|
|
0867f11a6a | ||
|
|
a6de052ed2 | ||
|
|
f5d48ec3bc | ||
|
|
6ec1180798 | ||
|
|
36db62a491 | ||
|
|
f9266e069f | ||
|
|
8163a153df | ||
|
|
c5e6bd1a7a | ||
|
|
c2f1bebd1f | ||
|
|
3138415533 | ||
|
|
8f6813bb63 | ||
|
|
734499fc03 | ||
|
|
f06988d914 | ||
|
|
71ac16220b | ||
|
|
8b3a4146af | ||
|
|
cfea31e6cf | ||
|
|
7090fec110 | ||
|
|
bc7c80fb9b | ||
|
|
91406abdb0 | ||
|
|
a5af8381ce | ||
|
|
2230ea6cc1 | ||
|
|
f057f0421b | ||
|
|
42c0b32a8d | ||
|
|
d61a1a7988 | ||
|
|
c438bbb362 | ||
|
|
cb5eef0eb5 | ||
|
|
79dee367cf | ||
|
|
dea58dea7e | ||
|
|
fecebfa7fa | ||
|
|
94d5612dce | ||
|
|
3eaf58bb74 | ||
|
|
e63cf40153 | ||
|
|
709c9509f4 | ||
|
|
6107c1ed22 | ||
|
|
7a0f377afe | ||
|
|
a04bf557fc | ||
|
|
b6736e82cf | ||
|
|
7923056bc0 | ||
|
|
71ff193ea3 | ||
|
|
c21d93de44 | ||
|
|
0039c8b5ce | ||
|
|
49c7178567 | ||
|
|
b038b29614 | ||
|
|
7a249a2284 | ||
|
|
a5e32ec31d | ||
|
|
bc5afe24c4 | ||
|
|
5070e3bbbd | ||
|
|
4ad1cbbd7d | ||
|
|
4f4e09477d | ||
|
|
cd35071f60 | ||
|
|
f373c94508 | ||
|
|
553c001eb0 | ||
|
|
f6c3a95783 | ||
|
|
c0bcef0ca4 |
18
.github/CODEOWNERS
vendored
18
.github/CODEOWNERS
vendored
@@ -1,16 +1,20 @@
|
||||
# Documentation
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
* @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood
|
||||
/.github/workflows @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||
/packages/fs-detectors @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @agadzik @chloetedder
|
||||
/packages/next @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||
/packages/routing-utils @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||
/packages/edge @vercel/edge-compute
|
||||
# Restricted Paths
|
||||
* @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @trek
|
||||
/.github/workflows @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @trek @ijjk
|
||||
/packages/fs-detectors @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @trek @agadzik @chloetedder
|
||||
/packages/next @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @trek @ijjk
|
||||
/packages/routing-utils @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @trek @ijjk
|
||||
/packages/edge @vercel/compute
|
||||
/examples @leerob
|
||||
/examples/create-react-app @Timer
|
||||
/examples/nextjs @timneutkens @ijjk @styfle
|
||||
/examples/hugo @styfle
|
||||
/examples/jekyll @styfle
|
||||
/examples/zola @styfle
|
||||
/packages/node @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @Kikobeats
|
||||
/packages/node @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @trek @Kikobeats
|
||||
|
||||
# Unrestricted Paths
|
||||
.changeset/
|
||||
|
||||
4
.github/DISCUSSION_TEMPLATE/general.yml
vendored
4
.github/DISCUSSION_TEMPLATE/general.yml
vendored
@@ -2,7 +2,9 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> **Note**: For discussions not related to Vercel CLI or Runtimes, please visit the [Vercel Community](https://github.com/orgs/vercel/discussions)
|
||||
**Note**: This category is intended for discussions related to Vercel CLI or Runtimes.
|
||||
|
||||
If you post in this repository seeking help with other Vercel tools and features, it may be missed by our support team. For help with topics other than the CLI and Runtimes, please visit the [Vercel Community](https://github.com/orgs/vercel/discussions).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
|
||||
6
.github/DISCUSSION_TEMPLATE/help.yml
vendored
6
.github/DISCUSSION_TEMPLATE/help.yml
vendored
@@ -2,9 +2,11 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> **Note**: For discussions not related to Vercel CLI or Runtimes, please visit the [Vercel Community](https://github.com/orgs/vercel/discussions/categories/help)
|
||||
**Note**: This category is intended for discussions related to Vercel CLI or Runtimes.
|
||||
|
||||
If you post in this repository seeking help with other Vercel tools and features, it may be missed by our support team. For help with topics other than the CLI and Runtimes, please visit the [Vercel Community](https://github.com/orgs/vercel/discussions).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Problem Description
|
||||
label: Question
|
||||
validations:
|
||||
required: true
|
||||
|
||||
4
.github/DISCUSSION_TEMPLATE/ideas.yml
vendored
4
.github/DISCUSSION_TEMPLATE/ideas.yml
vendored
@@ -2,7 +2,9 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> **Note**: For discussions not related to Vercel CLI or Runtimes, please visit the [Vercel Community](https://github.com/orgs/vercel/discussions/categories/ideas)
|
||||
**Note**: This category is intended for sharing ideas related to Vercel CLI or Runtimes.
|
||||
|
||||
Please visit the [Vercel Community](https://github.com/orgs/vercel/discussions) to share ideas for other Vercel tools and features.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Idea
|
||||
|
||||
10
.github/DISCUSSION_TEMPLATE/polls.yml
vendored
10
.github/DISCUSSION_TEMPLATE/polls.yml
vendored
@@ -1,10 +0,0 @@
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> **Note**: For discussions not related to Vercel CLI or Runtimes, please visit the [Vercel Community](https://github.com/orgs/vercel/discussions)
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
validations:
|
||||
required: true
|
||||
@@ -2,7 +2,9 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> **Note**: For discussions not related to Vercel CLI or Runtimes, please visit the [Vercel Community](https://github.com/orgs/vercel/discussions)
|
||||
**Note**: This category is intended for discussions related to Vercel CLI or Runtimes.
|
||||
|
||||
For topics related to other Vercel features, please visit the [Vercel Community](https://github.com/orgs/vercel/discussions).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
|
||||
5
.github/DISCUSSION_TEMPLATE/temporary.yml
vendored
5
.github/DISCUSSION_TEMPLATE/temporary.yml
vendored
@@ -1,5 +0,0 @@
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> **Note**: This category should not be used for new discussions. Please visit the [Vercel Community](https://github.com/orgs/vercel/discussions)
|
||||
16
.github/workflows/test-lint.yml
vendored
16
.github/workflows/test-lint.yml
vendored
@@ -19,6 +19,22 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
enforce-changeset:
|
||||
name: Enforce Changeset
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request' && github.event.pull_request.title != 'Version Packages'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: main
|
||||
- run: git checkout ${{ github.event.pull_request.head.ref }}
|
||||
- name: install pnpm@8.3.1
|
||||
run: npm i -g pnpm@8.3.1
|
||||
- run: pnpm install
|
||||
# Enforce a changeset file to be present
|
||||
- run: pnpm exec changeset status --since=main
|
||||
|
||||
test:
|
||||
name: Lint
|
||||
timeout-minutes: 10
|
||||
|
||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -81,11 +81,11 @@ jobs:
|
||||
run: echo | openssl s_client -showcerts -servername 'api.vercel.com' -connect 76.76.21.21:443
|
||||
|
||||
- name: Build ${{matrix.packageName}} and all its dependencies
|
||||
run: node utils/gen.js && node_modules/.bin/turbo run build --cache-dir=".turbo" --scope=${{matrix.packageName}} --include-dependencies --no-deps
|
||||
run: node utils/gen.js && node_modules/.bin/turbo run build --cache-dir=".turbo" --log-order=stream --scope=${{matrix.packageName}} --include-dependencies --no-deps
|
||||
env:
|
||||
FORCE_COLOR: '1'
|
||||
- name: Test ${{matrix.packageName}}
|
||||
run: node utils/gen.js && node_modules/.bin/turbo run test --cache-dir=".turbo" --scope=${{matrix.packageName}} --no-deps -- ${{ join(matrix.testPaths, ' ') }}
|
||||
run: node utils/gen.js && node_modules/.bin/turbo run test --cache-dir=".turbo" --log-order=stream --scope=${{matrix.packageName}} --no-deps -- ${{ join(matrix.testPaths, ' ') }}
|
||||
shell: bash
|
||||
env:
|
||||
VERCEL_CLI_VERSION: ${{ needs.setup.outputs.dplUrl }}/tarballs/vercel.tgz
|
||||
|
||||
@@ -44,6 +44,5 @@
|
||||
"react-use": "^17.4.0",
|
||||
"title": "^3.4.4",
|
||||
"typographic-base": "^1.0.4"
|
||||
},
|
||||
"author": "nrajlich"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(25%, auto));
|
||||
width: var(--max-width);
|
||||
max-width: 100%;
|
||||
width: var(--max-width);
|
||||
}
|
||||
|
||||
.card {
|
||||
|
||||
824
examples/nextjs/package-lock.json
generated
824
examples/nextjs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,9 +9,9 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "8.41.0",
|
||||
"eslint-config-next": "13.4.4",
|
||||
"next": "13.4.4",
|
||||
"eslint": "8.46.0",
|
||||
"eslint-config-next": "13.4.13",
|
||||
"next": "13.4.13",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
}
|
||||
|
||||
@@ -45,4 +45,4 @@ Locally preview production build:
|
||||
npm run preview
|
||||
```
|
||||
|
||||
Checkout the [deployment documentation](https://v3.nuxtjs.org/guide/deploy/presets) for more information.
|
||||
Checkout the [deployment documentation](https://nuxt.com/docs/getting-started/deployment#presets) for more information.
|
||||
|
||||
2
examples/package.json
vendored
2
examples/package.json
vendored
@@ -9,6 +9,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "27.4.1",
|
||||
"@vercel/frameworks": "1.4.2"
|
||||
"@vercel/frameworks": "1.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
|
||||
};
|
||||
|
||||
7
examples/remix/.gitignore
vendored
7
examples/remix/.gitignore
vendored
@@ -1,11 +1,12 @@
|
||||
node_modules
|
||||
|
||||
.cache
|
||||
/.cache
|
||||
/build
|
||||
/public/build
|
||||
.env
|
||||
|
||||
.vercel
|
||||
.output
|
||||
|
||||
/build/
|
||||
/public/build
|
||||
/api/index.js
|
||||
/api/index.js.map
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MetaFunction } from "@vercel/remix";
|
||||
import { cssBundleHref } from "@remix-run/css-bundle";
|
||||
import {
|
||||
Links,
|
||||
LiveReload,
|
||||
@@ -8,17 +8,18 @@ import {
|
||||
ScrollRestoration,
|
||||
} from "@remix-run/react";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
import type { LinksFunction } from "@vercel/remix";
|
||||
|
||||
export const meta: MetaFunction = () => ({
|
||||
charset: "utf-8",
|
||||
title: "New Remix App",
|
||||
viewport: "width=device-width,initial-scale=1",
|
||||
});
|
||||
export const links: LinksFunction = () => [
|
||||
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
|
||||
];
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<Meta />
|
||||
<Links />
|
||||
</head>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import type { V2_MetaFunction } from "@vercel/remix";
|
||||
|
||||
export const meta: V2_MetaFunction = () => [{ title: "New Remix App" }];
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { V2_MetaFunction } from "@vercel/remix";
|
||||
|
||||
export const config = { runtime: "edge" };
|
||||
|
||||
export const meta: V2_MetaFunction = () => [{ title: "Remix@Edge | New Remix App" }];
|
||||
|
||||
export default function Edge() {
|
||||
return (
|
||||
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
|
||||
|
||||
@@ -3,24 +3,28 @@
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "remix build",
|
||||
"dev": "remix dev"
|
||||
"dev": "remix dev",
|
||||
"start": "remix-serve build",
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@remix-run/node": "^1.15.0",
|
||||
"@remix-run/react": "^1.15.0",
|
||||
"@remix-run/serve": "^1.15.0",
|
||||
"@remix-run/css-bundle": "^1.18.0",
|
||||
"@remix-run/node": "^1.18.0",
|
||||
"@remix-run/react": "^1.18.0",
|
||||
"@remix-run/serve": "^1.18.0",
|
||||
"@vercel/analytics": "^0.1.11",
|
||||
"@vercel/remix": "^1.15.0",
|
||||
"@vercel/remix": "^1.18.0",
|
||||
"isbot": "^3.6.8",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@remix-run/dev": "^1.15.0",
|
||||
"@remix-run/eslint-config": "^1.15.0",
|
||||
"@remix-run/dev": "^1.18.0",
|
||||
"@remix-run/eslint-config": "^1.18.0",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"eslint": "^8.28.0",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
|
||||
7492
examples/remix/pnpm-lock.yaml
generated
7492
examples/remix/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,15 @@
|
||||
/**
|
||||
* @type {import('@remix-run/dev').AppConfig}
|
||||
*/
|
||||
/** @type {import('@remix-run/dev').AppConfig} */
|
||||
module.exports = {
|
||||
ignoredRouteFiles: ['**/.*'],
|
||||
future: {
|
||||
v2_dev: true,
|
||||
v2_errorBoundary: true,
|
||||
v2_headers: true,
|
||||
v2_meta: true,
|
||||
v2_normalizeFormMethod: true,
|
||||
v2_routeConvention: true,
|
||||
},
|
||||
ignoredRouteFiles: ["**/.*"],
|
||||
serverModuleFormat: "cjs",
|
||||
// appDirectory: "app",
|
||||
// assetsBuildDirectory: "public/build",
|
||||
// serverBuildPath: "build/index.js",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Saber Example
|
||||
|
||||
This directory is a brief example of a [Saber](https://saber.land) site that can be deployed to Vercel with zero configuration.
|
||||
This directory is a brief example of a [Saber](https://saber.egoist.dev) site that can be deployed to Vercel with zero configuration.
|
||||
|
||||
## Deploy Your Own
|
||||
|
||||
|
||||
@@ -5,4 +5,4 @@ layout: page
|
||||
|
||||
This is the Saber port of the base Jekyll theme. Check out the [GitHub project](https://github.com/egoist/saber-theme-minima) for detailed usages.
|
||||
|
||||
You can find out more info about customizing your theme, as well as basic Saber usage documentation at https://saber.land
|
||||
You can find out more info about customizing your theme, as well as basic Saber usage documentation at https://saber.egoist.dev
|
||||
|
||||
4
examples/storybook/vercel.json
Normal file
4
examples/storybook/vercel.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"framework": "storybook",
|
||||
"buildCommand": "storybook build"
|
||||
}
|
||||
@@ -1,5 +1,25 @@
|
||||
# @vercel-internals/constants
|
||||
|
||||
## 1.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Create new help output and arg parsing for deploy command ([#10090](https://github.com/vercel/vercel/pull/10090))
|
||||
|
||||
## 1.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`346892210`](https://github.com/vercel/vercel/commit/3468922108f411482a72acd0331f0f2ee52a6d4c)]:
|
||||
- @vercel/build-utils@6.8.0
|
||||
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`cd35071f6`](https://github.com/vercel/vercel/commit/cd35071f609d615d47bc04634c123b33768436cb)]:
|
||||
- @vercel/build-utils@6.7.5
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "@vercel-internals/constants",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.4",
|
||||
"types": "dist/index.d.ts",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "6.7.4",
|
||||
"@vercel/routing-utils": "2.2.1"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@vercel-internals/tsconfig": "1.0.0",
|
||||
"@vercel/style-guide": "4.0.2",
|
||||
|
||||
@@ -3,3 +3,7 @@ export const PROJECT_ENV_TARGET = [
|
||||
'preview',
|
||||
'development',
|
||||
] as const;
|
||||
|
||||
export const LOGO = '▲' as const;
|
||||
export const NAME = 'vercel' as const;
|
||||
export const TITLE = 'Vercel' as const;
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
# @vercel-internals/types
|
||||
|
||||
## 1.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`a8ecf40d6`](https://github.com/vercel/vercel/commit/a8ecf40d6f50e2fc8b13b02c8ef50b3dcafad3a6)]:
|
||||
- @vercel/build-utils@6.8.3
|
||||
|
||||
## 1.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`0750517af`](https://github.com/vercel/vercel/commit/0750517af99aea41410d4f1f772ce427699554e7)]:
|
||||
- @vercel/build-utils@6.8.2
|
||||
|
||||
## 1.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`7021279b2`](https://github.com/vercel/vercel/commit/7021279b284f314a4d1bdbb4306b4c22291efa08), [`718bbd365`](https://github.com/vercel/vercel/commit/718bbd365a50271a980bdca231ca801a0eead32b)]:
|
||||
- @vercel/build-utils@6.8.1
|
||||
- @vercel-internals/constants@1.0.4
|
||||
|
||||
## 1.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`346892210`](https://github.com/vercel/vercel/commit/3468922108f411482a72acd0331f0f2ee52a6d4c)]:
|
||||
- @vercel/build-utils@6.8.0
|
||||
- @vercel-internals/constants@1.0.3
|
||||
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`cd35071f6`](https://github.com/vercel/vercel/commit/cd35071f609d615d47bc04634c123b33768436cb)]:
|
||||
- @vercel/build-utils@6.7.5
|
||||
- @vercel-internals/constants@1.0.2
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
67
internals/types/index.d.ts
vendored
67
internals/types/index.d.ts
vendored
@@ -1,5 +1,6 @@
|
||||
import type { BuilderFunctions } from '@vercel/build-utils';
|
||||
import type { Readable, Writable } from 'stream';
|
||||
import type * as tty from 'tty';
|
||||
import type { Route } from '@vercel/routing-utils';
|
||||
import { PROJECT_ENV_TARGET } from '@vercel-internals/constants';
|
||||
|
||||
@@ -378,28 +379,60 @@ export interface ProjectLink {
|
||||
* to the root directory of the repository.
|
||||
*/
|
||||
repoRoot?: string;
|
||||
/**
|
||||
* When linked as a repository, contains the relative path
|
||||
* to the selected project root directory.
|
||||
*/
|
||||
projectRootDirectory?: string;
|
||||
}
|
||||
|
||||
export interface PaginationOptions {
|
||||
prev: number;
|
||||
/**
|
||||
* Amount of items in the current page.
|
||||
* @example 20
|
||||
*/
|
||||
count: number;
|
||||
next?: number;
|
||||
/**
|
||||
* Timestamp that must be used to request the next page.
|
||||
* @example 1540095775951
|
||||
*/
|
||||
next: number | null;
|
||||
/**
|
||||
* Timestamp that must be used to request the previous page.
|
||||
* @example 1540095775951
|
||||
*/
|
||||
prev: number | null;
|
||||
}
|
||||
|
||||
export type ProjectLinked = {
|
||||
status: 'linked';
|
||||
org: Org;
|
||||
project: Project;
|
||||
repoRoot?: string;
|
||||
};
|
||||
|
||||
export type ProjectNotLinked = {
|
||||
status: 'not_linked';
|
||||
org: null;
|
||||
project: null;
|
||||
};
|
||||
|
||||
export type ProjectLinkedError = {
|
||||
status: 'error';
|
||||
exitCode: number;
|
||||
reason?:
|
||||
| 'HEADLESS'
|
||||
| 'NOT_AUTHORIZED'
|
||||
| 'TEAM_DELETED'
|
||||
| 'PATH_IS_FILE'
|
||||
| 'INVALID_ROOT_DIRECTORY'
|
||||
| 'MISSING_PROJECT_SETTINGS';
|
||||
};
|
||||
|
||||
export type ProjectLinkResult =
|
||||
| { status: 'linked'; org: Org; project: Project; repoRoot?: string }
|
||||
| { status: 'not_linked'; org: null; project: null }
|
||||
| {
|
||||
status: 'error';
|
||||
exitCode: number;
|
||||
reason?:
|
||||
| 'HEADLESS'
|
||||
| 'NOT_AUTHORIZED'
|
||||
| 'TEAM_DELETED'
|
||||
| 'PATH_IS_FILE'
|
||||
| 'INVALID_ROOT_DIRECTORY'
|
||||
| 'MISSING_PROJECT_SETTINGS';
|
||||
};
|
||||
| ProjectLinked
|
||||
| ProjectNotLinked
|
||||
| ProjectLinkedError;
|
||||
|
||||
/**
|
||||
* @deprecated - `RollbackJobStatus` has been replace by `LastAliasRequest['jobStatus']`.
|
||||
@@ -599,6 +632,6 @@ export interface WritableTTY extends Writable {
|
||||
|
||||
export interface Stdio {
|
||||
stdin: ReadableTTY;
|
||||
stdout: WritableTTY;
|
||||
stderr: WritableTTY;
|
||||
stdout: tty.WriteStream;
|
||||
stderr: tty.WriteStream;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "@vercel-internals/types",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.6",
|
||||
"types": "index.d.ts",
|
||||
"main": "index.d.ts",
|
||||
"dependencies": {
|
||||
"@types/node": "14.14.31",
|
||||
"@vercel-internals/constants": "1.0.1",
|
||||
"@vercel/build-utils": "6.7.4",
|
||||
"@vercel-internals/constants": "1.0.4",
|
||||
"@vercel/build-utils": "6.8.3",
|
||||
"@vercel/routing-utils": "2.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
13
lerna.json
13
lerna.json
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"npmClient": "pnpm",
|
||||
"useWorkspaces": true,
|
||||
"packages": ["packages/*"],
|
||||
"command": {
|
||||
"publish": {
|
||||
"npmClient": "npm",
|
||||
"allowBranch": ["main"],
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
}
|
||||
},
|
||||
"version": "independent"
|
||||
}
|
||||
@@ -32,7 +32,7 @@
|
||||
"source-map-support": "0.5.12",
|
||||
"ts-eager": "2.0.2",
|
||||
"ts-jest": "29.1.0",
|
||||
"turbo": "1.9.9",
|
||||
"turbo": "1.10.12",
|
||||
"typescript": "4.9.5"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# @vercel/build-utils
|
||||
|
||||
## 6.8.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fix `getPrefixedEnvVars()` to handle `VERCEL_BRANCH_URL` ([#10315](https://github.com/vercel/vercel/pull/10315))
|
||||
|
||||
## 6.8.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Push back `nodejs16.x` discontinue date to `2024-02-06` ([#10209](https://github.com/vercel/vercel/pull/10209))
|
||||
|
||||
## 6.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Revert "[build-utils] Allow file-ref sema to be controlled through env flag" ([#10167](https://github.com/vercel/vercel/pull/10167))
|
||||
|
||||
## 6.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add `getNodeBinPaths()` and `traverseUpDirectories()` functions ([#10150](https://github.com/vercel/vercel/pull/10150))
|
||||
|
||||
## 6.7.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Publish missing build-utils ([`cd35071f6`](https://github.com/vercel/vercel/commit/cd35071f609d615d47bc04634c123b33768436cb))
|
||||
|
||||
## 6.7.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "6.7.4",
|
||||
"version": "6.8.3",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
@@ -5,13 +5,7 @@ import path from 'path';
|
||||
import Sema from 'async-sema';
|
||||
import { FileBase } from './types';
|
||||
|
||||
const DEFAULT_SEMA = 20;
|
||||
const semaToPreventEMFILE = new Sema(
|
||||
parseInt(
|
||||
process.env.VERCEL_INTERNAL_FILE_FS_REF_SEMA || String(DEFAULT_SEMA),
|
||||
10
|
||||
) || DEFAULT_SEMA
|
||||
);
|
||||
const semaToPreventEMFILE = new Sema(20);
|
||||
|
||||
interface FileFsRefOptions {
|
||||
mode?: number;
|
||||
|
||||
@@ -12,13 +12,7 @@ interface FileRefOptions {
|
||||
mutable?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_SEMA = 5;
|
||||
const semaToDownloadFromS3 = new Sema(
|
||||
parseInt(
|
||||
process.env.VERCEL_INTERNAL_FILE_REF_SEMA || String(DEFAULT_SEMA),
|
||||
10
|
||||
) || DEFAULT_SEMA
|
||||
);
|
||||
const semaToDownloadFromS3 = new Sema(5);
|
||||
|
||||
class BailableError extends Error {
|
||||
public bail: boolean;
|
||||
|
||||
@@ -10,7 +10,7 @@ function getOptions() {
|
||||
major: 16,
|
||||
range: '16.x',
|
||||
runtime: 'nodejs16.x',
|
||||
discontinueDate: new Date('2023-08-15'),
|
||||
discontinueDate: new Date('2024-02-06'),
|
||||
},
|
||||
{
|
||||
major: 14,
|
||||
|
||||
@@ -44,23 +44,33 @@ export interface ScanParentDirsResult {
|
||||
lockfileVersion?: number;
|
||||
}
|
||||
|
||||
export interface WalkParentDirsProps {
|
||||
export interface TraverseUpDirectoriesProps {
|
||||
/**
|
||||
* The highest directory, typically the workPath root of the project.
|
||||
* If this directory is reached and it doesn't contain the file, null is returned.
|
||||
*/
|
||||
base: string;
|
||||
/**
|
||||
* The directory to start searching, typically the same directory of the entrypoint.
|
||||
* If this directory doesn't contain the file, the parent is checked, etc.
|
||||
* The directory to start iterating from, typically the same directory of the entrypoint.
|
||||
*/
|
||||
start: string;
|
||||
/**
|
||||
* The highest directory, typically the workPath root of the project.
|
||||
*/
|
||||
base?: string;
|
||||
}
|
||||
|
||||
export interface WalkParentDirsProps
|
||||
extends Required<TraverseUpDirectoriesProps> {
|
||||
/**
|
||||
* The name of the file to search for, typically `package.json` or `Gemfile`.
|
||||
*/
|
||||
filename: string;
|
||||
}
|
||||
|
||||
export interface WalkParentDirsMultiProps
|
||||
extends Required<TraverseUpDirectoriesProps> {
|
||||
/**
|
||||
* The name of the file to search for, typically `package.json` or `Gemfile`.
|
||||
*/
|
||||
filenames: string[];
|
||||
}
|
||||
|
||||
export interface SpawnOptionsExtended extends SpawnOptions {
|
||||
/**
|
||||
* Pretty formatted command that is being spawned for logging purposes.
|
||||
@@ -131,6 +141,24 @@ export async function execCommand(command: string, options: SpawnOptions = {}) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function* traverseUpDirectories({
|
||||
start,
|
||||
base,
|
||||
}: TraverseUpDirectoriesProps) {
|
||||
let current: string | undefined = path.normalize(start);
|
||||
const normalizedRoot = base ? path.normalize(base) : undefined;
|
||||
while (current) {
|
||||
yield current;
|
||||
if (current === normalizedRoot) break;
|
||||
// Go up one directory
|
||||
const next = path.join(current, '..');
|
||||
current = next === current ? undefined : next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `getNodeBinPaths()` instead.
|
||||
*/
|
||||
export async function getNodeBinPath({
|
||||
cwd,
|
||||
}: {
|
||||
@@ -141,6 +169,15 @@ export async function getNodeBinPath({
|
||||
return path.join(dir, 'node_modules', '.bin');
|
||||
}
|
||||
|
||||
export function getNodeBinPaths({
|
||||
start,
|
||||
base,
|
||||
}: TraverseUpDirectoriesProps): string[] {
|
||||
return Array.from(traverseUpDirectories({ start, base })).map(dir =>
|
||||
path.join(dir, 'node_modules/.bin')
|
||||
);
|
||||
}
|
||||
|
||||
async function chmodPlusX(fsPath: string) {
|
||||
const s = await fs.stat(fsPath);
|
||||
const newMode = s.mode | 64 | 8 | 1; // eslint-disable-line no-bitwise
|
||||
@@ -297,22 +334,14 @@ export async function walkParentDirs({
|
||||
}: WalkParentDirsProps): Promise<string | null> {
|
||||
assert(path.isAbsolute(base), 'Expected "base" to be absolute path');
|
||||
assert(path.isAbsolute(start), 'Expected "start" to be absolute path');
|
||||
let parent = '';
|
||||
|
||||
for (let current = start; base.length <= current.length; current = parent) {
|
||||
const fullPath = path.join(current, filename);
|
||||
for (const dir of traverseUpDirectories({ start, base })) {
|
||||
const fullPath = path.join(dir, filename);
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
if (await fs.pathExists(fullPath)) {
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
parent = path.dirname(current);
|
||||
|
||||
if (parent === current) {
|
||||
// Reached root directory of the filesystem
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -322,14 +351,9 @@ async function walkParentDirsMulti({
|
||||
base,
|
||||
start,
|
||||
filenames,
|
||||
}: {
|
||||
base: string;
|
||||
start: string;
|
||||
filenames: string[];
|
||||
}): Promise<(string | undefined)[]> {
|
||||
let parent = '';
|
||||
for (let current = start; base.length <= current.length; current = parent) {
|
||||
const fullPaths = filenames.map(f => path.join(current, f));
|
||||
}: WalkParentDirsMultiProps): Promise<(string | undefined)[]> {
|
||||
for (const dir of traverseUpDirectories({ start, base })) {
|
||||
const fullPaths = filenames.map(f => path.join(dir, f));
|
||||
const existResults = await Promise.all(
|
||||
fullPaths.map(f => fs.pathExists(f))
|
||||
);
|
||||
@@ -338,13 +362,6 @@ async function walkParentDirsMulti({
|
||||
if (foundOneOrMore) {
|
||||
return fullPaths.map((f, i) => (existResults[i] ? f : undefined));
|
||||
}
|
||||
|
||||
parent = path.dirname(current);
|
||||
|
||||
if (parent === current) {
|
||||
// Reached root directory of the filesystem
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
@@ -14,7 +14,12 @@ export function getPrefixedEnvVars({
|
||||
envs: Envs;
|
||||
}): Envs {
|
||||
const vercelSystemEnvPrefix = 'VERCEL_';
|
||||
const allowed = ['VERCEL_URL', 'VERCEL_ENV', 'VERCEL_REGION'];
|
||||
const allowed = [
|
||||
'VERCEL_URL',
|
||||
'VERCEL_ENV',
|
||||
'VERCEL_REGION',
|
||||
'VERCEL_BRANCH_URL',
|
||||
];
|
||||
const newEnvs: Envs = {};
|
||||
if (envPrefix && envs.VERCEL_URL) {
|
||||
Object.keys(envs)
|
||||
|
||||
@@ -30,7 +30,9 @@ import {
|
||||
getNodeVersion,
|
||||
getSpawnOptions,
|
||||
getNodeBinPath,
|
||||
getNodeBinPaths,
|
||||
scanParentDirs,
|
||||
traverseUpDirectories,
|
||||
} from './fs/run-user-scripts';
|
||||
import {
|
||||
getLatestNodeVersion,
|
||||
@@ -43,6 +45,7 @@ import { getPlatformEnv } from './get-platform-env';
|
||||
import { getPrefixedEnvVars } from './get-prefixed-env-vars';
|
||||
import { cloneEnv } from './clone-env';
|
||||
import { hardLinkDir } from './hard-link-dir';
|
||||
import { validateNpmrc } from './validate-npmrc';
|
||||
|
||||
export {
|
||||
FileBlob,
|
||||
@@ -67,6 +70,7 @@ export {
|
||||
spawnCommand,
|
||||
walkParentDirs,
|
||||
getNodeBinPath,
|
||||
getNodeBinPaths,
|
||||
runNpmInstall,
|
||||
runBundleInstall,
|
||||
runPipInstall,
|
||||
@@ -88,6 +92,8 @@ export {
|
||||
getIgnoreFilter,
|
||||
cloneEnv,
|
||||
hardLinkDir,
|
||||
traverseUpDirectories,
|
||||
validateNpmrc,
|
||||
};
|
||||
|
||||
export { EdgeFunction } from './edge-function';
|
||||
|
||||
26
packages/build-utils/src/validate-npmrc.ts
Normal file
26
packages/build-utils/src/validate-npmrc.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { join } from 'path';
|
||||
import { readFile } from 'fs/promises';
|
||||
|
||||
/**
|
||||
* Checks if there is a `.npmrc` in the cwd (project root) and makes sure it
|
||||
* doesn't contain a `use-node-version`. This config setting is not supported
|
||||
* since it causes the package manager to install the Node.js version which in
|
||||
* the case of newer Node.js versions is not compatible with AWS due to
|
||||
* outdated GLIBC binaries.
|
||||
*
|
||||
* @see https://pnpm.io/npmrc#use-node-version
|
||||
*
|
||||
* @param cwd The current working directory (e.g. project root);
|
||||
*/
|
||||
export async function validateNpmrc(cwd: string): Promise<void> {
|
||||
const npmrc = await readFile(join(cwd, '.npmrc'), 'utf-8').catch(err => {
|
||||
if (err.code !== 'ENOENT') throw err;
|
||||
});
|
||||
|
||||
const nodeRegExp = /(?<!#.*)use-node-version/;
|
||||
if (npmrc?.match(nodeRegExp)) {
|
||||
throw new Error(
|
||||
'Detected unsupported "use-node-version" in your ".npmrc". Please use "engines" in your "package.json" instead.'
|
||||
);
|
||||
}
|
||||
}
|
||||
2
packages/build-utils/test/fixtures/29-npmrc/comment-use-node-version/.npmrc
vendored
Normal file
2
packages/build-utils/test/fixtures/29-npmrc/comment-use-node-version/.npmrc
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
foo=bar
|
||||
# use-node-version=16.16.0
|
||||
1
packages/build-utils/test/fixtures/29-npmrc/good/.npmrc
vendored
Normal file
1
packages/build-utils/test/fixtures/29-npmrc/good/.npmrc
vendored
Normal file
@@ -0,0 +1 @@
|
||||
foo=bar
|
||||
3
packages/build-utils/test/fixtures/29-npmrc/has-use-node-version/.npmrc
vendored
Normal file
3
packages/build-utils/test/fixtures/29-npmrc/has-use-node-version/.npmrc
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
foo=bar
|
||||
# the next line is not supported
|
||||
use-node-version=16.16.0
|
||||
17
packages/build-utils/test/unit.get-node-bin-paths.test.ts
vendored
Normal file
17
packages/build-utils/test/unit.get-node-bin-paths.test.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import { join } from 'path';
|
||||
import { getNodeBinPaths } from '../src/fs/run-user-scripts';
|
||||
|
||||
describe('getNodeBinPaths()', () => {
|
||||
const cwd = process.cwd();
|
||||
|
||||
it('should return array of `node_modules/.bin` paths', () => {
|
||||
const start = join(cwd, 'foo/bar/baz');
|
||||
const paths = getNodeBinPaths({ start, base: cwd });
|
||||
expect(paths).toEqual([
|
||||
join(cwd, 'foo/bar/baz/node_modules/.bin'),
|
||||
join(cwd, 'foo/bar/node_modules/.bin'),
|
||||
join(cwd, 'foo/node_modules/.bin'),
|
||||
join(cwd, 'node_modules/.bin'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,8 @@ describe('Test `getPrefixedEnvVars()`', () => {
|
||||
envs: {
|
||||
VERCEL: '1',
|
||||
VERCEL_URL: 'example.vercel.sh',
|
||||
VERCEL_ENV: 'production',
|
||||
VERCEL_BRANCH_URL: 'example-git-main-acme.vercel.app',
|
||||
USER_ENV_VAR_NOT_VERCEL: 'example.com',
|
||||
VERCEL_ARTIFACTS_TOKEN: 'abc123',
|
||||
FOO: 'bar',
|
||||
@@ -20,6 +22,8 @@ describe('Test `getPrefixedEnvVars()`', () => {
|
||||
},
|
||||
want: {
|
||||
NEXT_PUBLIC_VERCEL_URL: 'example.vercel.sh',
|
||||
NEXT_PUBLIC_VERCEL_ENV: 'production',
|
||||
NEXT_PUBLIC_VERCEL_BRANCH_URL: 'example-git-main-acme.vercel.app',
|
||||
TURBO_CI_VENDOR_ENV_KEY: 'NEXT_PUBLIC_VERCEL_',
|
||||
},
|
||||
},
|
||||
|
||||
6
packages/build-utils/test/unit.test.ts
vendored
6
packages/build-utils/test/unit.test.ts
vendored
@@ -238,7 +238,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('2023-10-01').getTime();
|
||||
global.Date.now = () => new Date('2024-02-13').getTime();
|
||||
|
||||
expect(getSupportedNodeVersion('8.10.x', false)).rejects.toThrow();
|
||||
expect(getSupportedNodeVersion('8.10.x', true)).rejects.toThrow();
|
||||
@@ -306,8 +306,8 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-03 will fail to build. Please set Node.js Version to 18.x in your Project Settings to use Node.js 18.',
|
||||
'Error: Node.js version 14.x has reached End-of-Life. Deployments created on or after 2023-08-15 will fail to build. Please set "engines": { "node": "18.x" } in your `package.json` file to use Node.js 18.',
|
||||
'Error: Node.js version 14.x has reached End-of-Life. Deployments created on or after 2023-08-15 will fail to build. Please set Node.js Version to 18.x in your Project Settings to use Node.js 18.',
|
||||
'Error: Node.js version 16.x has reached End-of-Life. Deployments created on or after 2023-08-15 will fail to build. Please set "engines": { "node": "18.x" } in your `package.json` file to use Node.js 18.',
|
||||
'Error: Node.js version 16.x has reached End-of-Life. Deployments created on or after 2023-08-15 will fail to build. Please set Node.js Version to 18.x in your Project Settings to use Node.js 18.',
|
||||
'Error: Node.js version 16.x has reached End-of-Life. Deployments created on or after 2024-02-06 will fail to build. Please set "engines": { "node": "18.x" } in your `package.json` file to use Node.js 18.',
|
||||
'Error: Node.js version 16.x has reached End-of-Life. Deployments created on or after 2024-02-06 will fail to build. Please set Node.js Version to 18.x in your Project Settings to use Node.js 18.',
|
||||
]);
|
||||
|
||||
global.Date.now = realDateNow;
|
||||
|
||||
50
packages/build-utils/test/unit.traverse-up-directories.test.ts
vendored
Normal file
50
packages/build-utils/test/unit.traverse-up-directories.test.ts
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
import { traverseUpDirectories } from '../src/fs/run-user-scripts';
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
describe('traverseUpDirectories()', () => {
|
||||
test.each(
|
||||
isWindows
|
||||
? [
|
||||
{
|
||||
start: 'C:\\foo\\bar\\baz',
|
||||
expected: ['C:\\foo\\bar\\baz', 'C:\\foo\\bar', 'C:\\foo', 'C:\\'],
|
||||
},
|
||||
{
|
||||
start: 'C:\\foo\\..\\bar\\.\\baz',
|
||||
expected: ['C:\\bar\\baz', 'C:\\bar', 'C:\\'],
|
||||
},
|
||||
{
|
||||
start: 'C:\\foo\\bar\\baz\\another',
|
||||
base: 'C:\\foo\\bar',
|
||||
expected: [
|
||||
'C:\\foo\\bar\\baz\\another',
|
||||
'C:\\foo\\bar\\baz',
|
||||
'C:\\foo\\bar',
|
||||
],
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
start: '/foo/bar/baz',
|
||||
expected: ['/foo/bar/baz', '/foo/bar', '/foo', '/'],
|
||||
},
|
||||
{
|
||||
start: '/foo/../bar/./baz',
|
||||
expected: ['/bar/baz', '/bar', '/'],
|
||||
},
|
||||
{
|
||||
start: '/foo/bar/baz/another',
|
||||
base: '/foo/bar',
|
||||
expected: ['/foo/bar/baz/another', '/foo/bar/baz', '/foo/bar'],
|
||||
},
|
||||
]
|
||||
)(
|
||||
'should traverse start="$start", base="$base"',
|
||||
({ start, base, expected }) => {
|
||||
expect(Array.from(traverseUpDirectories({ start, base }))).toEqual(
|
||||
expected
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
22
packages/build-utils/test/unit.validate-npmrc.test.ts
vendored
Normal file
22
packages/build-utils/test/unit.validate-npmrc.test.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import { join } from 'path';
|
||||
import { validateNpmrc } from '../src/validate-npmrc';
|
||||
|
||||
const fixture = (name: string) => join(__dirname, 'fixtures', '29-npmrc', name);
|
||||
|
||||
describe('validateNpmrc', () => {
|
||||
it('should not error with no use-node-version', async () => {
|
||||
await expect(validateNpmrc(fixture('good'))).resolves.toBe(undefined);
|
||||
});
|
||||
|
||||
it('should throw when use-node-version is found', async () => {
|
||||
await expect(
|
||||
validateNpmrc(fixture('has-use-node-version'))
|
||||
).rejects.toThrow('Detected unsupported');
|
||||
});
|
||||
|
||||
it('should not error when use-node-version is commented out', async () => {
|
||||
await expect(
|
||||
validateNpmrc(fixture('comment-use-node-version'))
|
||||
).resolves.toBe(undefined);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,253 @@
|
||||
# vercel
|
||||
|
||||
## 31.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Be looser in tests with mock server urls ([#10300](https://github.com/vercel/vercel/pull/10300))
|
||||
|
||||
- Handle calls for deployment aliases when mocking deployments ([#10303](https://github.com/vercel/vercel/pull/10303))
|
||||
|
||||
- Remove unused code ([#10309](https://github.com/vercel/vercel/pull/10309))
|
||||
|
||||
- Updated dependencies [[`5bf1fe4c7`](https://github.com/vercel/vercel/commit/5bf1fe4c743f6be3f7d5a24447ea5b083a68dc67), [`a8ecf40d6`](https://github.com/vercel/vercel/commit/a8ecf40d6f50e2fc8b13b02c8ef50b3dcafad3a6), [`08da4b9c9`](https://github.com/vercel/vercel/commit/08da4b9c923501d9d28eb6e3f26f4605fee83042), [`0945d24cb`](https://github.com/vercel/vercel/commit/0945d24cbe901ca3f0eedd011251ad499c72d472)]:
|
||||
- @vercel/next@3.9.4
|
||||
- @vercel/build-utils@6.8.3
|
||||
- @vercel/remix-builder@1.10.0
|
||||
- @vercel/node@2.15.9
|
||||
- @vercel/static-build@1.3.45
|
||||
|
||||
## 31.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Migrate list command to new structure ([#10284](https://github.com/vercel/vercel/pull/10284))
|
||||
|
||||
- Migrate whoami command to new structure ([#10266](https://github.com/vercel/vercel/pull/10266))
|
||||
|
||||
- Migrate logs command to new structure ([#10281](https://github.com/vercel/vercel/pull/10281))
|
||||
|
||||
- Migrate login command to new structure ([#10283](https://github.com/vercel/vercel/pull/10283))
|
||||
|
||||
- Migrate pull command to new structure ([#10280](https://github.com/vercel/vercel/pull/10280))
|
||||
|
||||
- Migrate logout command to new structure ([#10282](https://github.com/vercel/vercel/pull/10282))
|
||||
|
||||
- Migrate build command to new structure ([#10286](https://github.com/vercel/vercel/pull/10286))
|
||||
|
||||
- Migrate inspect command to new structure ([#10277](https://github.com/vercel/vercel/pull/10277))
|
||||
|
||||
- Migrate redeploy command to new structure ([#10279](https://github.com/vercel/vercel/pull/10279))
|
||||
|
||||
- Migrate link command to new structure ([#10285](https://github.com/vercel/vercel/pull/10285))
|
||||
|
||||
- Update spacing of --help output for CLI ([#10287](https://github.com/vercel/vercel/pull/10287))
|
||||
|
||||
- Updated dependencies [[`4af242af8`](https://github.com/vercel/vercel/commit/4af242af8633e58b6a9bf920564416da3ef22ad4), [`0cbdae141`](https://github.com/vercel/vercel/commit/0cbdae1411aa7936ff7dfe551919ca5e56cd6e98), [`85dd66778`](https://github.com/vercel/vercel/commit/85dd667781693539d753d587566e53964bbe189d)]:
|
||||
- @vercel/node@2.15.8
|
||||
- @vercel/remix-builder@1.9.1
|
||||
- @vercel/static-build@1.3.44
|
||||
|
||||
## 31.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Migrate bisect command to new structure ([#10276](https://github.com/vercel/vercel/pull/10276))
|
||||
|
||||
- Migrate remove command to new structure ([#10268](https://github.com/vercel/vercel/pull/10268))
|
||||
|
||||
- Updated dependencies [[`fc413707d`](https://github.com/vercel/vercel/commit/fc413707d017e234d5013b761d885f65f9b981bc)]:
|
||||
- @vercel/node@2.15.7
|
||||
- @vercel/static-build@1.3.43
|
||||
|
||||
## 31.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add a "Global Options" section to help output ([#10250](https://github.com/vercel/vercel/pull/10250))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`d1b0dbe3a`](https://github.com/vercel/vercel/commit/d1b0dbe3a7d8754286aa2b7ba0c8b55d3adafdea), [`4a8622a10`](https://github.com/vercel/vercel/commit/4a8622a10d52260cb629a1c4a6f797ade05ea154), [`6469ef1b8`](https://github.com/vercel/vercel/commit/6469ef1b8ce37e93f50ab4a108aa0953d7631fe8)]:
|
||||
- @vercel/remix-builder@1.9.0
|
||||
- @vercel/next@3.9.3
|
||||
|
||||
## 31.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`7c30b13cc`](https://github.com/vercel/vercel/commit/7c30b13ccb79bdf0ac240282bba4c084f1d0d122)]:
|
||||
- @vercel/next@3.9.2
|
||||
|
||||
## 31.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add 'Environment' column to 'vc list' with new '--environment' filter and pipe URLs to stdout ([#10239](https://github.com/vercel/vercel/pull/10239))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Update `proxy-agent` to v6.3.0 ([#10226](https://github.com/vercel/vercel/pull/10226))
|
||||
|
||||
- Use `getNodeBinPaths()` in `vc dev` ([#10225](https://github.com/vercel/vercel/pull/10225))
|
||||
|
||||
- Updated dependencies [[`b1c14cde0`](https://github.com/vercel/vercel/commit/b1c14cde03f94b2c15ba12c9be9d19c72df2fdbb), [`ce4633fe4`](https://github.com/vercel/vercel/commit/ce4633fe4d00cb5c251cdabbfab08f39ec3f3b5f)]:
|
||||
- @vercel/next@3.9.1
|
||||
- @vercel/static-build@1.3.42
|
||||
|
||||
## 31.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Detect multiple frameworks within the same root directory during `vc link --repo` ([#10203](https://github.com/vercel/vercel/pull/10203))
|
||||
|
||||
- Updated dependencies [[`b56639b62`](https://github.com/vercel/vercel/commit/b56639b624e9ad1df048a4c85083e26888696060), [`cae60155f`](https://github.com/vercel/vercel/commit/cae60155f34883f08a5e4f51b547e2a1a5fee694), [`c670e5171`](https://github.com/vercel/vercel/commit/c670e51712022193e078bd68b055f7e61013015d), [`5439d7c0c`](https://github.com/vercel/vercel/commit/5439d7c0c9b79e7161bf4fa84ffdb357365f9e7e)]:
|
||||
- @vercel/node@2.15.6
|
||||
- @vercel/next@3.9.0
|
||||
- @vercel/remix-builder@1.8.18
|
||||
- @vercel/static-build@1.3.41
|
||||
|
||||
## 31.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fix redeploy target to be undefined when null ([#10201](https://github.com/vercel/vercel/pull/10201))
|
||||
|
||||
- Respect forbidden API responses ([#10178](https://github.com/vercel/vercel/pull/10178))
|
||||
|
||||
- Update `supports-hyperlinks` to v3 ([#10208](https://github.com/vercel/vercel/pull/10208))
|
||||
|
||||
- Updated dependencies [[`0750517af`](https://github.com/vercel/vercel/commit/0750517af99aea41410d4f1f772ce427699554e7)]:
|
||||
- @vercel/build-utils@6.8.2
|
||||
- @vercel/static-build@1.3.40
|
||||
- @vercel/node@2.15.5
|
||||
- @vercel/remix-builder@1.8.17
|
||||
|
||||
## 31.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Allow additional project settings in `createProject()` ([#10172](https://github.com/vercel/vercel/pull/10172))
|
||||
|
||||
- Run local Project detection during `vc link --repo`. ([#10094](https://github.com/vercel/vercel/pull/10094))
|
||||
This allows for creation of new Projects that do not yet exist under the selected scope.
|
||||
|
||||
- Redeploy command no longer redeploys preview deployments to production ([#10186](https://github.com/vercel/vercel/pull/10186))
|
||||
|
||||
- Added trailing new line at end of help output ([#10170](https://github.com/vercel/vercel/pull/10170))
|
||||
|
||||
- Create new help output and arg parsing for deploy command ([#10090](https://github.com/vercel/vercel/pull/10090))
|
||||
|
||||
- [cli] Remove `preinstall` script ([#10157](https://github.com/vercel/vercel/pull/10157))
|
||||
|
||||
- Updated dependencies [[`7021279b2`](https://github.com/vercel/vercel/commit/7021279b284f314a4d1bdbb4306b4c22291efa08), [`5e5332fbc`](https://github.com/vercel/vercel/commit/5e5332fbc9317a8f3cc4ed0b72ec1a2c76020891), [`027bce00b`](https://github.com/vercel/vercel/commit/027bce00b3821d9b4a8f7ec320cd1c43ab9f4215)]:
|
||||
- @vercel/build-utils@6.8.1
|
||||
- @vercel/node@2.15.4
|
||||
- @vercel/remix-builder@1.8.16
|
||||
- @vercel/static-build@1.3.39
|
||||
|
||||
## 31.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`aa734efc6`](https://github.com/vercel/vercel/commit/aa734efc6c42badd4aa9bf64487904aa64e9bd49)]:
|
||||
- @vercel/next@3.8.8
|
||||
|
||||
## 31.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- Update `vc dev` redirect response to match production behavior ([#10143](https://github.com/vercel/vercel/pull/10143))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- require `--yes` to promote preview deployment ([#10135](https://github.com/vercel/vercel/pull/10135))
|
||||
|
||||
- [cli] Optimize write build result for vc build ([#10154](https://github.com/vercel/vercel/pull/10154))
|
||||
|
||||
- Only show relevant Project matches in Project selector ([#10114](https://github.com/vercel/vercel/pull/10114))
|
||||
|
||||
- [cli] Fix error message when token is invalid ([#10131](https://github.com/vercel/vercel/pull/10131))
|
||||
|
||||
- Updated dependencies [[`e4895d979`](https://github.com/vercel/vercel/commit/e4895d979b57e369e0618481c5974243887d72cc), [`346892210`](https://github.com/vercel/vercel/commit/3468922108f411482a72acd0331f0f2ee52a6d4c), [`346892210`](https://github.com/vercel/vercel/commit/3468922108f411482a72acd0331f0f2ee52a6d4c), [`a6de052ed`](https://github.com/vercel/vercel/commit/a6de052ed2f09cc80bf4c2d0f06bedd267a63cdc)]:
|
||||
- @vercel/next@3.8.7
|
||||
- @vercel/static-build@1.3.38
|
||||
- @vercel/build-utils@6.8.0
|
||||
- @vercel/remix-builder@1.8.15
|
||||
- @vercel/node@2.15.3
|
||||
|
||||
## 30.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [cli] do not force auto-assign value on deployments ([#10110](https://github.com/vercel/vercel/pull/10110))
|
||||
|
||||
- Updated dependencies [[`91406abdb`](https://github.com/vercel/vercel/commit/91406abdb0c332152fc6c7c1e4bd3a872b084434), [`2230ea6cc`](https://github.com/vercel/vercel/commit/2230ea6cc1b84c1f03227a4e197b7684635b5955), [`8b3a4146a`](https://github.com/vercel/vercel/commit/8b3a4146af68d2b7288c80a5b919d832dba929b5)]:
|
||||
- @vercel/node@2.15.2
|
||||
- @vercel/remix-builder@1.8.14
|
||||
- @vercel/static-build@1.3.37
|
||||
|
||||
## 30.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [cli] vc env pull should add `.env*.local` to `.gitignore` ([#10085](https://github.com/vercel/vercel/pull/10085))
|
||||
|
||||
- [cli] Fix team validation bug where you are apart of a team ([#10092](https://github.com/vercel/vercel/pull/10092))
|
||||
|
||||
- Add support for `vc dev` command with repo link ([#10082](https://github.com/vercel/vercel/pull/10082))
|
||||
|
||||
- Add support for `vc deploy --prebuilt` command with repo link ([#10083](https://github.com/vercel/vercel/pull/10083))
|
||||
|
||||
- Move readme copy logic to a helper function for `vc link` ([#10084](https://github.com/vercel/vercel/pull/10084))
|
||||
|
||||
- Add support for `vc pull` command with repo link ([#10078](https://github.com/vercel/vercel/pull/10078))
|
||||
|
||||
- Add support for `vc build` command with repo link ([#10075](https://github.com/vercel/vercel/pull/10075))
|
||||
|
||||
## 30.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`a04bf557f`](https://github.com/vercel/vercel/commit/a04bf557fc6e1080a117428977d0993dec78b004)]:
|
||||
- @vercel/node@2.15.1
|
||||
- @vercel/static-build@1.3.36
|
||||
|
||||
## 30.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [node] Add isomorphic functions ([#9947](https://github.com/vercel/vercel/pull/9947))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Add `client.fetchPaginated()` helper function ([#10054](https://github.com/vercel/vercel/pull/10054))
|
||||
|
||||
- Updated dependencies [[`bc5afe24c`](https://github.com/vercel/vercel/commit/bc5afe24c4547dbf798b939199e8212c4b34038e), [`49c717856`](https://github.com/vercel/vercel/commit/49c7178567ec5bcebe633b598c8c9c0e1aa40fbb), [`0039c8b5c`](https://github.com/vercel/vercel/commit/0039c8b5cea975316a62c4f6aaca5d66d731cc0d)]:
|
||||
- @vercel/node@2.15.0
|
||||
- @vercel/remix-builder@1.8.13
|
||||
- @vercel/static-build@1.3.35
|
||||
|
||||
## 30.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Publish missing build-utils ([`cd35071f6`](https://github.com/vercel/vercel/commit/cd35071f609d615d47bc04634c123b33768436cb))
|
||||
|
||||
- Updated dependencies [[`cd35071f6`](https://github.com/vercel/vercel/commit/cd35071f609d615d47bc04634c123b33768436cb)]:
|
||||
- @vercel/build-utils@6.7.5
|
||||
- @vercel/node@2.14.5
|
||||
- @vercel/remix-builder@1.8.12
|
||||
- @vercel/static-build@1.3.34
|
||||
|
||||
## 30.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [cli] vc build ignore '.env\*' & ignore files for '@vercel/static' ([#10056](https://github.com/vercel/vercel/pull/10056))
|
||||
|
||||
- [cli] Ensure .npmrc does not contain use-node-version ([#10049](https://github.com/vercel/vercel/pull/10049))
|
||||
|
||||
## 30.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "30.1.0",
|
||||
"version": "31.2.3",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -11,7 +11,6 @@
|
||||
"directory": "packages/cli"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "node ./scripts/preinstall.js",
|
||||
"test": "jest --env node --verbose --bail",
|
||||
"test-unit": "pnpm test test/unit/",
|
||||
"test-e2e": "rimraf test/fixtures/integration && pnpm test test/integration-1.test.ts test/integration-2.test.ts test/integration-3.test.ts",
|
||||
@@ -32,20 +31,20 @@
|
||||
"node": ">= 14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "6.7.4",
|
||||
"@vercel/build-utils": "6.8.3",
|
||||
"@vercel/go": "2.5.1",
|
||||
"@vercel/hydrogen": "0.0.64",
|
||||
"@vercel/next": "3.8.6",
|
||||
"@vercel/node": "2.14.4",
|
||||
"@vercel/next": "3.9.4",
|
||||
"@vercel/node": "2.15.9",
|
||||
"@vercel/python": "3.1.60",
|
||||
"@vercel/redwood": "1.1.15",
|
||||
"@vercel/remix-builder": "1.8.11",
|
||||
"@vercel/remix-builder": "1.10.0",
|
||||
"@vercel/ruby": "1.3.76",
|
||||
"@vercel/static-build": "1.3.33"
|
||||
"@vercel/static-build": "1.3.45"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@alex_neo/jest-expect-message": "1.0.5",
|
||||
"@edge-runtime/node-utils": "2.0.3",
|
||||
"@edge-runtime/node-utils": "2.1.0",
|
||||
"@next/env": "11.1.2",
|
||||
"@sentry/node": "5.5.0",
|
||||
"@sindresorhus/slugify": "0.11.0",
|
||||
@@ -85,13 +84,13 @@
|
||||
"@types/which": "3.0.0",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@types/yauzl-promise": "2.1.0",
|
||||
"@vercel-internals/constants": "1.0.1",
|
||||
"@vercel-internals/constants": "1.0.4",
|
||||
"@vercel-internals/get-package-json": "1.0.0",
|
||||
"@vercel-internals/types": "1.0.1",
|
||||
"@vercel/client": "12.6.1",
|
||||
"@vercel-internals/types": "1.0.6",
|
||||
"@vercel/client": "12.6.6",
|
||||
"@vercel/error-utils": "1.0.10",
|
||||
"@vercel/frameworks": "1.4.2",
|
||||
"@vercel/fs-detectors": "3.9.3",
|
||||
"@vercel/frameworks": "1.5.0",
|
||||
"@vercel/fs-detectors": "4.1.1",
|
||||
"@vercel/fun": "1.0.4",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@vercel/routing-utils": "2.2.1",
|
||||
@@ -150,7 +149,7 @@
|
||||
"pluralize": "7.0.0",
|
||||
"promisepipe": "3.0.0",
|
||||
"proxy": "2.0.0",
|
||||
"proxy-agent": "6.1.1",
|
||||
"proxy-agent": "6.3.0",
|
||||
"psl": "1.1.31",
|
||||
"qr-image": "3.2.0",
|
||||
"raw-body": "2.4.1",
|
||||
@@ -158,8 +157,7 @@
|
||||
"semver": "5.5.0",
|
||||
"serve-handler": "6.1.1",
|
||||
"strip-ansi": "6.0.1",
|
||||
"stripe": "5.1.0",
|
||||
"supports-hyperlinks": "2.2.0",
|
||||
"supports-hyperlinks": "3.0.0",
|
||||
"tar-fs": "1.16.3",
|
||||
"test-listen": "1.1.0",
|
||||
"text-table": "0.2.0",
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
const { join } = require('path');
|
||||
const { statSync } = require('fs');
|
||||
const pkg = require('../package');
|
||||
|
||||
function error(command) {
|
||||
console.error('> Error:', command);
|
||||
}
|
||||
|
||||
function debug(str) {
|
||||
if (
|
||||
process.argv.find(str => str === '--debug') ||
|
||||
process.env.PREINSTALL_DEBUG
|
||||
) {
|
||||
console.log(`[debug] [${new Date().toISOString()}]`, str);
|
||||
}
|
||||
}
|
||||
|
||||
function isYarn() {
|
||||
return process.env.npm_config_heading !== 'npm';
|
||||
}
|
||||
|
||||
function isGlobal() {
|
||||
const cmd = JSON.parse(process.env.npm_config_argv || '{ "original": [] }');
|
||||
|
||||
return isYarn()
|
||||
? cmd.original.includes('global')
|
||||
: Boolean(process.env.npm_config_global);
|
||||
}
|
||||
|
||||
function isVercel() {
|
||||
return pkg.name === 'vercel';
|
||||
}
|
||||
|
||||
function validateNodeVersion() {
|
||||
let semver = '>= 0';
|
||||
let major = '1';
|
||||
|
||||
try {
|
||||
major = process.versions.node.split('.')[0];
|
||||
const pkg = require('../package.json');
|
||||
semver = pkg.engines.node;
|
||||
} catch (e) {
|
||||
debug('Failed to read package.json engines');
|
||||
}
|
||||
|
||||
const isValid = eval(`${major} ${semver}`);
|
||||
return { isValid, expected: semver, actual: process.versions.node };
|
||||
}
|
||||
|
||||
function isInNodeModules(name) {
|
||||
try {
|
||||
const nodeModules = join(__dirname, '..', '..');
|
||||
const stat = statSync(join(nodeModules, name));
|
||||
return stat.isDirectory();
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (!isGlobal()) {
|
||||
debug('Skipping preinstall since Vercel CLI is being installed locally');
|
||||
return;
|
||||
}
|
||||
|
||||
const ver = validateNodeVersion();
|
||||
|
||||
if (!ver.isValid) {
|
||||
error(
|
||||
`Detected unsupported Node.js version.\n` +
|
||||
`Expected "${ver.expected}" but found "${ver.actual}".\n` +
|
||||
`Please update to the latest Node.js LTS version to install Vercel CLI.`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (isVercel() && isInNodeModules('now')) {
|
||||
const uninstall = isYarn()
|
||||
? 'yarn global remove now'
|
||||
: 'npm uninstall -g now';
|
||||
console.error(`NOTE: Run \`${uninstall}\` to uninstall \`now\`\n`);
|
||||
}
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', err => {
|
||||
console.error('Unhandled Rejection:');
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', err => {
|
||||
console.error('Uncaught Exception:');
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
69
packages/cli/src/commands/bisect/command.ts
Normal file
69
packages/cli/src/commands/bisect/command.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Command } from '../help';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
export const bisectCommand: Command = {
|
||||
name: 'bisect',
|
||||
description: 'Bisect the current project interactively.',
|
||||
arguments: [],
|
||||
options: [
|
||||
{
|
||||
name: 'bad',
|
||||
description: 'Known bad URL',
|
||||
argument: 'URL',
|
||||
shorthand: 'b',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'good',
|
||||
description: 'Known good URL',
|
||||
argument: 'URL',
|
||||
shorthand: 'g',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'open',
|
||||
description: 'Automatically open each URL in the browser',
|
||||
argument: 'URL',
|
||||
shorthand: 'o',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'path',
|
||||
description: 'Subpath of the deployment URL to test',
|
||||
argument: 'URL',
|
||||
shorthand: 'p',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'run',
|
||||
description: 'Test script to run for each deployment',
|
||||
argument: 'URL',
|
||||
shorthand: 'r',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
name: 'Bisect the current project interactively',
|
||||
value: `${getPkgName()} bisect`,
|
||||
},
|
||||
{
|
||||
name: 'Bisect with a known bad deployment',
|
||||
value: `${getPkgName()} bisect --bad example-310pce9i0.vercel.app`,
|
||||
},
|
||||
{
|
||||
name: 'Automated bisect with a run script',
|
||||
value: `${getPkgName()} bisect --run ./test.sh`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -6,57 +6,21 @@ import chalk, { Chalk } from 'chalk';
|
||||
import { URLSearchParams, parse } from 'url';
|
||||
|
||||
import box from '../../util/output/box';
|
||||
import sleep from '../../util/sleep';
|
||||
import formatDate from '../../util/format-date';
|
||||
import link from '../../util/output/link';
|
||||
import logo from '../../util/output/logo';
|
||||
import getArgs from '../../util/get-args';
|
||||
import Client from '../../util/client';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
import { Deployment, PaginationOptions } from '@vercel-internals/types';
|
||||
import { Deployment } from '@vercel-internals/types';
|
||||
import { normalizeURL } from '../../util/bisect/normalize-url';
|
||||
import getScope from '../../util/get-scope';
|
||||
import getDeployment from '../../util/get-deployment';
|
||||
import { help } from '../help';
|
||||
import { bisectCommand } from './command';
|
||||
|
||||
interface Deployments {
|
||||
deployments: Deployment[];
|
||||
pagination: PaginationOptions;
|
||||
}
|
||||
|
||||
const pkgName = getPkgName();
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${pkgName} bisect`)} [options]
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-b, --bad Known bad URL
|
||||
-g, --good Known good URL
|
||||
-o, --open Automatically open each URL in the browser
|
||||
-p, --path Subpath of the deployment URL to test
|
||||
-r, --run Test script to run for each deployment
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Bisect the current project interactively
|
||||
|
||||
${chalk.cyan(`$ ${pkgName} bisect`)}
|
||||
|
||||
${chalk.gray('–')} Bisect with a known bad deployment
|
||||
|
||||
${chalk.cyan(`$ ${pkgName} bisect --bad example-310pce9i0.vercel.app`)}
|
||||
|
||||
${chalk.gray('–')} Automated bisect with a run script
|
||||
|
||||
${chalk.cyan(`$ ${pkgName} bisect --run ./test.sh`)}
|
||||
`);
|
||||
};
|
||||
|
||||
export default async function main(client: Client): Promise<number> {
|
||||
export default async function bisect(client: Client): Promise<number> {
|
||||
const { output } = client;
|
||||
const scope = await getScope(client);
|
||||
const { contextName } = scope;
|
||||
@@ -75,7 +39,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
});
|
||||
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
output.print(help(bisectCommand, { columns: client.stderr.columns }));
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -206,44 +170,34 @@ export default async function main(client: Client): Promise<number> {
|
||||
|
||||
// Fetch all the project's "READY" deployments with the pagination API
|
||||
let deployments: Deployment[] = [];
|
||||
let next: number | undefined = badDeployment.createdAt + 1;
|
||||
do {
|
||||
const query = new URLSearchParams();
|
||||
query.set('projectId', projectId);
|
||||
if (badDeployment.target) {
|
||||
query.set('target', badDeployment.target);
|
||||
}
|
||||
query.set('limit', '100');
|
||||
query.set('state', 'READY');
|
||||
if (next) {
|
||||
query.set('until', String(next));
|
||||
}
|
||||
|
||||
const res = await client.fetch<Deployments>(`/v6/deployments?${query}`, {
|
||||
const query = new URLSearchParams();
|
||||
query.set('projectId', projectId);
|
||||
if (badDeployment.target) {
|
||||
query.set('target', badDeployment.target);
|
||||
}
|
||||
query.set('state', 'READY');
|
||||
query.set('until', String(badDeployment.createdAt + 1));
|
||||
|
||||
for await (const chunk of client.fetchPaginated<Deployments>(
|
||||
`/v6/deployments?${query}`,
|
||||
{
|
||||
accountId: badDeployment.ownerId,
|
||||
});
|
||||
|
||||
next = res.pagination.next;
|
||||
|
||||
let newDeployments = res.deployments;
|
||||
}
|
||||
)) {
|
||||
let newDeployments = chunk.deployments;
|
||||
|
||||
// If we have the "good" deployment in this chunk, then we're done
|
||||
for (let i = 0; i < newDeployments.length; i++) {
|
||||
if (newDeployments[i].url === good) {
|
||||
// grab all deployments up until the good one
|
||||
newDeployments = newDeployments.slice(0, i);
|
||||
next = undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
deployments = deployments.concat(newDeployments);
|
||||
|
||||
if (next) {
|
||||
// Small sleep to avoid rate limiting
|
||||
await sleep(100);
|
||||
}
|
||||
} while (next);
|
||||
}
|
||||
|
||||
if (!deployments.length) {
|
||||
output.error(
|
||||
|
||||
46
packages/cli/src/commands/build/command.ts
Normal file
46
packages/cli/src/commands/build/command.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Command } from '../help';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
export const buildCommand: Command = {
|
||||
name: 'build',
|
||||
description: 'Build the project.',
|
||||
arguments: [],
|
||||
options: [
|
||||
{
|
||||
name: 'prod',
|
||||
description: 'Build a production deployment',
|
||||
shorthand: null,
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'output',
|
||||
description: 'Directory where built assets should be written to',
|
||||
shorthand: null,
|
||||
argument: 'PATH',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'yes',
|
||||
description:
|
||||
'Skip the confirmation prompt about pulling environment variables and project settings when not found locally',
|
||||
shorthand: 'y',
|
||||
type: 'boolean',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
name: 'Build the project',
|
||||
value: `${getPkgName()} build`,
|
||||
},
|
||||
{
|
||||
name: 'Build the project in a specific directory',
|
||||
value: `${getPkgName()} build --cwd ./path-to-project`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,7 +1,10 @@
|
||||
import fs from 'fs-extra';
|
||||
import chalk from 'chalk';
|
||||
import dotenv from 'dotenv';
|
||||
import semver from 'semver';
|
||||
import minimatch from 'minimatch';
|
||||
import { join, normalize, relative, resolve, sep } from 'path';
|
||||
import frameworks from '@vercel/frameworks';
|
||||
import {
|
||||
getDiscontinuedNodeVersions,
|
||||
normalizePath,
|
||||
@@ -17,13 +20,14 @@ import {
|
||||
BuildResultV3,
|
||||
NowBuildError,
|
||||
Cron,
|
||||
validateNpmrc,
|
||||
} from '@vercel/build-utils';
|
||||
import {
|
||||
detectBuilders,
|
||||
detectFrameworkRecord,
|
||||
detectFrameworkVersion,
|
||||
LocalFileSystemDetector,
|
||||
} from '@vercel/fs-detectors';
|
||||
import minimatch from 'minimatch';
|
||||
import {
|
||||
appendRoutesToPhase,
|
||||
getTransformedRoutes,
|
||||
@@ -34,39 +38,37 @@ import {
|
||||
import { fileNameSymbol } from '@vercel/client';
|
||||
import type { VercelConfig } from '@vercel/client';
|
||||
|
||||
import pull from './pull';
|
||||
import { staticFiles as getFiles } from '../util/get-files';
|
||||
import Client from '../util/client';
|
||||
import getArgs from '../util/get-args';
|
||||
import cmd from '../util/output/cmd';
|
||||
import * as cli from '../util/pkg-name';
|
||||
import cliPkg from '../util/pkg';
|
||||
import readJSONFile from '../util/read-json-file';
|
||||
import { CantParseJSONFile } from '../util/errors-ts';
|
||||
import pull from '../pull';
|
||||
import { staticFiles as getFiles } from '../../util/get-files';
|
||||
import Client from '../../util/client';
|
||||
import getArgs from '../../util/get-args';
|
||||
import cmd from '../../util/output/cmd';
|
||||
import * as cli from '../../util/pkg-name';
|
||||
import cliPkg from '../../util/pkg';
|
||||
import readJSONFile from '../../util/read-json-file';
|
||||
import { CantParseJSONFile } from '../../util/errors-ts';
|
||||
import {
|
||||
pickOverrides,
|
||||
ProjectLinkAndSettings,
|
||||
readProjectSettings,
|
||||
} from '../util/projects/project-settings';
|
||||
import { VERCEL_DIR } from '../util/projects/link';
|
||||
import confirm from '../util/input/confirm';
|
||||
import { emoji, prependEmoji } from '../util/emoji';
|
||||
import stamp from '../util/output/stamp';
|
||||
} from '../../util/projects/project-settings';
|
||||
import { getProjectLink, VERCEL_DIR } from '../../util/projects/link';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import { emoji, prependEmoji } from '../../util/emoji';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import {
|
||||
OUTPUT_DIR,
|
||||
PathOverride,
|
||||
writeBuildResult,
|
||||
} from '../util/build/write-build-result';
|
||||
import { importBuilders } from '../util/build/import-builders';
|
||||
import { initCorepack, cleanupCorepack } from '../util/build/corepack';
|
||||
import { sortBuilders } from '../util/build/sort-builders';
|
||||
import { toEnumerableError } from '../util/error';
|
||||
import { validateConfig } from '../util/validate-config';
|
||||
|
||||
import { setMonorepoDefaultSettings } from '../util/build/monorepo';
|
||||
import frameworks from '@vercel/frameworks';
|
||||
import { detectFrameworkVersion } from '@vercel/fs-detectors';
|
||||
import semver from 'semver';
|
||||
} from '../../util/build/write-build-result';
|
||||
import { importBuilders } from '../../util/build/import-builders';
|
||||
import { initCorepack, cleanupCorepack } from '../../util/build/corepack';
|
||||
import { sortBuilders } from '../../util/build/sort-builders';
|
||||
import { toEnumerableError } from '../../util/error';
|
||||
import { validateConfig } from '../../util/validate-config';
|
||||
import { setMonorepoDefaultSettings } from '../../util/build/monorepo';
|
||||
import { help } from '../help';
|
||||
import { buildCommand } from './command';
|
||||
|
||||
type BuildResult = BuildResultV2 | BuildResultV3;
|
||||
|
||||
@@ -103,37 +105,9 @@ export interface BuildsManifest {
|
||||
builds?: SerializedBuilder[];
|
||||
}
|
||||
|
||||
const help = () => {
|
||||
return console.log(`
|
||||
${chalk.bold(`${cli.logo} ${cli.name} build`)}
|
||||
|
||||
${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
|
||||
--cwd [path] The current working directory
|
||||
--output [path] Directory where built assets should be written to
|
||||
--prod Build a production deployment
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-y, --yes Skip the confirmation prompt about pulling environment variables and project settings when not found locally
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('-')} Build the project
|
||||
|
||||
${chalk.cyan(`$ ${cli.name} build`)}
|
||||
${chalk.cyan(`$ ${cli.name} build --cwd ./path-to-project`)}
|
||||
`);
|
||||
};
|
||||
|
||||
export default async function main(client: Client): Promise<number> {
|
||||
const { cwd, output } = client;
|
||||
let { cwd } = client;
|
||||
const { output } = client;
|
||||
|
||||
// Ensure that `vc build` is not being invoked recursively
|
||||
if (process.env.__VERCEL_BUILD_RUNNING) {
|
||||
@@ -161,7 +135,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
});
|
||||
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
output.print(help(buildCommand, { columns: client.stderr.columns }));
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -169,10 +143,25 @@ export default async function main(client: Client): Promise<number> {
|
||||
const target = argv['--prod'] ? 'production' : 'preview';
|
||||
const yes = Boolean(argv['--yes']);
|
||||
|
||||
try {
|
||||
await validateNpmrc(cwd);
|
||||
} catch (err) {
|
||||
output.prettyError(err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If repo linked, update `cwd` to the repo root
|
||||
const link = await getProjectLink(client, cwd);
|
||||
const projectRootDirectory = link?.projectRootDirectory ?? '';
|
||||
if (link?.repoRoot) {
|
||||
cwd = client.cwd = link.repoRoot;
|
||||
}
|
||||
|
||||
// TODO: read project settings from the API, fall back to local `project.json` if that fails
|
||||
|
||||
// Read project settings, and pull them from Vercel if necessary
|
||||
let project = await readProjectSettings(join(cwd, VERCEL_DIR));
|
||||
const vercelDir = join(cwd, projectRootDirectory, VERCEL_DIR);
|
||||
let project = await readProjectSettings(vercelDir);
|
||||
const isTTY = process.stdin.isTTY;
|
||||
while (!project?.settings) {
|
||||
let confirmed = yes;
|
||||
@@ -199,6 +188,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
return 0;
|
||||
}
|
||||
const { argv: originalArgv } = client;
|
||||
client.cwd = join(cwd, projectRootDirectory);
|
||||
client.argv = [
|
||||
...originalArgv.slice(0, 2),
|
||||
'pull',
|
||||
@@ -209,12 +199,13 @@ export default async function main(client: Client): Promise<number> {
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
client.cwd = cwd;
|
||||
client.argv = originalArgv;
|
||||
project = await readProjectSettings(join(cwd, VERCEL_DIR));
|
||||
project = await readProjectSettings(vercelDir);
|
||||
}
|
||||
|
||||
// Delete output directory from potential previous build
|
||||
const defaultOutputDir = join(cwd, OUTPUT_DIR);
|
||||
const defaultOutputDir = join(cwd, projectRootDirectory, OUTPUT_DIR);
|
||||
const outputDir = argv['--output']
|
||||
? resolve(argv['--output'])
|
||||
: defaultOutputDir;
|
||||
@@ -233,7 +224,12 @@ export default async function main(client: Client): Promise<number> {
|
||||
const envToUnset = new Set<string>(['VERCEL', 'NOW_BUILDER']);
|
||||
|
||||
try {
|
||||
const envPath = join(cwd, VERCEL_DIR, `.env.${target}.local`);
|
||||
const envPath = join(
|
||||
cwd,
|
||||
projectRootDirectory,
|
||||
VERCEL_DIR,
|
||||
`.env.${target}.local`
|
||||
);
|
||||
// TODO (maybe?): load env vars from the API, fall back to the local file if that fails
|
||||
const dotenvResult = dotenv.config({
|
||||
path: envPath,
|
||||
@@ -1,50 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import logo from '../../util/output/logo';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
export const help = () => {
|
||||
return `
|
||||
${chalk.bold(`${logo} ${getPkgName()} [deploy]`)} [path-to-project] [options]
|
||||
|
||||
--prod Create a production deployment
|
||||
-p, --public Deployment is public (${chalk.dim(
|
||||
'`/_src`'
|
||||
)} is exposed)
|
||||
-e, --env Include an env var during run time (e.g.: ${chalk.dim(
|
||||
'`-e KEY=value`'
|
||||
)}). Can appear many times.
|
||||
-b, --build-env Similar to ${chalk.dim(
|
||||
'`--env`'
|
||||
)} but for build time only.
|
||||
-m, --meta Add metadata for the deployment (e.g.: ${chalk.dim(
|
||||
'`-m KEY=value`'
|
||||
)}). Can appear many times.
|
||||
--no-wait Don't wait for the deployment to finish
|
||||
-f, --force Force a new deployment even if nothing has changed
|
||||
--with-cache Retain build cache when using "--force"
|
||||
--regions Set default regions to enable the deployment on
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Deploy the current directory
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()}`)}
|
||||
|
||||
${chalk.gray('–')} Deploy a custom path
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} /usr/src/project`)}
|
||||
|
||||
${chalk.gray('–')} Deploy with Environment Variables
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} -e NODE_ENV=production`)}
|
||||
|
||||
${chalk.gray('–')} Deploy with prebuilt outputs
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} build`)}
|
||||
${chalk.cyan(`$ ${getPkgName()} deploy --prebuilt`)}
|
||||
|
||||
${chalk.gray('–')} Write Deployment URL to a file
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} > deployment-url.txt`)}
|
||||
`;
|
||||
};
|
||||
181
packages/cli/src/commands/deploy/command.ts
Normal file
181
packages/cli/src/commands/deploy/command.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { Command } from '../help';
|
||||
|
||||
export const deployCommand: Command = {
|
||||
name: 'deploy',
|
||||
description:
|
||||
'Deploy your project to Vercel. The `deploy` command is the default command for the Vercel CLI, and can be omitted (`vc deploy my-app` equals `vc my-app`).',
|
||||
arguments: [
|
||||
{
|
||||
name: 'project-path',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: 'force',
|
||||
shorthand: 'f',
|
||||
type: 'boolean',
|
||||
deprecated: false,
|
||||
description: 'Force a new deployment even if nothing has changed',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'with-cache',
|
||||
shorthand: null,
|
||||
type: 'boolean',
|
||||
deprecated: false,
|
||||
description: 'Retain build cache when using "--force"',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'public',
|
||||
shorthand: 'p',
|
||||
type: 'boolean',
|
||||
deprecated: false,
|
||||
description: 'Deployment is public (`/_src`) is exposed)',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'env',
|
||||
shorthand: 'e',
|
||||
type: 'string',
|
||||
argument: 'key=value',
|
||||
deprecated: false,
|
||||
multi: true,
|
||||
description:
|
||||
'Specify environment variables during run-time (e.g. `-e KEY1=value1 -e KEY2=value2`)',
|
||||
},
|
||||
{
|
||||
name: 'build-env',
|
||||
shorthand: 'b',
|
||||
type: 'string',
|
||||
argument: 'key=value',
|
||||
deprecated: false,
|
||||
multi: true,
|
||||
description:
|
||||
'Specify environment variables during build-time (e.g. `-b KEY1=value1 -b KEY2=value2`)',
|
||||
},
|
||||
{
|
||||
name: 'meta',
|
||||
shorthand: 'm',
|
||||
type: 'string',
|
||||
argument: 'key=value',
|
||||
deprecated: false,
|
||||
multi: true,
|
||||
description:
|
||||
'Specify metadata for the deployment (e.g. `-m KEY1=value1 -m KEY2=value2`)',
|
||||
},
|
||||
{
|
||||
name: 'regions',
|
||||
shorthand: null,
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
description: 'Set default regions to enable the deployment on',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'prebuilt',
|
||||
shorthand: null,
|
||||
type: 'boolean',
|
||||
deprecated: false,
|
||||
description:
|
||||
'Use in combination with `vc build`. Deploy an existing build',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'prod',
|
||||
shorthand: null,
|
||||
type: 'boolean',
|
||||
deprecated: false,
|
||||
description: 'Create a production deployment',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'archive',
|
||||
shorthand: null,
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
description:
|
||||
'Compress the deployment code into a file before uploading it',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'no-wait',
|
||||
shorthand: null,
|
||||
type: 'boolean',
|
||||
deprecated: false,
|
||||
description: "Don't wait for the deployment to finish",
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'skip-domain',
|
||||
shorthand: null,
|
||||
type: 'boolean',
|
||||
deprecated: false,
|
||||
description: undefined,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'yes',
|
||||
shorthand: 'y',
|
||||
type: 'boolean',
|
||||
deprecated: false,
|
||||
description: 'Use default options to skip all prompts',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
shorthand: 'n',
|
||||
type: 'string',
|
||||
deprecated: true,
|
||||
description: 'Provide a Vercel Project name',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'no-clipboard',
|
||||
shorthand: null,
|
||||
type: 'boolean',
|
||||
deprecated: true,
|
||||
description: 'Do not copy deployment URL to clipboard',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'target',
|
||||
shorthand: null,
|
||||
type: 'string',
|
||||
deprecated: true,
|
||||
description: 'Specify the target deployment environment',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'confirm',
|
||||
shorthand: 'c',
|
||||
type: 'boolean',
|
||||
deprecated: true,
|
||||
description: 'Use default options to skip all prompts',
|
||||
multi: false,
|
||||
},
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
name: 'Deploy the current directory',
|
||||
value: 'vercel',
|
||||
},
|
||||
{
|
||||
name: 'Deploy a custom path',
|
||||
value: 'vercel /usr/src/project',
|
||||
},
|
||||
{
|
||||
name: 'Deploy with run-time Environment Variables',
|
||||
value: 'vercel -e NODE_ENV=production',
|
||||
},
|
||||
{
|
||||
name: 'Deploy with prebuilt outputs',
|
||||
value: ['vercel build', 'vercel deploy --prebuilt'],
|
||||
},
|
||||
{
|
||||
name: 'Write Deployment URL to a file',
|
||||
value: 'vercel > deployment-url.txt',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -59,7 +59,6 @@ import validatePaths, {
|
||||
} from '../../util/validate-paths';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { Output } from '../../util/output';
|
||||
import { help } from './args';
|
||||
import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
|
||||
import parseTarget from '../../util/deploy/parse-target';
|
||||
import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
|
||||
@@ -69,44 +68,38 @@ import { parseEnv } from '../../util/parse-env';
|
||||
import { errorToString, isErrnoException, isError } from '@vercel/error-utils';
|
||||
import { pickOverrides } from '../../util/projects/project-settings';
|
||||
import { printDeploymentStatus } from '../../util/deploy/print-deployment-status';
|
||||
import { help } from '../help';
|
||||
import { deployCommand } from './command';
|
||||
|
||||
export default async (client: Client): Promise<number> => {
|
||||
const { output } = client;
|
||||
|
||||
let argv = null;
|
||||
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--force': Boolean,
|
||||
'--with-cache': Boolean,
|
||||
'--public': Boolean,
|
||||
'--env': [String],
|
||||
'--build-env': [String],
|
||||
'--meta': [String],
|
||||
// This is not an array in favor of matching
|
||||
// the config property name.
|
||||
'--regions': String,
|
||||
'--prebuilt': Boolean,
|
||||
'--prod': Boolean,
|
||||
'--archive': String,
|
||||
'--no-wait': Boolean,
|
||||
'--skip-domain': Boolean,
|
||||
'--yes': Boolean,
|
||||
'-f': '--force',
|
||||
'-p': '--public',
|
||||
'-e': '--env',
|
||||
'-b': '--build-env',
|
||||
'-m': '--meta',
|
||||
'-y': '--yes',
|
||||
const argOptions: {
|
||||
[k: string]:
|
||||
| BooleanConstructor
|
||||
| StringConstructor
|
||||
| string
|
||||
| [StringConstructor];
|
||||
} = {};
|
||||
for (const option of deployCommand.options) {
|
||||
argOptions[`--${option.name}`] =
|
||||
option.type === 'boolean' ? Boolean : String;
|
||||
if (option.shorthand) {
|
||||
argOptions[`-${option.shorthand}`] = `--${option.name}`;
|
||||
}
|
||||
if (
|
||||
option.name === 'env' ||
|
||||
option.name === 'build-env' ||
|
||||
option.name === 'meta'
|
||||
) {
|
||||
argOptions[`--${option.name}`] = [String];
|
||||
}
|
||||
}
|
||||
|
||||
// deprecated
|
||||
'--name': String,
|
||||
'-n': '--name',
|
||||
'--no-clipboard': Boolean,
|
||||
'--target': String,
|
||||
'--confirm': Boolean,
|
||||
'-c': '--confirm',
|
||||
});
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), argOptions);
|
||||
|
||||
if ('--confirm' in argv) {
|
||||
output.warn('`--confirm` is deprecated, please use `--yes` instead');
|
||||
@@ -118,7 +111,7 @@ export default async (client: Client): Promise<number> => {
|
||||
}
|
||||
|
||||
if (argv['--help']) {
|
||||
output.print(help());
|
||||
output.print(help(deployCommand, { columns: client.stderr.columns }));
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -203,56 +196,6 @@ export default async (client: Client): Promise<number> => {
|
||||
return target;
|
||||
}
|
||||
|
||||
// build `--prebuilt`
|
||||
if (argv['--prebuilt']) {
|
||||
const prebuiltExists = await fs.pathExists(join(cwd, '.vercel/output'));
|
||||
if (!prebuiltExists) {
|
||||
error(
|
||||
`The ${param(
|
||||
'--prebuilt'
|
||||
)} option was used, but no prebuilt output found in ".vercel/output". Run ${getCommandName(
|
||||
'build'
|
||||
)} to generate a local build.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const prebuiltBuild = await getPrebuiltJson(cwd);
|
||||
|
||||
// Ensure that there was not a build error
|
||||
const prebuiltError =
|
||||
prebuiltBuild?.error ||
|
||||
prebuiltBuild?.builds?.find(build => 'error' in build)?.error;
|
||||
if (prebuiltError) {
|
||||
output.log(
|
||||
`Prebuilt deployment cannot be created because ${getCommandName(
|
||||
'build'
|
||||
)} failed with error:\n`
|
||||
);
|
||||
prettyError(prebuiltError);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Ensure that the deploy target matches the build target
|
||||
const assumedTarget = target || 'preview';
|
||||
if (prebuiltBuild?.target && prebuiltBuild.target !== assumedTarget) {
|
||||
let specifyTarget = '';
|
||||
if (prebuiltBuild.target === 'production') {
|
||||
specifyTarget = ` --prod`;
|
||||
}
|
||||
|
||||
prettyError({
|
||||
message: `The ${param(
|
||||
'--prebuilt'
|
||||
)} option was used with the target environment "${assumedTarget}", but the prebuilt output found in ".vercel/output" was built with target environment "${
|
||||
prebuiltBuild.target
|
||||
}". Please run ${getCommandName(`--prebuilt${specifyTarget}`)}.`,
|
||||
link: 'https://vercel.link/prebuilt-environment-mismatch',
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
const archive = argv['--archive'];
|
||||
if (typeof archive === 'string' && !isValidArchive(archive)) {
|
||||
output.error(`Format must be one of: ${VALID_ARCHIVE_FORMATS.join(', ')}`);
|
||||
@@ -355,6 +298,66 @@ export default async (client: Client): Promise<number> => {
|
||||
throw new Error(`"org" is not defined`);
|
||||
}
|
||||
|
||||
// build `--prebuilt`
|
||||
if (argv['--prebuilt']) {
|
||||
// For repo-style linking, update `cwd` to be the Project
|
||||
// subdirectory when `rootDirectory` setting is defined
|
||||
if (
|
||||
link.status === 'linked' &&
|
||||
link.repoRoot &&
|
||||
link.project.rootDirectory
|
||||
) {
|
||||
cwd = join(cwd, link.project.rootDirectory);
|
||||
}
|
||||
|
||||
const prebuiltExists = await fs.pathExists(join(cwd, '.vercel/output'));
|
||||
if (!prebuiltExists) {
|
||||
error(
|
||||
`The ${param(
|
||||
'--prebuilt'
|
||||
)} option was used, but no prebuilt output found in ".vercel/output". Run ${getCommandName(
|
||||
'build'
|
||||
)} to generate a local build.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const prebuiltBuild = await getPrebuiltJson(cwd);
|
||||
|
||||
// Ensure that there was not a build error
|
||||
const prebuiltError =
|
||||
prebuiltBuild?.error ||
|
||||
prebuiltBuild?.builds?.find(build => 'error' in build)?.error;
|
||||
if (prebuiltError) {
|
||||
output.log(
|
||||
`Prebuilt deployment cannot be created because ${getCommandName(
|
||||
'build'
|
||||
)} failed with error:\n`
|
||||
);
|
||||
prettyError(prebuiltError);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Ensure that the deploy target matches the build target
|
||||
const assumedTarget = target || 'preview';
|
||||
if (prebuiltBuild?.target && prebuiltBuild.target !== assumedTarget) {
|
||||
let specifyTarget = '';
|
||||
if (prebuiltBuild.target === 'production') {
|
||||
specifyTarget = ` --prod`;
|
||||
}
|
||||
|
||||
prettyError({
|
||||
message: `The ${param(
|
||||
'--prebuilt'
|
||||
)} option was used with the target environment "${assumedTarget}", but the prebuilt output found in ".vercel/output" was built with target environment "${
|
||||
prebuiltBuild.target
|
||||
}". Please run ${getCommandName(`--prebuilt${specifyTarget}`)}.`,
|
||||
link: 'https://vercel.link/prebuilt-environment-mismatch',
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the `contextName` and `currentTeam` as specified by the
|
||||
// Project Settings, so that API calls happen with the proper scope
|
||||
const contextName = org.slug;
|
||||
@@ -511,7 +514,8 @@ export default async (client: Client): Promise<number> => {
|
||||
}
|
||||
|
||||
try {
|
||||
const autoAssignCustomDomains = !argv['--skip-domain'];
|
||||
// if this flag is not set, use `undefined` to allow the project setting to be used
|
||||
const autoAssignCustomDomains = argv['--skip-domain'] ? false : undefined;
|
||||
|
||||
const createArgs: CreateOptions = {
|
||||
name,
|
||||
|
||||
@@ -57,8 +57,15 @@ export default async function dev(
|
||||
|
||||
let projectSettings: ProjectSettings | undefined;
|
||||
let envValues: Record<string, string> = {};
|
||||
let repoRoot: string | undefined;
|
||||
if (link.status === 'linked') {
|
||||
const { project, org } = link;
|
||||
|
||||
// If repo linked, update `cwd` to the repo root
|
||||
if (link.repoRoot) {
|
||||
repoRoot = cwd = link.repoRoot;
|
||||
}
|
||||
|
||||
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
|
||||
|
||||
projectSettings = project;
|
||||
@@ -76,6 +83,7 @@ export default async function dev(
|
||||
output,
|
||||
projectSettings,
|
||||
envValues,
|
||||
repoRoot,
|
||||
});
|
||||
|
||||
// listen to SIGTERM for graceful shutdown
|
||||
|
||||
1
packages/cli/src/commands/env/index.ts
vendored
1
packages/cli/src/commands/env/index.ts
vendored
@@ -167,6 +167,7 @@ export default async function main(client: Client) {
|
||||
case 'pull':
|
||||
return pull(
|
||||
client,
|
||||
link,
|
||||
project,
|
||||
target,
|
||||
argv,
|
||||
|
||||
25
packages/cli/src/commands/env/pull.ts
vendored
25
packages/cli/src/commands/env/pull.ts
vendored
@@ -2,7 +2,11 @@ import chalk from 'chalk';
|
||||
import { outputFile } from 'fs-extra';
|
||||
import { closeSync, openSync, readSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import type { Project, ProjectEnvTarget } from '@vercel-internals/types';
|
||||
import type {
|
||||
Project,
|
||||
ProjectEnvTarget,
|
||||
ProjectLinked,
|
||||
} from '@vercel-internals/types';
|
||||
import Client from '../../util/client';
|
||||
import { emoji, prependEmoji } from '../../util/emoji';
|
||||
import confirm from '../../util/input/confirm';
|
||||
@@ -19,6 +23,7 @@ import {
|
||||
createEnvObject,
|
||||
} from '../../util/env/diff-env-files';
|
||||
import { isErrnoException } from '@vercel/error-utils';
|
||||
import { addToGitIgnore } from '../../util/link/add-to-gitignore';
|
||||
|
||||
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
|
||||
|
||||
@@ -51,6 +56,7 @@ function tryReadHeadSync(path: string, length: number) {
|
||||
|
||||
export default async function pull(
|
||||
client: Client,
|
||||
link: ProjectLinked,
|
||||
project: Project,
|
||||
environment: ProjectEnvTarget,
|
||||
opts: Partial<Options>,
|
||||
@@ -136,11 +142,22 @@ export default async function pull(
|
||||
output.log('No changes found.');
|
||||
}
|
||||
|
||||
let isGitIgnoreUpdated = false;
|
||||
if (filename === '.env.local') {
|
||||
// When the file is `.env.local`, we also add it to `.gitignore`
|
||||
// to avoid accidentally committing it to git.
|
||||
// We use '.env*.local' to match the default .gitignore from
|
||||
// create-next-app template. See:
|
||||
// https://github.com/vercel/next.js/blob/06abd634899095b6cc28e6e8315b1e8b9c8df939/packages/create-next-app/templates/app/js/gitignore#L28
|
||||
const rootPath = link.repoRoot ?? cwd;
|
||||
isGitIgnoreUpdated = await addToGitIgnore(rootPath, '.env*.local');
|
||||
}
|
||||
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`${exists ? 'Updated' : 'Created'} ${chalk.bold(
|
||||
filename
|
||||
)} file ${chalk.gray(pullStamp())}`,
|
||||
`${exists ? 'Updated' : 'Created'} ${chalk.bold(filename)} file ${
|
||||
isGitIgnoreUpdated ? 'and added it to .gitignore' : ''
|
||||
} ${chalk.gray(pullStamp())}`,
|
||||
emoji('success')
|
||||
)}\n`
|
||||
);
|
||||
|
||||
320
packages/cli/src/commands/help.ts
Normal file
320
packages/cli/src/commands/help.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
import chalk from 'chalk';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import { LOGO, NAME } from '@vercel-internals/constants';
|
||||
|
||||
const INDENT = ' '.repeat(2);
|
||||
const NEWLINE = '\n';
|
||||
|
||||
export interface CommandOption {
|
||||
name: string;
|
||||
shorthand: string | null;
|
||||
type: 'boolean' | 'string';
|
||||
argument?: string;
|
||||
deprecated: boolean;
|
||||
description?: string;
|
||||
multi: boolean;
|
||||
}
|
||||
export interface CommandArgument {
|
||||
name: string;
|
||||
required: boolean;
|
||||
}
|
||||
export interface CommandExample {
|
||||
name: string;
|
||||
value: string | string[];
|
||||
}
|
||||
export interface Command {
|
||||
name: string;
|
||||
description: string;
|
||||
arguments: CommandArgument[];
|
||||
options: CommandOption[];
|
||||
examples: CommandExample[];
|
||||
}
|
||||
|
||||
const globalCommandOptions: CommandOption[] = [
|
||||
{
|
||||
name: 'help',
|
||||
shorthand: 'h',
|
||||
type: 'string',
|
||||
description: 'Output usage information',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
shorthand: 'v',
|
||||
type: 'string',
|
||||
description: 'Output the version number',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'cwd',
|
||||
shorthand: null,
|
||||
type: 'string',
|
||||
argument: 'DIR',
|
||||
description:
|
||||
'Sets the current working directory for a single run of a command',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'local-config',
|
||||
shorthand: 'A',
|
||||
type: 'string',
|
||||
argument: 'FILE',
|
||||
description: 'Path to the local `vercel.json` file',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'global-config',
|
||||
shorthand: 'Q',
|
||||
type: 'string',
|
||||
argument: 'DIR',
|
||||
description: 'Path to the global `.vercel` directory',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'debug',
|
||||
shorthand: 'd',
|
||||
type: 'string',
|
||||
description: 'Debug mode (default off)',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'no-color',
|
||||
shorthand: null,
|
||||
type: 'string',
|
||||
description: 'No color mode (default off)',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'scope',
|
||||
shorthand: 'S',
|
||||
type: 'string',
|
||||
description: 'Set a custom scope',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'token',
|
||||
shorthand: 't',
|
||||
type: 'string',
|
||||
argument: 'TOKEN',
|
||||
description: 'Login token',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
];
|
||||
|
||||
export function calcLineLength(line: string[]) {
|
||||
return stripAnsi(lineToString(line)).length;
|
||||
}
|
||||
|
||||
// Insert spaces in between non-whitespace items only
|
||||
export function lineToString(line: string[]) {
|
||||
let string = '';
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
if (i === line.length - 1) {
|
||||
string += line[i];
|
||||
} else {
|
||||
const curr = line[i];
|
||||
const next = line[i + 1];
|
||||
string += curr;
|
||||
if (curr.trim() !== '' && next.trim() !== '') {
|
||||
string += ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
export function outputArrayToString(outputArray: (string | null)[]) {
|
||||
return outputArray.filter(line => line !== null).join(NEWLINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: `▲ vercel deploy [path] [options]`
|
||||
* @param command
|
||||
* @returns
|
||||
*/
|
||||
export function buildCommandSynopsisLine(command: Command) {
|
||||
const line: string[] = [
|
||||
INDENT,
|
||||
LOGO,
|
||||
chalk.bold(NAME),
|
||||
chalk.bold(command.name),
|
||||
];
|
||||
if (command.arguments.length > 0) {
|
||||
for (const argument of command.arguments) {
|
||||
line.push(argument.required ? argument.name : `[${argument.name}]`);
|
||||
}
|
||||
}
|
||||
if (command.options.length > 0) {
|
||||
line.push('[options]');
|
||||
}
|
||||
|
||||
line.push(NEWLINE);
|
||||
return lineToString(line);
|
||||
}
|
||||
|
||||
export function buildCommandOptionLines(
|
||||
commandOptions: CommandOption[],
|
||||
options: BuildHelpOutputOptions,
|
||||
sectionTitle: String
|
||||
) {
|
||||
// Filter out deprecated and intentionally undocumented options
|
||||
commandOptions = commandOptions.filter(
|
||||
option => !option.deprecated && option.description !== undefined
|
||||
);
|
||||
|
||||
if (commandOptions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Initialize output array with header and empty line
|
||||
const outputArray: string[] = [`${INDENT}${chalk.dim(sectionTitle)}:`, ''];
|
||||
|
||||
// Start building option lines
|
||||
const optionLines: string[][] = [];
|
||||
// Sort command options alphabetically
|
||||
commandOptions.sort((a, b) =>
|
||||
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
||||
);
|
||||
// Keep track of longest "start" of an option line to determine description spacing
|
||||
let maxLineStartLength = 0;
|
||||
// Iterate over options and create the "start" of each option (e.g. ` -b, --build-env <key=value>`)
|
||||
for (const option of commandOptions) {
|
||||
const startLine: string[] = [INDENT, INDENT, INDENT];
|
||||
if (option.shorthand) {
|
||||
startLine.push(`-${option.shorthand},`);
|
||||
}
|
||||
startLine.push(`--${option.name}`);
|
||||
if (option.argument) {
|
||||
startLine.push(`<${option.argument}>`);
|
||||
}
|
||||
// the length includes the INDENT
|
||||
const lineLength = calcLineLength(startLine);
|
||||
maxLineStartLength = Math.max(lineLength, maxLineStartLength);
|
||||
optionLines.push(startLine);
|
||||
}
|
||||
/*
|
||||
* Iterate over in-progress option lines to add space-filler and description
|
||||
* For Example:
|
||||
* | --archive My description starts here.
|
||||
* |
|
||||
* | -b, --build-env <key=value> Start of description here then
|
||||
* | it wraps here.
|
||||
* |
|
||||
* | -e, --env <key=value> My description is short.
|
||||
*
|
||||
* Breaking down option lines:
|
||||
* | -b, --build-env <key=value> Start of description here then
|
||||
* |[][ ][][ ]
|
||||
* |↑ ↑ ↑ ↑
|
||||
* |1 2 3 4
|
||||
* | it wraps here.
|
||||
* |[][ ][ ]
|
||||
* |↑ ↑ ↑
|
||||
* |5 6 7
|
||||
* | 1, 5 = indent
|
||||
* | 2 = start
|
||||
* | 3, 6 = space-filler
|
||||
* | 4, 7 = description
|
||||
*/
|
||||
for (let i = 0; i < optionLines.length; i++) {
|
||||
const optionLine = optionLines[i];
|
||||
const option = commandOptions[i];
|
||||
// Add only 2 spaces to the longest line, and then make all shorter lines the same length.
|
||||
optionLine.push(
|
||||
' '.repeat(2 + (maxLineStartLength - calcLineLength(optionLine)))
|
||||
);
|
||||
|
||||
// Descriptions may be longer than max line length. Wrap them to the same column as the first description line
|
||||
const lines: string[][] = [optionLine];
|
||||
if (option.description) {
|
||||
for (const descriptionWord of option.description.split(' ')) {
|
||||
// insert a new line when the next word would match or exceed the maximum line length
|
||||
if (
|
||||
calcLineLength(lines[lines.length - 1]) +
|
||||
stripAnsi(descriptionWord).length >=
|
||||
options.columns
|
||||
) {
|
||||
// initialize the new line with the necessary whitespace. The INDENT is apart of `maxLineStartLength`
|
||||
lines.push([' '.repeat(maxLineStartLength + 2)]);
|
||||
}
|
||||
// insert the word to the current last line
|
||||
lines[lines.length - 1].push(descriptionWord);
|
||||
}
|
||||
}
|
||||
// for every line, transform into a string and push it to the output
|
||||
for (const line of lines) {
|
||||
outputArray.push(lineToString(line));
|
||||
}
|
||||
}
|
||||
|
||||
return `${outputArrayToString(outputArray)}${NEWLINE}`;
|
||||
}
|
||||
|
||||
export function buildCommandExampleLines(command: Command) {
|
||||
const outputArray: string[] = [`${INDENT}${chalk.dim('Examples:')}`, ''];
|
||||
for (const example of command.examples) {
|
||||
const nameLine: string[] = [INDENT];
|
||||
nameLine.push(chalk.gray('-'));
|
||||
nameLine.push(example.name);
|
||||
outputArray.push(lineToString(nameLine));
|
||||
outputArray.push('');
|
||||
const buildValueLine = (value: string) => {
|
||||
return lineToString([INDENT, INDENT, chalk.cyan(`$ ${value}`)]);
|
||||
};
|
||||
if (Array.isArray(example.value)) {
|
||||
for (const line of example.value) {
|
||||
outputArray.push(buildValueLine(line));
|
||||
}
|
||||
} else {
|
||||
outputArray.push(buildValueLine(example.value));
|
||||
}
|
||||
outputArray.push('');
|
||||
}
|
||||
|
||||
return outputArrayToString(outputArray);
|
||||
}
|
||||
|
||||
function buildDescriptionLine(command: Command) {
|
||||
const line: string[] = [INDENT, command.description, NEWLINE];
|
||||
return lineToString(line);
|
||||
}
|
||||
|
||||
interface BuildHelpOutputOptions {
|
||||
columns: number;
|
||||
}
|
||||
|
||||
export function buildHelpOutput(
|
||||
command: Command,
|
||||
options: BuildHelpOutputOptions
|
||||
) {
|
||||
const outputArray: (string | null)[] = [
|
||||
'',
|
||||
buildCommandSynopsisLine(command),
|
||||
buildDescriptionLine(command),
|
||||
buildCommandOptionLines(command.options, options, 'Options'),
|
||||
buildCommandOptionLines(globalCommandOptions, options, 'Global Options'),
|
||||
buildCommandExampleLines(command),
|
||||
'',
|
||||
];
|
||||
|
||||
return outputArrayToString(outputArray);
|
||||
}
|
||||
|
||||
export interface HelpOptions {
|
||||
columns?: number;
|
||||
}
|
||||
|
||||
export function help(command: Command, options: HelpOptions) {
|
||||
return buildHelpOutput(command, {
|
||||
columns: options.columns ?? 80,
|
||||
});
|
||||
}
|
||||
50
packages/cli/src/commands/inspect/command.ts
Normal file
50
packages/cli/src/commands/inspect/command.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Command } from '../help';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
export const inspectCommand: Command = {
|
||||
name: 'inspect',
|
||||
description: 'Show information about a deployment.',
|
||||
arguments: [
|
||||
{
|
||||
name: 'url',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: 'timeout',
|
||||
description: 'Time to wait for deployment completion [3m]',
|
||||
argument: 'TIME',
|
||||
shorthand: null,
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'wait',
|
||||
description: 'Blocks until deployment completes',
|
||||
shorthand: null,
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
name: 'Get information about a deployment by its unique URL',
|
||||
value: `${getPkgName()} inspect my-deployment-ji2fjij2.vercel.app`,
|
||||
},
|
||||
{
|
||||
name: 'Get information about the deployment an alias points to',
|
||||
value: `${getPkgName()} inspect my-deployment.vercel.app`,
|
||||
},
|
||||
{
|
||||
name: 'Get information about a deployment by piping in the URL',
|
||||
value: `echo my-deployment.vercel.app | ${getPkgName()} inspect`,
|
||||
},
|
||||
{
|
||||
name: 'Wait up to 90 seconds for deployment to complete',
|
||||
value: `${getPkgName()} inspect my-deployment.vercel.app --wait --timeout 90s`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,71 +1,27 @@
|
||||
import chalk from 'chalk';
|
||||
import getArgs from '../util/get-args';
|
||||
import buildsList from '../util/output/builds';
|
||||
import routesList from '../util/output/routes';
|
||||
import indent from '../util/output/indent';
|
||||
import logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed';
|
||||
import { handleError } from '../util/error';
|
||||
import getScope from '../util/get-scope';
|
||||
import { getPkgName, getCommandName } from '../util/pkg-name';
|
||||
import Client from '../util/client';
|
||||
import getDeployment from '../util/get-deployment';
|
||||
import getArgs from '../../util/get-args';
|
||||
import buildsList from '../../util/output/builds';
|
||||
import routesList from '../../util/output/routes';
|
||||
import indent from '../../util/output/indent';
|
||||
import elapsed from '../../util/output/elapsed';
|
||||
import { handleError } from '../../util/error';
|
||||
import getScope from '../../util/get-scope';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import Client from '../../util/client';
|
||||
import getDeployment from '../../util/get-deployment';
|
||||
import type { Build, Deployment } from '@vercel-internals/types';
|
||||
import title from 'title';
|
||||
import { isErrnoException } from '@vercel/error-utils';
|
||||
import { URL } from 'url';
|
||||
import readStandardInput from '../util/input/read-standard-input';
|
||||
import sleep from '../util/sleep';
|
||||
import readStandardInput from '../../util/input/read-standard-input';
|
||||
import sleep from '../../util/sleep';
|
||||
import ms from 'ms';
|
||||
import { isDeploying } from '../util/deploy/is-deploying';
|
||||
import { isDeploying } from '../../util/deploy/is-deploying';
|
||||
import { help } from '../help';
|
||||
import { inspectCommand } from './command';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${getPkgName()} inspect`)} <url>
|
||||
|
||||
${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
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-d, --debug Debug mode [off]
|
||||
--no-color No color mode [off]
|
||||
-S, --scope Set a custom scope
|
||||
--timeout=${chalk.bold.underline(
|
||||
'TIME'
|
||||
)} Time to wait for deployment completion [3m]
|
||||
--wait Blocks until deployment completes
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Get information about a deployment by its unique URL
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment-ji2fjij2.vercel.app`)}
|
||||
|
||||
${chalk.gray('-')} Get information about the deployment an alias points to
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment.vercel.app`)}
|
||||
|
||||
${chalk.gray('-')} Get information about a deployment by piping in the URL
|
||||
|
||||
${chalk.cyan(`$ echo my-deployment.vercel.app | ${getPkgName()} inspect`)}
|
||||
|
||||
${chalk.gray('-')} Wait up to 90 seconds for deployment to complete
|
||||
|
||||
${chalk.cyan(
|
||||
`$ ${getPkgName()} inspect my-deployment.vercel.app --wait --timeout 90s`
|
||||
)}
|
||||
`);
|
||||
};
|
||||
|
||||
export default async function main(client: Client) {
|
||||
export default async function inspect(client: Client) {
|
||||
const { output } = client;
|
||||
let argv;
|
||||
|
||||
try {
|
||||
@@ -79,7 +35,7 @@ export default async function main(client: Client) {
|
||||
}
|
||||
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
output.print(help(inspectCommand, { columns: client.stderr.columns }));
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -99,7 +55,7 @@ export default async function main(client: Client) {
|
||||
|
||||
if (!deploymentIdOrHost) {
|
||||
error(`${getCommandName('inspect <url>')} expects exactly one argument`);
|
||||
help();
|
||||
output.print(help(inspectCommand, { columns: client.stderr.columns }));
|
||||
return 1;
|
||||
}
|
||||
|
||||
54
packages/cli/src/commands/link/command.ts
Normal file
54
packages/cli/src/commands/link/command.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Command } from '../help';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
export const linkCommand: Command = {
|
||||
name: 'link',
|
||||
description: 'Link a local directory to a Vercel Project.',
|
||||
arguments: [],
|
||||
options: [
|
||||
{
|
||||
name: 'repo',
|
||||
description: 'Link multiple projects based on Git repository (alpha)',
|
||||
shorthand: 'r',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'project',
|
||||
description: 'Specify a project name',
|
||||
shorthand: 'p',
|
||||
argument: 'NAME',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'yes',
|
||||
description:
|
||||
'Skip questions when setting up new project using default scope and settings',
|
||||
shorthand: 'y',
|
||||
type: 'boolean',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
name: 'Link current directory to a Vercel Project',
|
||||
value: `${getPkgName()} link`,
|
||||
},
|
||||
{
|
||||
name: 'Link current directory with default options and skip questions',
|
||||
value: `${getPkgName()} link --yes`,
|
||||
},
|
||||
{
|
||||
name: 'Link a specific directory to a Vercel Project',
|
||||
value: `${getPkgName()} link --cwd /path/to/project`,
|
||||
},
|
||||
{
|
||||
name: 'Link to the current Git repository, allowing for multiple Vercel Projects to be linked simultaneously (useful for monorepos)',
|
||||
value: `${getPkgName()} link --repo`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,62 +1,12 @@
|
||||
import chalk from 'chalk';
|
||||
import Client from '../../util/client';
|
||||
import getArgs from '../../util/get-args';
|
||||
import logo from '../../util/output/logo';
|
||||
import cmd from '../../util/output/cmd';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
import { ensureLink } from '../../util/link/ensure-link';
|
||||
import { ensureRepoLink } from '../../util/link/repo';
|
||||
import { help } from '../help';
|
||||
import { linkCommand } from './command';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${getPkgName()} link`)} [options]
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
-r, --repo Link multiple projects based on Git repository (alpha)
|
||||
-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]
|
||||
--no-color No color mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-p ${chalk.bold.underline('NAME')}, --project=${chalk.bold.underline(
|
||||
'NAME'
|
||||
)} Project name
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Link current directory to a Vercel Project
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} link`)}
|
||||
|
||||
${chalk.gray(
|
||||
'–'
|
||||
)} Link current directory with default options and skip questions
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} link --yes`)}
|
||||
|
||||
${chalk.gray('–')} Link a specific directory to a Vercel Project
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} link --cwd /path/to/project`)}
|
||||
|
||||
${chalk.gray('–')} ${chalk.yellow(
|
||||
'(alpha)'
|
||||
)} Link to the current Git repository, allowing for multiple
|
||||
Vercel Projects to be linked simultaneously (useful for monorepos)
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} link --repo`)}
|
||||
`);
|
||||
};
|
||||
|
||||
export default async function main(client: Client) {
|
||||
export default async function link(client: Client) {
|
||||
const argv = getArgs(client.argv.slice(2), {
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
@@ -71,7 +21,7 @@ export default async function main(client: Client) {
|
||||
});
|
||||
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
client.output.print(help(linkCommand, { columns: client.stderr.columns }));
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -97,7 +47,7 @@ export default async function main(client: Client) {
|
||||
client.output.warn(
|
||||
`The ${cmd('--repo')} flag is in alpha, please report issues`
|
||||
);
|
||||
await ensureRepoLink(client, cwd, yes);
|
||||
await ensureRepoLink(client, cwd, { yes, overwrite: true });
|
||||
} else {
|
||||
const link = await ensureLink('link', client, cwd, {
|
||||
autoConfirm: yes,
|
||||
|
||||
61
packages/cli/src/commands/list/command.ts
Normal file
61
packages/cli/src/commands/list/command.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Command } from '../help';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
export const listCommand: Command = {
|
||||
name: 'list',
|
||||
description: 'List app deployments for an app.',
|
||||
arguments: [
|
||||
{
|
||||
name: 'app',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: 'meta',
|
||||
description:
|
||||
'Filter deployments by metadata (e.g.: `-m KEY=value`). Can appear many times.',
|
||||
argument: 'KEY=value',
|
||||
shorthand: null,
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: true,
|
||||
},
|
||||
{
|
||||
name: 'environment',
|
||||
description: '',
|
||||
argument: 'production|preview',
|
||||
shorthand: null,
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'next',
|
||||
description: 'Show next page of results',
|
||||
argument: 'MS',
|
||||
shorthand: 'n',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
name: 'List all deployments for the currently linked project',
|
||||
value: `${getPkgName()} list`,
|
||||
},
|
||||
{
|
||||
name: 'List all deployments for the project `my-app` in the team of the currently linked project',
|
||||
value: `${getPkgName()} list my-app`,
|
||||
},
|
||||
{
|
||||
name: 'Filter deployments by metadata',
|
||||
value: `${getPkgName()} list -m key1=value1 -m key2=value2`,
|
||||
},
|
||||
{
|
||||
name: 'Paginate deployments for a project, where `1584722256178` is the time in milliseconds since the UNIX epoch',
|
||||
value: `${getPkgName()} list my-app --next 1584722256178`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -2,85 +2,37 @@ 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 logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed';
|
||||
import strlen from '../util/strlen';
|
||||
import toHost from '../util/to-host';
|
||||
import parseMeta from '../util/parse-meta';
|
||||
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 Now from '../../util';
|
||||
import getArgs from '../../util/get-args';
|
||||
import { handleError } from '../../util/error';
|
||||
import elapsed from '../../util/output/elapsed';
|
||||
import strlen from '../../util/strlen';
|
||||
import toHost from '../../util/to-host';
|
||||
import parseMeta from '../../util/parse-meta';
|
||||
import { isValidName } from '../../util/is-valid-name';
|
||||
import getCommandFlags from '../../util/get-command-flags';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import Client from '../../util/client';
|
||||
import { Deployment } from '@vercel/client';
|
||||
import { getLinkedProject } from '../util/projects/link';
|
||||
import { ensureLink } from '../util/link/ensure-link';
|
||||
import getScope from '../util/get-scope';
|
||||
import { isAPIError } from '../util/errors-ts';
|
||||
import { getLinkedProject } from '../../util/projects/link';
|
||||
import { ensureLink } from '../../util/link/ensure-link';
|
||||
import getScope from '../../util/get-scope';
|
||||
import { isAPIError } from '../../util/errors-ts';
|
||||
import { isErrnoException } from '@vercel/error-utils';
|
||||
import { help } from '../help';
|
||||
import { listCommand } from './command';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${getPkgName()} list`)} [app]
|
||||
|
||||
${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]
|
||||
--no-color No color mode [off]
|
||||
-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
|
||||
-S, --scope Set a custom scope
|
||||
-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:')}
|
||||
|
||||
${chalk.gray('–')} List all deployments for the currently linked project
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} ls`)}
|
||||
|
||||
${chalk.gray('–')} List all deployments for the project ${chalk.dim(
|
||||
'`my-app`'
|
||||
)} in the team of the currently linked project
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} ls my-app`)}
|
||||
|
||||
${chalk.gray('–')} Filter deployments by metadata
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} ls -m key1=value1 -m key2=value2`)}
|
||||
|
||||
${chalk.gray('–')} Paginate deployments for a project, where ${chalk.dim(
|
||||
'`1584722256178`'
|
||||
)} is the time in milliseconds since the UNIX epoch.
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} ls my-app --next 1584722256178`)}
|
||||
`);
|
||||
};
|
||||
|
||||
export default async function main(client: Client) {
|
||||
export default async function list(client: Client) {
|
||||
let argv;
|
||||
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--environment': String,
|
||||
'--meta': [String],
|
||||
'-m': '--meta',
|
||||
'--next': Number,
|
||||
'-N': '--next',
|
||||
'--prod': Boolean,
|
||||
'--prod': Boolean, // this can be deprecated someday
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
|
||||
@@ -108,14 +60,19 @@ export default async function main(client: Client) {
|
||||
}
|
||||
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
output.print(help(listCommand, { columns: client.stderr.columns }));
|
||||
return 2;
|
||||
}
|
||||
|
||||
const autoConfirm = !!argv['--yes'];
|
||||
const prod = argv['--prod'] || false;
|
||||
const meta = parseMeta(argv['--meta']);
|
||||
|
||||
const target = argv['--prod']
|
||||
? 'production'
|
||||
: typeof argv['--environment'] === 'string'
|
||||
? argv['--environment'].toLowerCase()
|
||||
: undefined;
|
||||
|
||||
// retrieve `project` and `org` from .vercel
|
||||
let link = await getLinkedProject(client, cwd);
|
||||
|
||||
@@ -219,15 +176,12 @@ export default async function main(client: Client) {
|
||||
|
||||
debug('Fetching deployments');
|
||||
|
||||
const response = await now.list(
|
||||
app,
|
||||
{
|
||||
version: 6,
|
||||
meta,
|
||||
nextTimestamp,
|
||||
},
|
||||
prod
|
||||
);
|
||||
const response = await now.list(app, {
|
||||
version: 6,
|
||||
meta,
|
||||
nextTimestamp,
|
||||
target,
|
||||
});
|
||||
|
||||
let {
|
||||
deployments,
|
||||
@@ -280,9 +234,11 @@ export default async function main(client: Client) {
|
||||
}
|
||||
|
||||
log(
|
||||
`${prod ? `Production deployments` : `Deployments`} for ${chalk.bold(
|
||||
app
|
||||
)} under ${chalk.bold(contextName)} ${elapsed(Date.now() - start)}`
|
||||
`${
|
||||
target === 'production' ? `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
|
||||
@@ -292,8 +248,9 @@ export default async function main(client: Client) {
|
||||
|
||||
print('\n');
|
||||
|
||||
const headers = ['Age', 'Deployment', 'Status', 'Duration'];
|
||||
const headers = ['Age', 'Deployment', 'Status', 'Environment', 'Duration'];
|
||||
if (showUsername) headers.push('Username');
|
||||
const urls: string[] = [];
|
||||
|
||||
client.output.print(
|
||||
`${table(
|
||||
@@ -301,18 +258,17 @@ export default async function main(client: Client) {
|
||||
headers.map(header => chalk.bold(chalk.cyan(header))),
|
||||
...deployments
|
||||
.sort(sortRecent())
|
||||
.map(dep => [
|
||||
[
|
||||
.map(dep => {
|
||||
urls.push(`https://${dep.url}`);
|
||||
return [
|
||||
chalk.gray(ms(Date.now() - dep.createdAt)),
|
||||
`https://${dep.url}`,
|
||||
stateString(dep.state || ''),
|
||||
dep.target === 'production' ? 'Production' : 'Preview',
|
||||
chalk.gray(getDeploymentDuration(dep)),
|
||||
showUsername ? chalk.gray(dep.creator?.username) : '',
|
||||
],
|
||||
])
|
||||
// flatten since the previous step returns a nested
|
||||
// array of the deployment and (optionally) its instances
|
||||
.flat()
|
||||
];
|
||||
})
|
||||
.filter(app =>
|
||||
// if an app wasn't supplied to filter by,
|
||||
// we only want to render one deployment per app
|
||||
@@ -327,6 +283,11 @@ export default async function main(client: Client) {
|
||||
).replace(/^/gm, ' ')}\n\n`
|
||||
);
|
||||
|
||||
if (!client.stdout.isTTY) {
|
||||
client.stdout.write(urls.join('\n'));
|
||||
client.stdout.write('\n');
|
||||
}
|
||||
|
||||
if (pagination && pagination.count === 20) {
|
||||
const flags = getCommandFlags(argv, ['_', '--next']);
|
||||
log(
|
||||
49
packages/cli/src/commands/login/command.ts
Normal file
49
packages/cli/src/commands/login/command.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Command } from '../help';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
export const loginCommand: Command = {
|
||||
name: 'login',
|
||||
description: 'Authenticate using your email or team id.',
|
||||
arguments: [
|
||||
{
|
||||
name: 'email or team id',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: 'github',
|
||||
description: 'Log in with GitHub',
|
||||
shorthand: null,
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'oob',
|
||||
description: 'Log in with "out of band" authentication',
|
||||
shorthand: null,
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
name: 'Log into the Vercel platform',
|
||||
value: `${getPkgName()} login`,
|
||||
},
|
||||
{
|
||||
name: 'Log in using a specific email address',
|
||||
value: `${getPkgName()} login username@example.com`,
|
||||
},
|
||||
{
|
||||
name: 'Log in using a specific team "slug" for SAML Single Sign-On',
|
||||
value: `${getPkgName()} login acme`,
|
||||
},
|
||||
{
|
||||
name: 'Log in using GitHub in "out-of-band" mode',
|
||||
value: `${getPkgName()} login --github --oob`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,55 +1,24 @@
|
||||
import { validate as validateEmail } from 'email-validator';
|
||||
import chalk from 'chalk';
|
||||
import hp from '../util/humanize-path';
|
||||
import getArgs from '../util/get-args';
|
||||
import logo from '../util/output/logo';
|
||||
import prompt from '../util/login/prompt';
|
||||
import doSamlLogin from '../util/login/saml';
|
||||
import doEmailLogin from '../util/login/email';
|
||||
import doGithubLogin from '../util/login/github';
|
||||
import doGitlabLogin from '../util/login/gitlab';
|
||||
import doBitbucketLogin from '../util/login/bitbucket';
|
||||
import { prependEmoji, emoji } from '../util/emoji';
|
||||
import { getCommandName, getPkgName } from '../util/pkg-name';
|
||||
import getGlobalPathConfig from '../util/config/global-path';
|
||||
import { writeToAuthConfigFile, writeToConfigFile } from '../util/config/files';
|
||||
import Client from '../util/client';
|
||||
import { LoginResult } from '../util/login/types';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${getPkgName()} login`)} <email or team>
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
--no-color No color mode [off]
|
||||
-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
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Log into the Vercel platform
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} login`)}
|
||||
|
||||
${chalk.gray('–')} Log in using a specific email address
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} login john@doe.com`)}
|
||||
|
||||
${chalk.gray('–')} Log in using a specific team "slug" for SAML Single Sign-On
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} login acme`)}
|
||||
|
||||
${chalk.gray('–')} Log in using GitHub in "out-of-band" mode
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} login --github --oob`)}
|
||||
`);
|
||||
};
|
||||
import hp from '../../util/humanize-path';
|
||||
import getArgs from '../../util/get-args';
|
||||
import prompt from '../../util/login/prompt';
|
||||
import doSamlLogin from '../../util/login/saml';
|
||||
import doEmailLogin from '../../util/login/email';
|
||||
import doGithubLogin from '../../util/login/github';
|
||||
import doGitlabLogin from '../../util/login/gitlab';
|
||||
import doBitbucketLogin from '../../util/login/bitbucket';
|
||||
import { prependEmoji, emoji } from '../../util/emoji';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import getGlobalPathConfig from '../../util/config/global-path';
|
||||
import {
|
||||
writeToAuthConfigFile,
|
||||
writeToConfigFile,
|
||||
} from '../../util/config/files';
|
||||
import Client from '../../util/client';
|
||||
import { LoginResult } from '../../util/login/types';
|
||||
import { help } from '../help';
|
||||
import { loginCommand } from './command';
|
||||
|
||||
export default async function login(client: Client): Promise<number> {
|
||||
const { output } = client;
|
||||
@@ -62,7 +31,7 @@ export default async function login(client: Client): Promise<number> {
|
||||
});
|
||||
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
output.print(help(loginCommand, { columns: client.stderr.columns }));
|
||||
return 2;
|
||||
}
|
||||
|
||||
15
packages/cli/src/commands/logout/command.ts
Normal file
15
packages/cli/src/commands/logout/command.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Command } from '../help';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
export const logoutCommand: Command = {
|
||||
name: 'logout',
|
||||
description: 'Logout the current authenticated user or team.',
|
||||
arguments: [],
|
||||
options: [],
|
||||
examples: [
|
||||
{
|
||||
name: 'Logout from the CLI',
|
||||
value: `${getPkgName()} logout`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,37 +1,19 @@
|
||||
import chalk from 'chalk';
|
||||
import logo from '../util/output/logo';
|
||||
import { handleError } from '../util/error';
|
||||
import { writeToConfigFile, writeToAuthConfigFile } from '../util/config/files';
|
||||
import getArgs from '../util/get-args';
|
||||
import Client from '../util/client';
|
||||
import { getCommandName, getPkgName } from '../util/pkg-name';
|
||||
import { isAPIError } from '../util/errors-ts';
|
||||
import { handleError } from '../../util/error';
|
||||
import {
|
||||
writeToConfigFile,
|
||||
writeToAuthConfigFile,
|
||||
} from '../../util/config/files';
|
||||
import getArgs from '../../util/get-args';
|
||||
import Client from '../../util/client';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { isAPIError } from '../../util/errors-ts';
|
||||
import { errorToString } from '@vercel/error-utils';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${getPkgName()} logout`)}
|
||||
|
||||
${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
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Logout from the CLI:
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} logout`)}
|
||||
`);
|
||||
};
|
||||
import { help } from '../help';
|
||||
import { logoutCommand } from './command';
|
||||
|
||||
export default async function main(client: Client): Promise<number> {
|
||||
let argv;
|
||||
const { authConfig, config, output } = client;
|
||||
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
@@ -44,12 +26,10 @@ export default async function main(client: Client): Promise<number> {
|
||||
}
|
||||
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
output.print(help(logoutCommand, { columns: client.stderr.columns }));
|
||||
return 2;
|
||||
}
|
||||
|
||||
const { authConfig, config, output } = client;
|
||||
|
||||
if (!authConfig.token) {
|
||||
output.note(
|
||||
`Not currently logged in, so ${getCommandName('logout')} did nothing`
|
||||
66
packages/cli/src/commands/logs/command.ts
Normal file
66
packages/cli/src/commands/logs/command.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Command } from '../help';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
export const logsCommand: Command = {
|
||||
name: 'logs',
|
||||
description: 'Display logs for a specific deployment.',
|
||||
arguments: [
|
||||
{
|
||||
name: 'url|deploymentId',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: 'follow',
|
||||
shorthand: 'f',
|
||||
description: 'Wait for additional data [off]',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
shorthand: 'n',
|
||||
description: 'Number of log entries [100]',
|
||||
argument: 'NUMBER',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'since',
|
||||
shorthand: null,
|
||||
description: 'Only return logs after date (ISO 8601)',
|
||||
argument: 'SINCE',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'until',
|
||||
shorthand: null,
|
||||
description:
|
||||
'Only return logs before date (ISO 8601), ignored when used with --follow',
|
||||
argument: 'UNTIL',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'output',
|
||||
shorthand: 'o',
|
||||
description: `Specify the output format (short|raw) [short]`,
|
||||
argument: 'MODE',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
name: 'Print the logs for the deployment DEPLOYMENT_ID',
|
||||
value: `${getPkgName()} logs DEPLOYMENT_ID`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,60 +1,15 @@
|
||||
import chalk from 'chalk';
|
||||
import logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed';
|
||||
import { maybeURL, normalizeURL } from '../util/url';
|
||||
import printEvents, { DeploymentEvent } from '../util/events';
|
||||
import getScope from '../util/get-scope';
|
||||
import { getPkgName } from '../util/pkg-name';
|
||||
import getArgs from '../util/get-args';
|
||||
import Client from '../util/client';
|
||||
import getDeployment from '../util/get-deployment';
|
||||
import elapsed from '../../util/output/elapsed';
|
||||
import { maybeURL, normalizeURL } from '../../util/url';
|
||||
import printEvents, { DeploymentEvent } from '../../util/events';
|
||||
import getScope from '../../util/get-scope';
|
||||
import getArgs from '../../util/get-args';
|
||||
import Client from '../../util/client';
|
||||
import getDeployment from '../../util/get-deployment';
|
||||
import { help } from '../help';
|
||||
import { logsCommand } from './command';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${getPkgName()} logs`)} <url|deploymentId>
|
||||
|
||||
${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]
|
||||
--no-color No color mode [off]
|
||||
-f, --follow Wait for additional data [off]
|
||||
-n ${chalk.bold.underline(
|
||||
'NUMBER'
|
||||
)} Number of logs [100]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
--since=${chalk.bold.underline(
|
||||
'SINCE'
|
||||
)} Only return logs after date (ISO 8601)
|
||||
--until=${chalk.bold.underline(
|
||||
'UNTIL'
|
||||
)} Only return logs before date (ISO 8601), ignored for ${'`-f`'}
|
||||
-S, --scope Set a custom scope
|
||||
-o ${chalk.bold.underline('MODE')}, --output=${chalk.bold.underline(
|
||||
'MODE'
|
||||
)} Specify the output format (${Object.keys(logPrinters).join(
|
||||
'|'
|
||||
)}) [short]
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Print the logs for the deployment ${chalk.dim(
|
||||
'`deploymentId`'
|
||||
)}
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} logs deploymentId`)}
|
||||
`);
|
||||
};
|
||||
|
||||
export default async function main(client: Client) {
|
||||
export default async function logs(client: Client) {
|
||||
let head;
|
||||
let limit;
|
||||
let follow;
|
||||
@@ -76,14 +31,13 @@ export default async function main(client: Client) {
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
deploymentIdOrURL = argv._[0];
|
||||
const { output } = client;
|
||||
|
||||
if (argv['--help'] || !deploymentIdOrURL || deploymentIdOrURL === 'help') {
|
||||
help();
|
||||
output.print(help(logsCommand, { columns: client.stderr.columns }));
|
||||
return 2;
|
||||
}
|
||||
|
||||
const { output } = client;
|
||||
|
||||
try {
|
||||
since = argv['--since'] ? toTimestamp(argv['--since']) : 0;
|
||||
} catch (err) {
|
||||
@@ -38,6 +38,10 @@ export default async function rm(client: Client, args: string[]) {
|
||||
client.output.error('No such project exists');
|
||||
return 1;
|
||||
}
|
||||
if (isAPIError(err) && err.status === 403) {
|
||||
client.output.error(err.message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
const elapsed = ms(Date.now() - start);
|
||||
client.output.log(
|
||||
|
||||
@@ -73,6 +73,8 @@ export default async (client: Client): Promise<number> => {
|
||||
return 2;
|
||||
}
|
||||
|
||||
const yes = argv['--yes'] ?? false;
|
||||
|
||||
// validate the timeout
|
||||
let timeout = argv['--timeout'];
|
||||
if (timeout && ms(timeout) === undefined) {
|
||||
@@ -103,6 +105,7 @@ export default async (client: Client): Promise<number> => {
|
||||
client,
|
||||
deployId: actionOrDeployId,
|
||||
timeout,
|
||||
yes,
|
||||
});
|
||||
} catch (err) {
|
||||
if (isErrnoException(err)) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getCommandName } from '../../util/pkg-name';
|
||||
import getProjectByDeployment from '../../util/projects/get-project-by-deployment';
|
||||
import ms from 'ms';
|
||||
import promoteStatus from './status';
|
||||
import confirm from '../../util/input/confirm';
|
||||
|
||||
/**
|
||||
* Requests a promotion and waits for it complete.
|
||||
@@ -16,10 +17,12 @@ export default async function requestPromote({
|
||||
client,
|
||||
deployId,
|
||||
timeout,
|
||||
yes,
|
||||
}: {
|
||||
client: Client;
|
||||
deployId: string;
|
||||
timeout?: string;
|
||||
yes: boolean;
|
||||
}): Promise<number> {
|
||||
const { output } = client;
|
||||
|
||||
@@ -29,6 +32,16 @@ export default async function requestPromote({
|
||||
output: client.output,
|
||||
});
|
||||
|
||||
if (deployment.target !== 'production' && !yes) {
|
||||
const question =
|
||||
'This deployment does not target production, therefore promotion will not apply production environment variables. Are you sure you want to continue?';
|
||||
const answer = await confirm(client, question, false);
|
||||
if (!answer) {
|
||||
output.error('Canceled');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// request the promotion
|
||||
await client.fetch(`/v9/projects/${project.id}/promote/${deployment.id}`, {
|
||||
body: {}, // required
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import { join } from 'path';
|
||||
import Client from '../util/client';
|
||||
import type { Project, ProjectEnvTarget } from '@vercel-internals/types';
|
||||
import { emoji, prependEmoji } from '../util/emoji';
|
||||
import getArgs from '../util/get-args';
|
||||
import logo from '../util/output/logo';
|
||||
import stamp from '../util/output/stamp';
|
||||
import { getPkgName } from '../util/pkg-name';
|
||||
import { VERCEL_DIR, VERCEL_DIR_PROJECT } from '../util/projects/link';
|
||||
import { writeProjectSettings } from '../util/projects/project-settings';
|
||||
import envPull from './env/pull';
|
||||
import {
|
||||
isValidEnvTarget,
|
||||
getEnvTargetPlaceholder,
|
||||
} from '../util/env/env-target';
|
||||
import { ensureLink } from '../util/link/ensure-link';
|
||||
|
||||
const help = () => {
|
||||
return console.log(`
|
||||
${chalk.bold(`${logo} ${getPkgName()} pull`)} [project-path]
|
||||
|
||||
${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]
|
||||
--no-color No color mode [off]
|
||||
--environment [environment] Deployment environment [development]
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray(
|
||||
'–'
|
||||
)} Pull the latest Environment Variables and Project Settings from the cloud
|
||||
and stores them in \`.vercel/.env.\${target}.local\` and \`.vercel/project.json\` respectively.
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} pull`)}
|
||||
${chalk.cyan(`$ ${getPkgName()} pull ./path-to-project`)}
|
||||
|
||||
${chalk.gray('–')} Pull for a specific environment
|
||||
|
||||
${chalk.cyan(
|
||||
`$ ${getPkgName()} pull --environment=${getEnvTargetPlaceholder()}`
|
||||
)}
|
||||
|
||||
${chalk.gray(
|
||||
'If you want to download environment variables to a specific file, use `vercel env pull` instead.'
|
||||
)}
|
||||
`);
|
||||
};
|
||||
|
||||
function processArgs(client: Client) {
|
||||
return getArgs(client.argv.slice(2), {
|
||||
'--yes': Boolean,
|
||||
'--environment': String,
|
||||
'--git-branch': String,
|
||||
'--debug': Boolean,
|
||||
'-d': '--debug',
|
||||
'-y': '--yes',
|
||||
});
|
||||
}
|
||||
|
||||
function parseArgs(client: Client) {
|
||||
const argv = processArgs(client);
|
||||
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
return 2;
|
||||
}
|
||||
|
||||
return argv;
|
||||
}
|
||||
|
||||
async function pullAllEnvFiles(
|
||||
environment: ProjectEnvTarget,
|
||||
client: Client,
|
||||
project: Project,
|
||||
argv: ReturnType<typeof processArgs>,
|
||||
cwd: string
|
||||
): Promise<number> {
|
||||
const environmentFile = `.env.${environment}.local`;
|
||||
return envPull(
|
||||
client,
|
||||
project,
|
||||
environment,
|
||||
argv,
|
||||
[join('.vercel', environmentFile)],
|
||||
client.output,
|
||||
cwd,
|
||||
'vercel-cli:pull'
|
||||
);
|
||||
}
|
||||
|
||||
export function parseEnvironment(
|
||||
environment = 'development'
|
||||
): ProjectEnvTarget {
|
||||
if (!isValidEnvTarget(environment)) {
|
||||
throw new Error(
|
||||
`environment "${environment}" not supported; must be one of ${getEnvTargetPlaceholder()}`
|
||||
);
|
||||
}
|
||||
return environment;
|
||||
}
|
||||
|
||||
export default async function main(client: Client) {
|
||||
const argv = parseArgs(client);
|
||||
if (typeof argv === 'number') {
|
||||
return argv;
|
||||
}
|
||||
|
||||
const cwd = argv._[1] || process.cwd();
|
||||
const autoConfirm = Boolean(argv['--yes']);
|
||||
const environment = parseEnvironment(argv['--environment'] || undefined);
|
||||
|
||||
const link = await ensureLink('pull', client, cwd, { autoConfirm });
|
||||
if (typeof link === 'number') {
|
||||
return link;
|
||||
}
|
||||
|
||||
const { project, org } = link;
|
||||
|
||||
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
|
||||
|
||||
const pullResultCode = await pullAllEnvFiles(
|
||||
environment,
|
||||
client,
|
||||
project,
|
||||
argv,
|
||||
cwd
|
||||
);
|
||||
if (pullResultCode !== 0) {
|
||||
return pullResultCode;
|
||||
}
|
||||
|
||||
client.output.print('\n');
|
||||
client.output.log('Downloading project settings');
|
||||
await writeProjectSettings(cwd, project, org);
|
||||
|
||||
const settingsStamp = stamp();
|
||||
client.output.print(
|
||||
`${prependEmoji(
|
||||
`Downloaded project settings to ${chalk.bold(
|
||||
join(VERCEL_DIR, VERCEL_DIR_PROJECT)
|
||||
)} ${chalk.gray(settingsStamp())}`,
|
||||
emoji('success')
|
||||
)}\n`
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
53
packages/cli/src/commands/pull/command.ts
Normal file
53
packages/cli/src/commands/pull/command.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Command } from '../help';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
import { getEnvTargetPlaceholder } from '../../util/env/env-target';
|
||||
|
||||
export const pullCommand: Command = {
|
||||
name: 'pull',
|
||||
description:
|
||||
'Pull latest environment variables and project settings from Vercel. ',
|
||||
arguments: [
|
||||
{
|
||||
name: 'project-path',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: 'environment',
|
||||
description: 'Deployment environment [development]',
|
||||
argument: 'environment',
|
||||
shorthand: null,
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'yes',
|
||||
description:
|
||||
'Skip questions when setting up new project using default scope and settings',
|
||||
shorthand: 'y',
|
||||
type: 'string',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
name: 'Pull the latest Environment Variables and Project Settings from the cloud',
|
||||
value: `${getPkgName()} pull`,
|
||||
},
|
||||
{
|
||||
name: 'Pull the latest Environment Variables and Project Settings from the cloud targeting a directory',
|
||||
value: `${getPkgName()} pull ./path-to-project`,
|
||||
},
|
||||
{
|
||||
name: 'Pull for a specific environment',
|
||||
value: `${getPkgName()} pull --environment=${getEnvTargetPlaceholder()}`,
|
||||
},
|
||||
{
|
||||
name: 'If you want to download environment variables to a specific file, use `vercel env pull` instead',
|
||||
value: `${getPkgName()} env pull`,
|
||||
},
|
||||
],
|
||||
};
|
||||
131
packages/cli/src/commands/pull/index.ts
Normal file
131
packages/cli/src/commands/pull/index.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import chalk from 'chalk';
|
||||
import { join } from 'path';
|
||||
import Client from '../../util/client';
|
||||
import type {
|
||||
Project,
|
||||
ProjectEnvTarget,
|
||||
ProjectLinked,
|
||||
} from '@vercel-internals/types';
|
||||
import { emoji, prependEmoji } from '../../util/emoji';
|
||||
import getArgs from '../../util/get-args';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import { VERCEL_DIR, VERCEL_DIR_PROJECT } from '../../util/projects/link';
|
||||
import { writeProjectSettings } from '../../util/projects/project-settings';
|
||||
import envPull from '../env/pull';
|
||||
import {
|
||||
isValidEnvTarget,
|
||||
getEnvTargetPlaceholder,
|
||||
} from '../../util/env/env-target';
|
||||
import { ensureLink } from '../../util/link/ensure-link';
|
||||
import humanizePath from '../../util/humanize-path';
|
||||
|
||||
import { help } from '../help';
|
||||
import { pullCommand } from './command';
|
||||
|
||||
function processArgs(client: Client) {
|
||||
return getArgs(client.argv.slice(2), {
|
||||
'--yes': Boolean,
|
||||
'--environment': String,
|
||||
'--git-branch': String,
|
||||
'--debug': Boolean,
|
||||
'-d': '--debug',
|
||||
'-y': '--yes',
|
||||
});
|
||||
}
|
||||
|
||||
function parseArgs(client: Client) {
|
||||
const argv = processArgs(client);
|
||||
|
||||
if (argv['--help']) {
|
||||
client.output.print(help(pullCommand, { columns: client.stderr.columns }));
|
||||
return 2;
|
||||
}
|
||||
|
||||
return argv;
|
||||
}
|
||||
|
||||
async function pullAllEnvFiles(
|
||||
environment: ProjectEnvTarget,
|
||||
client: Client,
|
||||
link: ProjectLinked,
|
||||
project: Project,
|
||||
argv: ReturnType<typeof processArgs>,
|
||||
cwd: string
|
||||
): Promise<number> {
|
||||
const environmentFile = `.env.${environment}.local`;
|
||||
return envPull(
|
||||
client,
|
||||
link,
|
||||
project,
|
||||
environment,
|
||||
argv,
|
||||
[join('.vercel', environmentFile)],
|
||||
client.output,
|
||||
cwd,
|
||||
'vercel-cli:pull'
|
||||
);
|
||||
}
|
||||
|
||||
export function parseEnvironment(
|
||||
environment = 'development'
|
||||
): ProjectEnvTarget {
|
||||
if (!isValidEnvTarget(environment)) {
|
||||
throw new Error(
|
||||
`environment "${environment}" not supported; must be one of ${getEnvTargetPlaceholder()}`
|
||||
);
|
||||
}
|
||||
return environment;
|
||||
}
|
||||
|
||||
export default async function main(client: Client) {
|
||||
const argv = parseArgs(client);
|
||||
if (typeof argv === 'number') {
|
||||
return argv;
|
||||
}
|
||||
|
||||
let cwd = argv._[1] || client.cwd;
|
||||
const autoConfirm = Boolean(argv['--yes']);
|
||||
const environment = parseEnvironment(argv['--environment'] || undefined);
|
||||
|
||||
const link = await ensureLink('pull', client, cwd, { autoConfirm });
|
||||
if (typeof link === 'number') {
|
||||
return link;
|
||||
}
|
||||
|
||||
const { project, org, repoRoot } = link;
|
||||
|
||||
if (repoRoot) {
|
||||
cwd = join(repoRoot, project.rootDirectory || '');
|
||||
}
|
||||
|
||||
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
|
||||
|
||||
const pullResultCode = await pullAllEnvFiles(
|
||||
environment,
|
||||
client,
|
||||
link,
|
||||
project,
|
||||
argv,
|
||||
cwd
|
||||
);
|
||||
if (pullResultCode !== 0) {
|
||||
return pullResultCode;
|
||||
}
|
||||
|
||||
client.output.print('\n');
|
||||
client.output.log('Downloading project settings');
|
||||
const isRepoLinked = typeof repoRoot === 'string';
|
||||
await writeProjectSettings(cwd, project, org, isRepoLinked);
|
||||
|
||||
const settingsStamp = stamp();
|
||||
client.output.print(
|
||||
`${prependEmoji(
|
||||
`Downloaded project settings to ${chalk.bold(
|
||||
humanizePath(join(cwd, VERCEL_DIR, VERCEL_DIR_PROJECT))
|
||||
)} ${chalk.gray(settingsStamp())}`,
|
||||
emoji('success')
|
||||
)}\n`
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
33
packages/cli/src/commands/redeploy/command.ts
Normal file
33
packages/cli/src/commands/redeploy/command.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Command } from '../help';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
export const redeployCommand: Command = {
|
||||
name: 'redeploy',
|
||||
description: 'Rebuild and deploy a previous deployment.',
|
||||
arguments: [
|
||||
{
|
||||
name: 'deploymentId|deploymentName',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: 'no-wait',
|
||||
shorthand: null,
|
||||
description: "Don't wait for the redeploy to finish",
|
||||
type: 'boolean',
|
||||
deprecated: false,
|
||||
multi: false,
|
||||
},
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
name: 'Rebuild and deploy an existing deployment using id or url',
|
||||
value: `${getPkgName()} redeploy my-deployment.vercel.app`,
|
||||
},
|
||||
{
|
||||
name: 'Write Deployment URL to a file',
|
||||
value: `${getPkgName()} redeploy my-deployment.vercel.app > deployment-url.txt`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,66 +1,29 @@
|
||||
import chalk from 'chalk';
|
||||
import { checkDeploymentStatus } from '@vercel/client';
|
||||
import type Client from '../util/client';
|
||||
import { emoji, prependEmoji } from '../util/emoji';
|
||||
import getArgs from '../util/get-args';
|
||||
import { getCommandName, getPkgName } from '../util/pkg-name';
|
||||
import { getDeploymentByIdOrURL } from '../util/deploy/get-deployment-by-id-or-url';
|
||||
import getScope from '../util/get-scope';
|
||||
import handleError from '../util/handle-error';
|
||||
import type Client from '../../util/client';
|
||||
import { emoji, prependEmoji } from '../../util/emoji';
|
||||
import getArgs from '../../util/get-args';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { getDeploymentByIdOrURL } from '../../util/deploy/get-deployment-by-id-or-url';
|
||||
import getScope from '../../util/get-scope';
|
||||
import handleError from '../../util/handle-error';
|
||||
import { isErrnoException } from '@vercel/error-utils';
|
||||
import logo from '../util/output/logo';
|
||||
import Now from '../util';
|
||||
import { printDeploymentStatus } from '../util/deploy/print-deployment-status';
|
||||
import stamp from '../util/output/stamp';
|
||||
import ua from '../util/ua';
|
||||
import Now from '../../util';
|
||||
import { printDeploymentStatus } from '../../util/deploy/print-deployment-status';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import ua from '../../util/ua';
|
||||
import type { VercelClientOptions } from '@vercel/client';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(
|
||||
`${logo} ${getPkgName()} redeploy`
|
||||
)} [deploymentId|deploymentName]
|
||||
|
||||
Rebuild and deploy a previous deployment.
|
||||
|
||||
${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]
|
||||
--no-color No color mode [off]
|
||||
--no-wait Don't wait for the redeploy to finish
|
||||
-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('–')} Rebuild and deploy an existing deployment using id or url
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} redeploy my-deployment.vercel.app`)}
|
||||
|
||||
${chalk.gray('–')} Write Deployment URL to a file
|
||||
|
||||
${chalk.cyan(
|
||||
`$ ${getPkgName()} redeploy my-deployment.vercel.app > deployment-url.txt`
|
||||
)}
|
||||
`);
|
||||
};
|
||||
import { help } from '../help';
|
||||
import { redeployCommand } from './command';
|
||||
|
||||
/**
|
||||
* `vc redeploy` command
|
||||
* @param {Client} client
|
||||
* @returns {Promise<number>} Resolves an exit code; 0 on success
|
||||
*/
|
||||
export default async (client: Client): Promise<number> => {
|
||||
export default async function redeploy(client: Client): Promise<number> {
|
||||
let argv;
|
||||
const { output } = client;
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--no-wait': Boolean,
|
||||
@@ -73,11 +36,10 @@ export default async (client: Client): Promise<number> => {
|
||||
}
|
||||
|
||||
if (argv['--help'] || argv._[0] === 'help') {
|
||||
help();
|
||||
output.print(help(redeployCommand, { columns: client.stderr.columns }));
|
||||
return 2;
|
||||
}
|
||||
|
||||
const { output } = client;
|
||||
const deployIdOrUrl = argv._[1];
|
||||
if (!deployIdOrUrl) {
|
||||
output.error(
|
||||
@@ -108,7 +70,7 @@ export default async (client: Client): Promise<number> => {
|
||||
action: 'redeploy',
|
||||
},
|
||||
name: fromDeployment.name,
|
||||
target: fromDeployment.target || 'production',
|
||||
target: fromDeployment.target ?? undefined,
|
||||
},
|
||||
method: 'POST',
|
||||
});
|
||||
@@ -201,4 +163,4 @@ export default async (client: Client): Promise<number> => {
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
45
packages/cli/src/commands/remove/command.ts
Normal file
45
packages/cli/src/commands/remove/command.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Command } from '../help';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
export const removeCommand: Command = {
|
||||
name: 'remove',
|
||||
description: 'Remove a deployment by name or id.',
|
||||
arguments: [
|
||||
{
|
||||
name: '...deploymentId|deploymentName',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: 'yes',
|
||||
shorthand: 'y',
|
||||
type: 'boolean',
|
||||
deprecated: false,
|
||||
description: 'Skip confirmation',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
name: 'safe',
|
||||
shorthand: 's',
|
||||
type: 'boolean',
|
||||
deprecated: false,
|
||||
description: 'Skip deployments with an active alias',
|
||||
multi: false,
|
||||
},
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
name: 'Remove a deployment identified by `deploymentId`',
|
||||
value: `${getPkgName()} remove my-app`,
|
||||
},
|
||||
{
|
||||
name: 'Remove all deployments with name `my-app`',
|
||||
value: `${getPkgName()} remove deploymentId`,
|
||||
},
|
||||
{
|
||||
name: 'Remove two deployments with IDs `eyWt6zuSdeus` and `uWHoA9RQ1d1o`',
|
||||
value: `${getPkgName()} remove eyWt6zuSdeus uWHoA9RQ1d1o`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -2,74 +2,31 @@ import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import plural from 'pluralize';
|
||||
import table from 'text-table';
|
||||
import Now from '../util';
|
||||
import getAliases from '../util/alias/get-aliases';
|
||||
import logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed';
|
||||
import { normalizeURL } from '../util/url';
|
||||
import getScope from '../util/get-scope';
|
||||
import { isValidName } from '../util/is-valid-name';
|
||||
import removeProject from '../util/projects/remove-project';
|
||||
import getProjectByIdOrName from '../util/projects/get-project-by-id-or-name';
|
||||
import getDeployment from '../util/get-deployment';
|
||||
import getDeploymentsByProjectId from '../util/deploy/get-deployments-by-project-id';
|
||||
import { getPkgName, getCommandName } from '../util/pkg-name';
|
||||
import getArgs from '../util/get-args';
|
||||
import handleError from '../util/handle-error';
|
||||
import type Client from '../util/client';
|
||||
import { Output } from '../util/output';
|
||||
import Now from '../../util';
|
||||
import getAliases from '../../util/alias/get-aliases';
|
||||
import elapsed from '../../util/output/elapsed';
|
||||
import { normalizeURL } from '../../util/url';
|
||||
import getScope from '../../util/get-scope';
|
||||
import { isValidName } from '../../util/is-valid-name';
|
||||
import removeProject from '../../util/projects/remove-project';
|
||||
import getProjectByIdOrName from '../../util/projects/get-project-by-id-or-name';
|
||||
import getDeployment from '../../util/get-deployment';
|
||||
import getDeploymentsByProjectId from '../../util/deploy/get-deployments-by-project-id';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import getArgs from '../../util/get-args';
|
||||
import handleError from '../../util/handle-error';
|
||||
import type Client from '../../util/client';
|
||||
import { Output } from '../../util/output';
|
||||
import { Alias, Deployment, Project } from '@vercel-internals/types';
|
||||
import { NowError } from '../util/now-error';
|
||||
import { NowError } from '../../util/now-error';
|
||||
import { help } from '../help';
|
||||
import { removeCommand } from './command';
|
||||
|
||||
type DeploymentWithAliases = Deployment & {
|
||||
aliases: Alias[];
|
||||
};
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(
|
||||
`${logo} ${getPkgName()} remove`
|
||||
)} [...deploymentId|deploymentName]
|
||||
|
||||
${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]
|
||||
--no-color No color mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-y, --yes Skip confirmation
|
||||
-s, --safe Skip deployments with an active alias
|
||||
-S, --scope Set a custom scope
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Remove a deployment identified by ${chalk.dim(
|
||||
'`deploymentId`'
|
||||
)}
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} rm deploymentId`)}
|
||||
|
||||
${chalk.gray('–')} Remove all deployments with name ${chalk.dim('`my-app`')}
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} rm my-app`)}
|
||||
|
||||
${chalk.gray('–')} Remove two deployments with IDs ${chalk.dim(
|
||||
'`eyWt6zuSdeus`'
|
||||
)} and ${chalk.dim('`uWHoA9RQ1d1o`')}
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} rm eyWt6zuSdeus uWHoA9RQ1d1o`)}
|
||||
`);
|
||||
};
|
||||
|
||||
export default async function main(client: Client) {
|
||||
export default async function remove(client: Client) {
|
||||
let argv;
|
||||
|
||||
try {
|
||||
@@ -98,13 +55,13 @@ export default async function main(client: Client) {
|
||||
const { success, error, log } = output;
|
||||
|
||||
if (argv['--help'] || ids[0] === 'help') {
|
||||
help();
|
||||
output.print(help(removeCommand, { columns: client.stderr.columns }));
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (ids.length < 1) {
|
||||
error(`${getCommandName('rm')} expects at least one argument`);
|
||||
help();
|
||||
output.print(help(removeCommand, { columns: client.stderr.columns }));
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import logo from '../util/output/logo';
|
||||
import getScope from '../util/get-scope';
|
||||
import { getPkgName } from '../util/pkg-name';
|
||||
import getArgs from '../util/get-args';
|
||||
import Client from '../util/client';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${getPkgName()} whoami`)}
|
||||
|
||||
${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]
|
||||
--no-color No color mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Shows the username of the currently logged in user
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} whoami`)}
|
||||
`);
|
||||
};
|
||||
|
||||
export default async (client: Client): Promise<number> => {
|
||||
const { output } = client;
|
||||
const argv = getArgs(client.argv.slice(2), {});
|
||||
argv._ = argv._.slice(1);
|
||||
|
||||
if (argv['--help'] || argv._[0] === 'help') {
|
||||
help();
|
||||
return 2;
|
||||
}
|
||||
|
||||
const { contextName } = await getScope(client, { getTeam: false });
|
||||
|
||||
if (client.stdout.isTTY) {
|
||||
output.log(contextName);
|
||||
} else {
|
||||
// If stdout is not a TTY, then only print the username
|
||||
// to support piping the output to another file / exe
|
||||
client.stdout.write(`${contextName}\n`);
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
15
packages/cli/src/commands/whoami/command.ts
Normal file
15
packages/cli/src/commands/whoami/command.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Command } from '../help';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
export const whoamiCommand: Command = {
|
||||
name: 'whoami',
|
||||
description: 'Shows the username of the currently logged in user.',
|
||||
arguments: [],
|
||||
options: [],
|
||||
examples: [
|
||||
{
|
||||
name: 'Shows the username of the currently logged in user',
|
||||
value: `${getPkgName()} whoami`,
|
||||
},
|
||||
],
|
||||
};
|
||||
29
packages/cli/src/commands/whoami/index.ts
Normal file
29
packages/cli/src/commands/whoami/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { help } from '../help';
|
||||
import { whoamiCommand } from './command';
|
||||
|
||||
import getScope from '../../util/get-scope';
|
||||
import getArgs from '../../util/get-args';
|
||||
import Client from '../../util/client';
|
||||
|
||||
export default async function whoami(client: Client): Promise<number> {
|
||||
const { output } = client;
|
||||
const argv = getArgs(client.argv.slice(2), {});
|
||||
argv._ = argv._.slice(1);
|
||||
|
||||
if (argv['--help'] || argv._[0] === 'help') {
|
||||
output.print(help(whoamiCommand, { columns: client.stderr.columns }));
|
||||
return 2;
|
||||
}
|
||||
|
||||
const { contextName } = await getScope(client, { getTeam: false });
|
||||
|
||||
if (client.stdout.isTTY) {
|
||||
output.log(contextName);
|
||||
} else {
|
||||
// If stdout is not a TTY, then only print the username
|
||||
// to support piping the output to another file / exe
|
||||
client.stdout.write(`${contextName}\n`);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -12,7 +12,10 @@ export const build: BuildV2 = async ({ entrypoint, files, config }) => {
|
||||
if (
|
||||
filename.startsWith('.git/') ||
|
||||
filename === 'vercel.json' ||
|
||||
filename === 'now.json'
|
||||
filename === '.vercelignore' ||
|
||||
filename === 'now.json' ||
|
||||
filename === '.nowignore' ||
|
||||
filename.startsWith('.env')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -131,6 +131,7 @@ async function writeBuildResultV2(
|
||||
|
||||
const lambdas = new Map<Lambda, string>();
|
||||
const overrides: Record<string, PathOverride> = {};
|
||||
|
||||
for (const [path, output] of Object.entries(buildResult.output)) {
|
||||
const normalizedPath = stripDuplicateSlashes(path);
|
||||
if (isLambda(output)) {
|
||||
@@ -156,11 +157,26 @@ async function writeBuildResultV2(
|
||||
const ext = getFileExtension(fallback);
|
||||
const fallbackName = `${normalizedPath}.prerender-fallback${ext}`;
|
||||
const fallbackPath = join(outputDir, 'functions', fallbackName);
|
||||
const stream = fallback.toStream();
|
||||
await pipe(
|
||||
stream,
|
||||
fs.createWriteStream(fallbackPath, { mode: fallback.mode })
|
||||
);
|
||||
|
||||
// if file is already on the disk we can hard link
|
||||
// instead of creating a new copy
|
||||
let usedHardLink = false;
|
||||
if ('fsPath' in fallback) {
|
||||
try {
|
||||
await fs.link(fallback.fsPath, fallbackPath);
|
||||
usedHardLink = true;
|
||||
} catch (_) {
|
||||
// if link fails we continue attempting to copy
|
||||
}
|
||||
}
|
||||
|
||||
if (!usedHardLink) {
|
||||
const stream = fallback.toStream();
|
||||
await pipe(
|
||||
stream,
|
||||
fs.createWriteStream(fallbackPath, { mode: fallback.mode })
|
||||
);
|
||||
}
|
||||
fallback = new FileFsRef({
|
||||
...output.fallback,
|
||||
fsPath: basename(fallbackName),
|
||||
@@ -288,6 +304,14 @@ async function writeStaticFile(
|
||||
const dest = join(outputDir, 'static', fsPath);
|
||||
await fs.mkdirp(dirname(dest));
|
||||
|
||||
// if already on disk hard link instead of copying
|
||||
if ('fsPath' in file) {
|
||||
try {
|
||||
return await fs.link(file.fsPath, dest);
|
||||
} catch (_) {
|
||||
// if link fails we continue attempting to copy
|
||||
}
|
||||
}
|
||||
await downloadFile(file, dest);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,14 @@ import type {
|
||||
JSONObject,
|
||||
Stdio,
|
||||
ReadableTTY,
|
||||
WritableTTY,
|
||||
PaginationOptions,
|
||||
} from '@vercel-internals/types';
|
||||
import { sharedPromise } from './promise';
|
||||
import { APIError } from './errors-ts';
|
||||
import { normalizeError } from '@vercel/error-utils';
|
||||
import type { Agent } from 'http';
|
||||
import sleep from './sleep';
|
||||
import type * as tty from 'tty';
|
||||
|
||||
const isSAMLError = (v: any): v is SAMLError => {
|
||||
return v && v.saml;
|
||||
@@ -57,8 +59,8 @@ export default class Client extends EventEmitter implements Stdio {
|
||||
apiUrl: string;
|
||||
authConfig: AuthConfig;
|
||||
stdin: ReadableTTY;
|
||||
stdout: WritableTTY;
|
||||
stderr: WritableTTY;
|
||||
stdout: tty.WriteStream;
|
||||
stderr: tty.WriteStream;
|
||||
output: Output;
|
||||
config: GlobalConfig;
|
||||
agent?: Agent;
|
||||
@@ -144,7 +146,8 @@ export default class Client extends EventEmitter implements Stdio {
|
||||
if (!res.ok) {
|
||||
const error = await responseError(res);
|
||||
|
||||
if (isSAMLError(error)) {
|
||||
// we should force reauth only if error has a teamId
|
||||
if (isSAMLError(error) && error.teamId) {
|
||||
try {
|
||||
// A SAML error means the token is expired, or is not
|
||||
// designated for the requested team, so the user needs
|
||||
@@ -176,6 +179,31 @@ export default class Client extends EventEmitter implements Stdio {
|
||||
}, opts.retry);
|
||||
}
|
||||
|
||||
async *fetchPaginated<T>(
|
||||
url: string | URL,
|
||||
opts?: FetchOptions
|
||||
): AsyncGenerator<T & { pagination: PaginationOptions }> {
|
||||
const endpoint =
|
||||
typeof url === 'string' ? new URL(url, this.apiUrl) : new URL(url.href);
|
||||
if (!endpoint.searchParams.has('limit')) {
|
||||
endpoint.searchParams.set('limit', '100');
|
||||
}
|
||||
let next: number | null | undefined;
|
||||
do {
|
||||
if (next) {
|
||||
// Small sleep to avoid rate limiting
|
||||
await sleep(100);
|
||||
endpoint.searchParams.set('until', String(next));
|
||||
}
|
||||
const res = await this.fetch<T & { pagination: PaginationOptions }>(
|
||||
endpoint.href,
|
||||
opts
|
||||
);
|
||||
yield res;
|
||||
next = res.pagination?.next;
|
||||
} while (next);
|
||||
}
|
||||
|
||||
reauthenticate = sharedPromise(async function (
|
||||
this: Client,
|
||||
error: SAMLError
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import stripeFactory from 'stripe';
|
||||
import Now from '.';
|
||||
|
||||
const stripe = stripeFactory('pk_live_alyEi3lN0kSwbdevK0nrGwTw');
|
||||
|
||||
export default class CreditCards extends Now {
|
||||
async ls() {
|
||||
const res = await this._fetch('/stripe/sources/');
|
||||
const body = await res.json();
|
||||
|
||||
if (res.status !== 200) {
|
||||
const e = new Error(body.error.message);
|
||||
e.code = body.error.code;
|
||||
throw e;
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
async setDefault(source) {
|
||||
await this._fetch('/stripe/sources/', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
source,
|
||||
makeDefault: true,
|
||||
},
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async rm(source) {
|
||||
await this._fetch(`/stripe/sources/`, {
|
||||
method: 'DELETE',
|
||||
body: { source },
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async add(card) {
|
||||
if (!card.expDate) {
|
||||
throw new Error(`Please define an expiration date for your card`);
|
||||
}
|
||||
|
||||
const expDateParts = card.expDate.split(' / ');
|
||||
|
||||
card = {
|
||||
name: card.name,
|
||||
number: card.cardNumber,
|
||||
cvc: card.ccv,
|
||||
};
|
||||
|
||||
card.exp_month = expDateParts[0];
|
||||
card.exp_year = expDateParts[1];
|
||||
|
||||
try {
|
||||
const token = (await stripe.tokens.create({ card })).id;
|
||||
|
||||
const res = await this._fetch('/stripe/sources/', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
source: token,
|
||||
},
|
||||
});
|
||||
|
||||
const { source, error } = await res.json();
|
||||
|
||||
if (source && source.id) {
|
||||
return {
|
||||
last4: source.last4,
|
||||
};
|
||||
} else if (error && error.message) {
|
||||
throw new Error(error.message);
|
||||
} else {
|
||||
throw new Error('Unknown error');
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err.message || 'Unknown error');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,6 +234,10 @@ export default async function processDeployment({
|
||||
return error;
|
||||
}
|
||||
|
||||
if (error.code === 'forbidden') {
|
||||
return error;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
Builder,
|
||||
cloneEnv,
|
||||
Env,
|
||||
getNodeBinPath,
|
||||
getNodeBinPaths,
|
||||
StartDevServerResult,
|
||||
FileFsRef,
|
||||
PackageJson,
|
||||
@@ -124,6 +124,7 @@ function sortBuilders(buildA: Builder, buildB: Builder) {
|
||||
|
||||
export default class DevServer {
|
||||
public cwd: string;
|
||||
public repoRoot: string;
|
||||
public output: Output;
|
||||
public proxy: httpProxy;
|
||||
public envConfigs: EnvConfigs;
|
||||
@@ -169,6 +170,7 @@ export default class DevServer {
|
||||
|
||||
constructor(cwd: string, options: DevServerOptions) {
|
||||
this.cwd = cwd;
|
||||
this.repoRoot = options.repoRoot ?? cwd;
|
||||
this.output = options.output;
|
||||
this.envConfigs = { buildEnv: {}, runEnv: {}, allEnv: {} };
|
||||
this.envValues = options.envValues || {};
|
||||
@@ -1131,7 +1133,7 @@ export default class DevServer {
|
||||
body = redirectTemplate({ location, statusCode });
|
||||
} else {
|
||||
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
||||
body = `Redirecting to ${location} (${statusCode})\n`;
|
||||
body = `Redirecting...\n`;
|
||||
}
|
||||
res.end(body);
|
||||
}
|
||||
@@ -1412,7 +1414,7 @@ export default class DevServer {
|
||||
files,
|
||||
entrypoint: middleware.entrypoint,
|
||||
workPath,
|
||||
repoRootPath: this.cwd,
|
||||
repoRootPath: this.repoRoot,
|
||||
config: middleware.config || {},
|
||||
meta: {
|
||||
isDev: true,
|
||||
@@ -1849,7 +1851,7 @@ export default class DevServer {
|
||||
entrypoint: match.entrypoint,
|
||||
workPath,
|
||||
config: match.config || {},
|
||||
repoRootPath: this.cwd,
|
||||
repoRootPath: this.repoRoot,
|
||||
meta: {
|
||||
isDev: true,
|
||||
requestPath,
|
||||
@@ -2237,7 +2239,8 @@ export default class DevServer {
|
||||
);
|
||||
|
||||
// add the node_modules/.bin directory to the PATH
|
||||
const nodeBinPath = await getNodeBinPath({ cwd });
|
||||
const nodeBinPaths = getNodeBinPaths({ base: this.repoRoot, start: cwd });
|
||||
const nodeBinPath = nodeBinPaths.join(path.delimiter);
|
||||
env.PATH = `${nodeBinPath}${path.delimiter}${env.PATH}`;
|
||||
|
||||
// This is necesary so that the dev command in the Project
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface DevServerOptions {
|
||||
output: Output;
|
||||
projectSettings?: ProjectSettings;
|
||||
envValues?: Record<string, string>;
|
||||
repoRoot?: string;
|
||||
}
|
||||
|
||||
export interface EnvConfigs {
|
||||
|
||||
@@ -8,10 +8,7 @@ import {
|
||||
import type { Server } from 'http';
|
||||
import type Client from '../client';
|
||||
|
||||
const toHeaders = buildToHeaders({
|
||||
// @ts-expect-error - `node-fetch` Headers is missing `getAll()`
|
||||
Headers,
|
||||
});
|
||||
const toHeaders = buildToHeaders({ Headers });
|
||||
|
||||
export function createProxy(client: Client): Server {
|
||||
return createServer(async (req, res) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { basename } from 'path';
|
||||
import { VercelConfig } from '@vercel/client';
|
||||
|
||||
export interface GetProjectNameOptions {
|
||||
argv: { '--name'?: string };
|
||||
argv: { [k: string]: string | undefined };
|
||||
nowConfig?: VercelConfig;
|
||||
paths?: string[];
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user