mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-24 03:39:11 +00:00
Compare commits
73 Commits
@vercel/ru
...
add/vercel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d48f704eb9 | ||
|
|
75840896ee | ||
|
|
9657bda86d | ||
|
|
4536029d42 | ||
|
|
a35654d047 | ||
|
|
f6a01a1af3 | ||
|
|
c2bca954e0 | ||
|
|
21701c3c3d | ||
|
|
7f4723b3f8 | ||
|
|
619ca93421 | ||
|
|
cea2981512 | ||
|
|
1f30e3a4b7 | ||
|
|
20e8fdd049 | ||
|
|
3df8313d69 | ||
|
|
95c9ea92c4 | ||
|
|
7ddebb099d | ||
|
|
6498fd1aab | ||
|
|
9d7d822da3 | ||
|
|
b8110d97d1 | ||
|
|
2d9373b0f1 | ||
|
|
cc7577a648 | ||
|
|
f3f3d7df5b | ||
|
|
0bafea6d51 | ||
|
|
5abb74eddd | ||
|
|
23d7eaccab | ||
|
|
2cb008e8ed | ||
|
|
b5c3610113 | ||
|
|
f23c7fc4fc | ||
|
|
3dc1ad6437 | ||
|
|
4dc0bef62b | ||
|
|
8b3c52b9e8 | ||
|
|
fc74300ad0 | ||
|
|
28f8a38e00 | ||
|
|
c318ce9695 | ||
|
|
5b36eaacff | ||
|
|
c9f7ca23a8 | ||
|
|
57e0db0f65 | ||
|
|
d5537500d8 | ||
|
|
4b1736b2f2 | ||
|
|
9da1c6fa66 | ||
|
|
a6c320846e | ||
|
|
4973814978 | ||
|
|
f1289ff263 | ||
|
|
3ff93279cd | ||
|
|
58f9d649a8 | ||
|
|
cadc082ad1 | ||
|
|
2906d83eae | ||
|
|
675c3e2915 | ||
|
|
13a8a7dbf6 | ||
|
|
b480e632a3 | ||
|
|
bc6c364888 | ||
|
|
6cad10c899 | ||
|
|
0d78ec4666 | ||
|
|
b05d653cf1 | ||
|
|
5a38b94de2 | ||
|
|
6668d6cf21 | ||
|
|
b63f444abd | ||
|
|
9d71d4332f | ||
|
|
151db21a2f | ||
|
|
aba9c95ea2 | ||
|
|
e7e0a55b72 | ||
|
|
c2163e3e4f | ||
|
|
873f549637 | ||
|
|
ead1e411ee | ||
|
|
7c1c089a70 | ||
|
|
a7dbd9649b | ||
|
|
2e439045c9 | ||
|
|
520e0d01f4 | ||
|
|
3856623785 | ||
|
|
1c14e945f9 | ||
|
|
32357fc06f | ||
|
|
f7c57dc539 | ||
|
|
34f7c35c13 |
@@ -8,7 +8,6 @@ packages/*/test/fixtures
|
||||
|
||||
# cli
|
||||
packages/cli/@types
|
||||
packages/cli/download
|
||||
packages/cli/dist
|
||||
packages/cli/test/dev/fixtures
|
||||
packages/cli/bin
|
||||
|
||||
15
.eslintrc.cjs
Normal file
15
.eslintrc.cjs
Normal file
@@ -0,0 +1,15 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
require.resolve('@vercel/style-guide/eslint/node'),
|
||||
require.resolve('@vercel/style-guide/eslint/typescript'),
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
extends: [
|
||||
require.resolve('@vercel/style-guide/eslint/jest'),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
28
.github/CODEOWNERS
vendored
28
.github/CODEOWNERS
vendored
@@ -1,23 +1,17 @@
|
||||
# Documentation
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
* @TooTallNate @EndangeredMassa @styfle
|
||||
/.github/workflows @TooTallNate @EndangeredMassa @styfle @ijjk
|
||||
/packages/frameworks @TooTallNate @EndangeredMassa @styfle @AndyBitz
|
||||
/packages/cli/src/commands/domains @javivelasco @mglagola @anatrajkovska
|
||||
/packages/cli/src/commands/certs @javivelasco @mglagola @anatrajkovska
|
||||
/packages/cli/src/commands/env @styfle @lucleray
|
||||
/packages/client @TooTallNate @EndangeredMassa @styfle
|
||||
/packages/build-utils @TooTallNate @EndangeredMassa @styfle @AndyBitz
|
||||
/packages/middleware @gdborton @javivelasco
|
||||
/packages/node @TooTallNate @EndangeredMassa @styfle
|
||||
/packages/node-bridge @TooTallNate @EndangeredMassa @styfle @ijjk
|
||||
/packages/next @TooTallNate @ijjk
|
||||
/packages/go @TooTallNate @EndangeredMassa @styfle
|
||||
/packages/python @TooTallNate @EndangeredMassa @styfle
|
||||
/packages/ruby @TooTallNate @EndangeredMassa @styfle
|
||||
/packages/static-build @TooTallNate @EndangeredMassa @styfle @AndyBitz
|
||||
/packages/routing-utils @TooTallNate @EndangeredMassa @styfle @ijjk
|
||||
* @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood
|
||||
/.github/workflows @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||
/packages/cli/src/commands/domains @mglagola @anatrajkovska
|
||||
/packages/cli/src/commands/certs @mglagola @anatrajkovska
|
||||
/packages/cli/src/commands/env @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood
|
||||
/packages/fs-detectors @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @agadzik @chloetedder
|
||||
/packages/middleware @gdborton @vercel/edge-function
|
||||
/packages/node-bridge @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||
/packages/next @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||
/packages/routing-utils @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||
/packages/edge @vercel/edge-function
|
||||
/examples @leerob
|
||||
/examples/create-react-app @Timer
|
||||
/examples/nextjs @timneutkens @ijjk @styfle
|
||||
|
||||
1
.github/workflows/publish.yml
vendored
1
.github/workflows/publish.yml
vendored
@@ -34,6 +34,7 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
cache: 'yarn'
|
||||
- name: Install
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
run: yarn install --check-files --frozen-lockfile --network-timeout 1000000
|
||||
|
||||
22
.github/workflows/required-pr-label.yml
vendored
Normal file
22
.github/workflows/required-pr-label.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Required PR Label
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, labeled, unlabeled, synchronize]
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR Labels
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const labels = context.payload.pull_request.labels.map(l => l.name);
|
||||
if (labels.filter(l => l.startsWith('area:')).length === 0) {
|
||||
console.error('\u001b[31mMissing label: Please add at least one "area" label.');
|
||||
process.exit(1);
|
||||
}
|
||||
if (labels.filter(l => l.startsWith('semver:')).length !== 1) {
|
||||
console.error('\u001b[31mMissing label: Please add exactly one "semver" label.');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('\u001b[32mSuccess: This pull request has correct labels, thanks!');
|
||||
22
.github/workflows/test-integration-cli.yml
vendored
22
.github/workflows/test-integration-cli.yml
vendored
@@ -8,6 +8,11 @@ on:
|
||||
- '!*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TURBO_REMOTE_ONLY: 'true'
|
||||
TURBO_TEAM: 'vercel'
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: CLI
|
||||
@@ -19,25 +24,16 @@ jobs:
|
||||
node: [14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Conditionally set remote env
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
run: |
|
||||
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 100
|
||||
- run: git --version
|
||||
- run: git fetch origin main --depth=100
|
||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
||||
- run: git diff origin/main...HEAD --name-only
|
||||
cache: 'yarn'
|
||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||
- run: yarn run build
|
||||
- run: yarn test-integration-cli
|
||||
|
||||
24
.github/workflows/test-unit.yml
vendored
24
.github/workflows/test-unit.yml
vendored
@@ -8,6 +8,11 @@ on:
|
||||
- '!*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TURBO_REMOTE_ONLY: 'true'
|
||||
TURBO_TEAM: 'vercel'
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Unit
|
||||
@@ -19,25 +24,16 @@ jobs:
|
||||
node: [14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Conditionally set remote env
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
run: |
|
||||
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 100
|
||||
- run: git --version
|
||||
- run: git fetch origin main --depth=100
|
||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
||||
- run: git diff origin/main...HEAD --name-only
|
||||
fetch-depth: 2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: 'yarn'
|
||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||
- run: yarn run build
|
||||
- run: yarn run lint
|
||||
|
||||
16
.github/workflows/test.yml
vendored
16
.github/workflows/test.yml
vendored
@@ -10,6 +10,9 @@ on:
|
||||
|
||||
env:
|
||||
NODE_VERSION: '14'
|
||||
TURBO_REMOTE_ONLY: 'true'
|
||||
TURBO_TEAM: 'vercel'
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
@@ -20,8 +23,11 @@ jobs:
|
||||
dplUrl: ${{ steps.waitForTarball.outputs.url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: git --version
|
||||
- run: git fetch origin main
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
@@ -52,12 +58,6 @@ jobs:
|
||||
matrix:
|
||||
include: ${{ fromJson(needs.setup.outputs['tests']) }}
|
||||
steps:
|
||||
- name: Conditionally set remote env
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
run: |
|
||||
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version = 1
|
||||
|
||||
[merge]
|
||||
automerge_label = ["semver-major","semver-minor","semver-patch"]
|
||||
automerge_label = ["pr: automerge"]
|
||||
blacklist_title_regex = "^WIP.*"
|
||||
blacklist_labels = ["work in progress"]
|
||||
method = "squash"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import fs from 'fs/promises';
|
||||
import { join, dirname } from 'path';
|
||||
import execa from 'execa';
|
||||
import { getExampleList } from '../examples/example-list';
|
||||
import { mapOldToNew } from '../examples/map-old-to-new';
|
||||
|
||||
@@ -13,6 +12,12 @@ async function main() {
|
||||
await fs.rm(pubDir, { recursive: true, force: true });
|
||||
await fs.mkdir(pubDir);
|
||||
|
||||
await fs.cp(
|
||||
join(repoRoot, 'packages', 'frameworks', 'logos'),
|
||||
join(pubDir, 'framework-logos'),
|
||||
{ recursive: true, force: true }
|
||||
);
|
||||
|
||||
const examples = await getExampleList();
|
||||
const pathListAll = join(pubDir, 'list-all.json');
|
||||
await fs.writeFile(pathListAll, JSON.stringify(examples));
|
||||
@@ -41,10 +46,6 @@ async function main() {
|
||||
JSON.stringify([...existingExamples, ...oldExamples])
|
||||
);
|
||||
|
||||
const { stdout: sha } = await execa('git', ['rev-parse', '--short', 'HEAD'], {
|
||||
cwd: repoRoot,
|
||||
});
|
||||
|
||||
const tarballsDir = join(pubDir, 'tarballs');
|
||||
const packagesDir = join(repoRoot, 'packages');
|
||||
const packages = await fs.readdir(packagesDir);
|
||||
@@ -55,12 +56,21 @@ async function main() {
|
||||
'utf-8'
|
||||
);
|
||||
const packageJson = JSON.parse(packageJsonRaw);
|
||||
const tarballName = `${packageJson.name
|
||||
.replace('@', '')
|
||||
.replace('/', '-')}-v${packageJson.version}-${sha.trim()}.tgz`;
|
||||
const files = await fs.readdir(fullDir);
|
||||
const tarballName = files.find(f => /^vercel-.+\.tgz$/.test(f));
|
||||
if (!tarballName) {
|
||||
throw new Error(
|
||||
`Expected vercel-*.tgz in ${fullDir} but found ${JSON.stringify(
|
||||
files,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
}
|
||||
const srcTarballPath = join(fullDir, tarballName);
|
||||
const destTarballPath = join(tarballsDir, `${packageJson.name}.tgz`);
|
||||
await fs.mkdir(dirname(destTarballPath), { recursive: true });
|
||||
await fs.copyFile(join(fullDir, tarballName), destTarballPath);
|
||||
await fs.copyFile(srcTarballPath, destTarballPath);
|
||||
}
|
||||
|
||||
console.log('Completed building static frontend.');
|
||||
|
||||
@@ -16,10 +16,6 @@ const frameworks = (_frameworks as Framework[])
|
||||
defaultRoutes: undefined,
|
||||
};
|
||||
|
||||
if (framework.logo) {
|
||||
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
||||
}
|
||||
|
||||
return framework;
|
||||
});
|
||||
|
||||
|
||||
9
examples/nextjs/.gitignore
vendored
9
examples/nextjs/.gitignore
vendored
@@ -26,10 +26,11 @@ yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
|
||||
4882
examples/nextjs/package-lock.json
generated
4882
examples/nextjs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,6 @@
|
||||
{
|
||||
"name": "nextjs",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -7,12 +9,12 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "12.1.4",
|
||||
"react": "18.0.0",
|
||||
"react-dom": "18.0.0"
|
||||
"next": "12.3.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "8.12.0",
|
||||
"eslint-config-next": "12.1.4"
|
||||
"eslint": "8.23.1",
|
||||
"eslint-config-next": "12.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,3 +114,16 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.card,
|
||||
.footer {
|
||||
border-color: #222;
|
||||
}
|
||||
.code {
|
||||
background: #111;
|
||||
}
|
||||
.logo img {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,3 +14,13 @@ a {
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
body {
|
||||
color: white;
|
||||
background: black;
|
||||
}
|
||||
}
|
||||
|
||||
1703
examples/nextjs/yarn.lock
Normal file
1703
examples/nextjs/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,21 +8,9 @@ Everything you need to build a Svelte project, powered by [`create-svelte`](http
|
||||
|
||||
_Live Example: https://sveltekit-template.vercel.app_
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm init svelte
|
||||
|
||||
# create a new project in my-app
|
||||
npm init svelte my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `pnpm install`, start a development server:
|
||||
Once you've installed dependencies with `pnpm install`, start a development server:
|
||||
|
||||
```bash
|
||||
pnpm run dev
|
||||
|
||||
@@ -10,4 +10,8 @@
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "svelte-kit dev",
|
||||
"build": "svelte-kit build",
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"package": "svelte-kit package",
|
||||
"preview": "svelte-kit preview",
|
||||
"prepare": "svelte-kit sync",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./jsconfig.json",
|
||||
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
|
||||
"lint": "prettier --check --plugin-search-dir=. .",
|
||||
"format": "prettier --write --plugin-search-dir=. ."
|
||||
"lint": "prettier --check .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "1.0.0-next.50",
|
||||
"@sveltejs/kit": "1.0.0-next.347",
|
||||
"@types/cookie": "^0.4.1",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier-plugin-svelte": "^2.5.0",
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
"@sveltejs/kit": "next",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-plugin-svelte": "^2.7.0",
|
||||
"svelte": "^3.46.0",
|
||||
"svelte-check": "^2.2.6",
|
||||
"typescript": "~4.6.2"
|
||||
"svelte-check": "^2.7.1",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.8"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
1458
examples/sveltekit/pnpm-lock.yaml
generated
Normal file
1458
examples/sveltekit/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
7
examples/sveltekit/src/app.d.ts
vendored
7
examples/sveltekit/src/app.d.ts
vendored
@@ -1,7 +1,6 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
interface Locals {
|
||||
userid: string;
|
||||
@@ -9,7 +8,7 @@ declare namespace App {
|
||||
|
||||
// interface Platform {}
|
||||
|
||||
// interface Session {}
|
||||
// interface PrivateEnv {}
|
||||
|
||||
// interface Stuff {}
|
||||
// interface PublicEnv {}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,107 +1,107 @@
|
||||
<script>
|
||||
import { spring } from 'svelte/motion';
|
||||
import { spring } from 'svelte/motion';
|
||||
|
||||
let count = 0;
|
||||
let count = 0;
|
||||
|
||||
const displayed_count = spring();
|
||||
$: displayed_count.set(count);
|
||||
$: offset = modulo($displayed_count, 1);
|
||||
const displayed_count = spring();
|
||||
$: displayed_count.set(count);
|
||||
$: offset = modulo($displayed_count, 1);
|
||||
|
||||
/**
|
||||
* @param {number} n
|
||||
* @param {number} m
|
||||
*/
|
||||
function modulo(n, m) {
|
||||
// handle negative numbers
|
||||
return ((n % m) + m) % m;
|
||||
}
|
||||
/**
|
||||
* @param {number} n
|
||||
* @param {number} m
|
||||
*/
|
||||
function modulo(n, m) {
|
||||
// handle negative numbers
|
||||
return ((n % m) + m) % m;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="counter">
|
||||
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5" />
|
||||
</svg>
|
||||
</button>
|
||||
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="counter-viewport">
|
||||
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
||||
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
||||
<strong>{Math.floor($displayed_count)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter-viewport">
|
||||
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
||||
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
||||
<strong>{Math.floor($displayed_count)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
||||
</svg>
|
||||
</button>
|
||||
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.counter {
|
||||
display: flex;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.counter {
|
||||
display: flex;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.counter button {
|
||||
width: 2em;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
touch-action: manipulation;
|
||||
color: var(--text-color);
|
||||
font-size: 2rem;
|
||||
}
|
||||
.counter button {
|
||||
width: 2em;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
touch-action: manipulation;
|
||||
color: var(--text-color);
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.counter button:hover {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
.counter button:hover {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 25%;
|
||||
height: 25%;
|
||||
}
|
||||
svg {
|
||||
width: 25%;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
path {
|
||||
vector-effect: non-scaling-stroke;
|
||||
stroke-width: 2px;
|
||||
stroke: var(--text-color);
|
||||
}
|
||||
path {
|
||||
vector-effect: non-scaling-stroke;
|
||||
stroke-width: 2px;
|
||||
stroke: var(--text-color);
|
||||
}
|
||||
|
||||
.counter-viewport {
|
||||
width: 8em;
|
||||
height: 4em;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
.counter-viewport {
|
||||
width: 8em;
|
||||
height: 4em;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.counter-viewport strong {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-weight: 400;
|
||||
color: var(--accent-color);
|
||||
font-size: 4rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.counter-viewport strong {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-weight: 400;
|
||||
color: var(--accent-color);
|
||||
font-size: 4rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.counter-digits {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.counter-digits {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
top: -100%;
|
||||
user-select: none;
|
||||
}
|
||||
.hidden {
|
||||
top: -100%;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,11 +31,11 @@ import { invalidate } from '$app/navigation';
|
||||
export function enhance(form, { pending, error, result } = {}) {
|
||||
let current_token;
|
||||
|
||||
/** @param {SubmitEvent} e */
|
||||
async function handle_submit(e) {
|
||||
/** @param {SubmitEvent} event */
|
||||
async function handle_submit(event) {
|
||||
const token = (current_token = {});
|
||||
|
||||
e.preventDefault();
|
||||
event.preventDefault();
|
||||
|
||||
const data = new FormData(form);
|
||||
|
||||
@@ -63,11 +63,11 @@ export function enhance(form, { pending, error, result } = {}) {
|
||||
} else {
|
||||
console.error(await response.text());
|
||||
}
|
||||
} catch (e) {
|
||||
if (error && e instanceof Error) {
|
||||
error({ data, form, error: e, response: null });
|
||||
} catch (err) {
|
||||
if (error && err instanceof Error) {
|
||||
error({ data, form, error: err, response: null });
|
||||
} else {
|
||||
throw e;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,124 +1,124 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import logo from './svelte-logo.svg';
|
||||
import { page } from '$app/stores';
|
||||
import logo from './svelte-logo.svg';
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<div class="corner">
|
||||
<a href="https://kit.svelte.dev">
|
||||
<img src={logo} alt="SvelteKit" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="corner">
|
||||
<a href="https://kit.svelte.dev">
|
||||
<img src={logo} alt="SvelteKit" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
||||
</svg>
|
||||
<ul>
|
||||
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
||||
<li class:active={$page.url.pathname === '/about'}>
|
||||
<a sveltekit:prefetch href="/about">About</a>
|
||||
</li>
|
||||
<li class:active={$page.url.pathname === '/todos'}>
|
||||
<a sveltekit:prefetch href="/todos">Todos</a>
|
||||
</li>
|
||||
</ul>
|
||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
|
||||
</svg>
|
||||
</nav>
|
||||
<nav>
|
||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
||||
</svg>
|
||||
<ul>
|
||||
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
||||
<li class:active={$page.url.pathname === '/about'}>
|
||||
<a sveltekit:prefetch href="/about">About</a>
|
||||
</li>
|
||||
<li class:active={$page.url.pathname === '/todos'}>
|
||||
<a sveltekit:prefetch href="/todos">Todos</a>
|
||||
</li>
|
||||
</ul>
|
||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
|
||||
</svg>
|
||||
</nav>
|
||||
|
||||
<div class="corner">
|
||||
<!-- TODO put something else here? github link? -->
|
||||
</div>
|
||||
<div class="corner">
|
||||
<!-- TODO put something else here? github link? -->
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.corner {
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
}
|
||||
.corner {
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
.corner a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.corner a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.corner img {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
object-fit: contain;
|
||||
}
|
||||
.corner img {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
--background: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
--background: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 2em;
|
||||
height: 3em;
|
||||
display: block;
|
||||
}
|
||||
svg {
|
||||
width: 2em;
|
||||
height: 3em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
path {
|
||||
fill: var(--background);
|
||||
}
|
||||
path {
|
||||
fill: var(--background);
|
||||
}
|
||||
|
||||
ul {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 3em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
list-style: none;
|
||||
background: var(--background);
|
||||
background-size: contain;
|
||||
}
|
||||
ul {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 3em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
list-style: none;
|
||||
background: var(--background);
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
li {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
li.active::before {
|
||||
--size: 6px;
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(50% - var(--size));
|
||||
border: var(--size) solid transparent;
|
||||
border-top: var(--size) solid var(--accent-color);
|
||||
}
|
||||
li.active::before {
|
||||
--size: 6px;
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(50% - var(--size));
|
||||
border: var(--size) solid transparent;
|
||||
border-top: var(--size) solid var(--accent-color);
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
padding: 0 1em;
|
||||
color: var(--heading-color);
|
||||
font-weight: 700;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s linear;
|
||||
}
|
||||
nav a {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
padding: 0 1em;
|
||||
color: var(--heading-color);
|
||||
font-weight: 700;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s linear;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
a:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
58
examples/sveltekit/src/routes/+layout.svelte
Normal file
58
examples/sveltekit/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,58 @@
|
||||
<script>
|
||||
import Header from '$lib/header/Header.svelte';
|
||||
import { webVitals } from '$lib/vitals';
|
||||
import { browser } from '$app/env';
|
||||
import { page } from '$app/stores';
|
||||
import '../app.css';
|
||||
|
||||
let analyticsId = import.meta.env.VERCEL_ANALYTICS_ID;
|
||||
|
||||
$: if (browser && analyticsId) {
|
||||
webVitals({
|
||||
path: $page.url.pathname,
|
||||
params: $page.params,
|
||||
analyticsId
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
footer {
|
||||
padding: 40px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1
examples/sveltekit/src/routes/+page.js
Normal file
1
examples/sveltekit/src/routes/+page.js
Normal file
@@ -0,0 +1 @@
|
||||
export const prerender = true;
|
||||
57
examples/sveltekit/src/routes/+page.svelte
Normal file
57
examples/sveltekit/src/routes/+page.svelte
Normal file
@@ -0,0 +1,57 @@
|
||||
<script>
|
||||
import Counter from '$lib/Counter.svelte';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Home</title>
|
||||
<meta name="description" content="Svelte demo app" />
|
||||
</svelte:head>
|
||||
|
||||
<section>
|
||||
<h1>
|
||||
<span class="welcome">
|
||||
<picture>
|
||||
<source srcset="svelte-welcome.webp" type="image/webp" />
|
||||
<img src="svelte-welcome.png" alt="Welcome" />
|
||||
</picture>
|
||||
</span>
|
||||
|
||||
to your new<br />SvelteKit app
|
||||
</h1>
|
||||
|
||||
<h2>
|
||||
try editing <strong>src/routes/index.svelte</strong>
|
||||
</h2>
|
||||
|
||||
<Counter />
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
h1 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding: 0 0 calc(100% * 495 / 2048) 0;
|
||||
}
|
||||
|
||||
.welcome img {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -1,58 +0,0 @@
|
||||
<script>
|
||||
import Header from '$lib/header/Header.svelte';
|
||||
import { webVitals } from '$lib/vitals';
|
||||
import { browser } from '$app/env';
|
||||
import { page } from '$app/stores';
|
||||
import '../app.css';
|
||||
|
||||
let analyticsId = import.meta.env.VERCEL_ANALYTICS_ID;
|
||||
|
||||
$: if (browser && analyticsId) {
|
||||
webVitals({
|
||||
path: $page.url.pathname,
|
||||
params: $page.params,
|
||||
analyticsId
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
footer {
|
||||
padding: 40px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,50 +0,0 @@
|
||||
<script context="module">
|
||||
import { browser, dev } from '$app/env';
|
||||
|
||||
// we don't need any JS on this page, though we'll load
|
||||
// it in dev so that we get hot module replacement...
|
||||
export const hydrate = dev;
|
||||
|
||||
// ...but if the client-side router is already loaded
|
||||
// (i.e. we came here from elsewhere in the app), use it
|
||||
export const router = browser;
|
||||
|
||||
// since there's no dynamic data here, we can prerender
|
||||
// it so that it gets served as a static asset in prod
|
||||
export const prerender = true;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>About</title>
|
||||
<meta name="description" content="About this app" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="content">
|
||||
<h1>About this app</h1>
|
||||
|
||||
<p>
|
||||
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
|
||||
following into your command line and following the prompts:
|
||||
</p>
|
||||
|
||||
<pre>npm init svelte</pre>
|
||||
|
||||
<p>
|
||||
The page you're looking at is purely static HTML, with no client-side interactivity needed.
|
||||
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
|
||||
the devtools network panel and reloading.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
|
||||
it with JavaScript disabled!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: var(--column-width);
|
||||
margin: var(--column-margin-top) auto 0 auto;
|
||||
}
|
||||
</style>
|
||||
13
examples/sveltekit/src/routes/about/+page.js
Normal file
13
examples/sveltekit/src/routes/about/+page.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { browser, dev } from '$app/env';
|
||||
|
||||
// we don't need any JS on this page, though we'll load
|
||||
// it in dev so that we get hot module replacement...
|
||||
export const hydrate = dev;
|
||||
|
||||
// ...but if the client-side router is already loaded
|
||||
// (i.e. we came here from elsewhere in the app), use it
|
||||
export const router = browser;
|
||||
|
||||
// since there's no dynamic data here, we can prerender
|
||||
// it so that it gets served as a static asset in prod
|
||||
export const prerender = true;
|
||||
34
examples/sveltekit/src/routes/about/+page.svelte
Normal file
34
examples/sveltekit/src/routes/about/+page.svelte
Normal file
@@ -0,0 +1,34 @@
|
||||
<svelte:head>
|
||||
<title>About</title>
|
||||
<meta name="description" content="About this app" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="content">
|
||||
<h1>About this app</h1>
|
||||
|
||||
<p>
|
||||
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
|
||||
following into your command line and following the prompts:
|
||||
</p>
|
||||
|
||||
<pre>npm create svelte@latest</pre>
|
||||
|
||||
<p>
|
||||
The page you're looking at is purely static HTML, with no client-side interactivity needed.
|
||||
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
|
||||
the devtools network panel and reloading.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
|
||||
it with JavaScript disabled!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: var(--column-width);
|
||||
margin: var(--column-margin-top) auto 0 auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,60 +0,0 @@
|
||||
<script context="module">
|
||||
export const prerender = true;
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import Counter from '$lib/Counter.svelte';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Home</title>
|
||||
<meta name="description" content="Svelte demo app" />
|
||||
</svelte:head>
|
||||
|
||||
<section>
|
||||
<h1>
|
||||
<div class="welcome">
|
||||
<picture>
|
||||
<source srcset="svelte-welcome.webp" type="image/webp" />
|
||||
<img src="svelte-welcome.png" alt="Welcome" />
|
||||
</picture>
|
||||
</div>
|
||||
|
||||
to your new<br />SvelteKit app
|
||||
</h1>
|
||||
|
||||
<h2>
|
||||
try editing <strong>src/routes/index.svelte</strong>
|
||||
</h2>
|
||||
|
||||
<Counter />
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
h1 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding: 0 0 calc(100% * 495 / 2048) 0;
|
||||
}
|
||||
|
||||
.welcome img {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
62
examples/sveltekit/src/routes/todos/+page.server.js
Normal file
62
examples/sveltekit/src/routes/todos/+page.server.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { api } from './api';
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* uid: string;
|
||||
* created_at: Date;
|
||||
* text: string;
|
||||
* done: boolean;
|
||||
* pending_delete: boolean;
|
||||
* }} Todo
|
||||
*/
|
||||
|
||||
/** @type {import('./$types').PageServerLoad} */
|
||||
export const load = async ({ locals }) => {
|
||||
// locals.userid comes from src/hooks.js
|
||||
const response = await api('GET', `todos/${locals.userid}`);
|
||||
|
||||
if (response.status === 404) {
|
||||
// user hasn't created a todo list.
|
||||
// start with an empty array
|
||||
return {
|
||||
/** @type {Todo[]} */
|
||||
todos: []
|
||||
};
|
||||
}
|
||||
|
||||
if (response.status === 200) {
|
||||
return {
|
||||
/** @type {Todo[]} */
|
||||
todos: await response.json()
|
||||
};
|
||||
}
|
||||
|
||||
throw error(response.status);
|
||||
};
|
||||
|
||||
/** @type {import('./$types').Action} */
|
||||
export const POST = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('POST', `todos/${locals.userid}`, {
|
||||
text: form.get('text')
|
||||
});
|
||||
};
|
||||
|
||||
/** @type {import('./$types').Action} */
|
||||
export const PATCH = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('PATCH', `todos/${locals.userid}/${form.get('uid')}`, {
|
||||
text: form.has('text') ? form.get('text') : undefined,
|
||||
done: form.has('done') ? !!form.get('done') : undefined
|
||||
});
|
||||
};
|
||||
|
||||
/** @type {import('./$types').Action} */
|
||||
export const DELETE = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('DELETE', `todos/${locals.userid}/${form.get('uid')}`);
|
||||
};
|
||||
180
examples/sveltekit/src/routes/todos/+page.svelte
Normal file
180
examples/sveltekit/src/routes/todos/+page.svelte
Normal file
@@ -0,0 +1,180 @@
|
||||
<script>
|
||||
import { enhance } from '$lib/form';
|
||||
import { scale } from 'svelte/transition';
|
||||
import { flip } from 'svelte/animate';
|
||||
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Todos</title>
|
||||
<meta name="description" content="A todo list app" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="todos">
|
||||
<h1>Todos</h1>
|
||||
|
||||
<form
|
||||
class="new"
|
||||
action="/todos"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
result: async ({ form }) => {
|
||||
form.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
|
||||
</form>
|
||||
|
||||
{#each data.todos as todo (todo.uid)}
|
||||
<div
|
||||
class="todo"
|
||||
class:done={todo.done}
|
||||
transition:scale|local={{ start: 0.7 }}
|
||||
animate:flip={{ duration: 200 }}
|
||||
>
|
||||
<form
|
||||
action="/todos?_method=PATCH"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: ({ data }) => {
|
||||
todo.done = !!data.get('done');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
|
||||
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
|
||||
</form>
|
||||
|
||||
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
|
||||
<button class="save" aria-label="Save todo" />
|
||||
</form>
|
||||
|
||||
<form
|
||||
action="/todos?_method=DELETE"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: () => (todo.pending_delete = true)
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
|
||||
</form>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.todos {
|
||||
width: 100%;
|
||||
max-width: var(--column-width);
|
||||
margin: var(--column-margin-top) auto 0 auto;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.new {
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
input:focus-visible {
|
||||
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #ff3e00 !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.new input {
|
||||
font-size: 28px;
|
||||
width: 100%;
|
||||
padding: 0.5em 1em 0.3em 1em;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.todo {
|
||||
display: grid;
|
||||
grid-template-columns: 2rem 1fr 2rem;
|
||||
grid-gap: 0.5rem;
|
||||
align-items: center;
|
||||
margin: 0 0 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
|
||||
transform: translate(-1px, -1px);
|
||||
transition: filter 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.done {
|
||||
transform: none;
|
||||
opacity: 0.4;
|
||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
form.text {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.todo input {
|
||||
flex: 1;
|
||||
padding: 0.5em 2em 0.5em 0.8em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.todo button {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
button.toggle {
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
background-size: 1em auto;
|
||||
}
|
||||
|
||||
.done .toggle {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.delete {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.delete:hover,
|
||||
.delete:focus {
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.save {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
|
||||
}
|
||||
|
||||
.todo input:focus + .save,
|
||||
.save:focus {
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,7 @@
|
||||
/*
|
||||
This module is used by the /todos endpoint to
|
||||
make calls to api.svelte.dev, which stores todos
|
||||
for each user. The leading underscore indicates that this is
|
||||
a private module, _not_ an endpoint — visiting /todos/_api
|
||||
will net you a 404 response.
|
||||
for each user.
|
||||
|
||||
(The data on the todo app will expire periodically; no
|
||||
guarantees are made. Don't use it to organise your life.)
|
||||
@@ -1,70 +0,0 @@
|
||||
import { api } from './_api';
|
||||
|
||||
/** @type {import('./__types').RequestHandler} */
|
||||
export const get = async ({ locals }) => {
|
||||
// locals.userid comes from src/hooks.js
|
||||
const response = await api('get', `todos/${locals.userid}`);
|
||||
|
||||
if (response.status === 404) {
|
||||
// user hasn't created a todo list.
|
||||
// start with an empty array
|
||||
return {
|
||||
body: {
|
||||
todos: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (response.status === 200) {
|
||||
return {
|
||||
body: {
|
||||
todos: await response.json()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status
|
||||
};
|
||||
};
|
||||
|
||||
/** @type {import('./index').RequestHandler} */
|
||||
export const post = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('post', `todos/${locals.userid}`, {
|
||||
text: form.get('text')
|
||||
});
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
// If the user has JavaScript disabled, the URL will change to
|
||||
// include the method override unless we redirect back to /todos
|
||||
const redirect = {
|
||||
status: 303,
|
||||
headers: {
|
||||
location: '/todos'
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {import('./index').RequestHandler} */
|
||||
export const patch = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('patch', `todos/${locals.userid}/${form.get('uid')}`, {
|
||||
text: form.has('text') ? form.get('text') : undefined,
|
||||
done: form.has('done') ? !!form.get('done') : undefined
|
||||
});
|
||||
|
||||
return redirect;
|
||||
};
|
||||
|
||||
/** @type {import('./index').RequestHandler} */
|
||||
export const del = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('delete', `todos/${locals.userid}/${form.get('uid')}`);
|
||||
|
||||
return redirect;
|
||||
};
|
||||
@@ -1,190 +0,0 @@
|
||||
<script>
|
||||
import { enhance } from '$lib/form';
|
||||
import { scale } from 'svelte/transition';
|
||||
import { flip } from 'svelte/animate';
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* uid: string;
|
||||
* created_at: Date;
|
||||
* text: string;
|
||||
* done: boolean;
|
||||
* pending_delete: boolean;
|
||||
* }} Todo
|
||||
*/
|
||||
|
||||
/** @type {Todo[]} */
|
||||
export let todos;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Todos</title>
|
||||
<meta name="description" content="A todo list app" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="todos">
|
||||
<h1>Todos</h1>
|
||||
|
||||
<form
|
||||
class="new"
|
||||
action="/todos"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
result: async ({ form }) => {
|
||||
form.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
|
||||
</form>
|
||||
|
||||
{#each todos as todo (todo.uid)}
|
||||
<div
|
||||
class="todo"
|
||||
class:done={todo.done}
|
||||
transition:scale|local={{ start: 0.7 }}
|
||||
animate:flip={{ duration: 200 }}
|
||||
>
|
||||
<form
|
||||
action="/todos?_method=PATCH"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: ({ data }) => {
|
||||
todo.done = !!data.get('done');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
|
||||
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
|
||||
</form>
|
||||
|
||||
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
|
||||
<button class="save" aria-label="Save todo" />
|
||||
</form>
|
||||
|
||||
<form
|
||||
action="/todos?_method=DELETE"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: () => (todo.pending_delete = true)
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
|
||||
</form>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.todos {
|
||||
width: 100%;
|
||||
max-width: var(--column-width);
|
||||
margin: var(--column-margin-top) auto 0 auto;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.new {
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
input:focus-visible {
|
||||
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #ff3e00 !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.new input {
|
||||
font-size: 28px;
|
||||
width: 100%;
|
||||
padding: 0.5em 1em 0.3em 1em;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.todo {
|
||||
display: grid;
|
||||
grid-template-columns: 2rem 1fr 2rem;
|
||||
grid-gap: 0.5rem;
|
||||
align-items: center;
|
||||
margin: 0 0 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
|
||||
transform: translate(-1px, -1px);
|
||||
transition: filter 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.done {
|
||||
transform: none;
|
||||
opacity: 0.4;
|
||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
form.text {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.todo input {
|
||||
flex: 1;
|
||||
padding: 0.5em 2em 0.5em 0.8em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.todo button {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
button.toggle {
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
background-size: 1em auto;
|
||||
}
|
||||
|
||||
.done .toggle {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.delete {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.delete:hover,
|
||||
.delete:focus {
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.save {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
|
||||
}
|
||||
|
||||
.todo input:focus + .save,
|
||||
.save:focus {
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -8,11 +8,6 @@ const config = {
|
||||
// Override http methods in the Todo forms
|
||||
methodOverride: {
|
||||
allowed: ['PATCH', 'DELETE']
|
||||
},
|
||||
vite: {
|
||||
define: {
|
||||
'import.meta.env.VERCEL_ANALYTICS_ID': JSON.stringify(process.env.VERCEL_ANALYTICS_ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
11
examples/sveltekit/vite.config.js
Normal file
11
examples/sveltekit/vite.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
|
||||
/** @type {import('vite').UserConfig} */
|
||||
const config = {
|
||||
plugins: [sveltekit()],
|
||||
define: {
|
||||
'import.meta.env.VERCEL_ANALYTICS_ID': JSON.stringify(process.env.VERCEL_ANALYTICS_ID)
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
File diff suppressed because it is too large
Load Diff
96
package.json
96
package.json
@@ -8,20 +8,20 @@
|
||||
"packages/*"
|
||||
],
|
||||
"nohoist": [
|
||||
"**/@types/**"
|
||||
"**/@types/**",
|
||||
"**/typedoc",
|
||||
"**/typedoc-plugin-markdown",
|
||||
"**/typedoc-plugin-mdn-links"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"lerna": "3.16.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "5.21.0",
|
||||
"@typescript-eslint/parser": "5.21.0",
|
||||
"@vercel/style-guide": "3.0.0",
|
||||
"async-retry": "1.2.3",
|
||||
"buffer-replace": "1.0.0",
|
||||
"eslint": "8.14.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-jest": "26.1.5",
|
||||
"husky": "7.0.4",
|
||||
"jest": "28.0.2",
|
||||
"json5": "2.1.1",
|
||||
@@ -31,7 +31,7 @@
|
||||
"prettier": "2.6.2",
|
||||
"ts-eager": "2.0.2",
|
||||
"ts-jest": "28.0.5",
|
||||
"turbo": "1.3.2-canary.1"
|
||||
"turbo": "1.4.7"
|
||||
},
|
||||
"scripts": {
|
||||
"lerna": "lerna",
|
||||
@@ -63,87 +63,5 @@
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"arrowParens": "avoid"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"modules": true
|
||||
},
|
||||
"plugins": [
|
||||
"jest"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"env": {
|
||||
"node": true,
|
||||
"jest": true,
|
||||
"es6": true
|
||||
},
|
||||
"rules": {
|
||||
"no-restricted-syntax": [
|
||||
"warn",
|
||||
"WithStatement",
|
||||
{
|
||||
"message": "substr() is deprecated, use slice() or substring() instead",
|
||||
"selector": "MemberExpression > Identifier[name='substr']"
|
||||
}
|
||||
],
|
||||
"no-dupe-keys": 2,
|
||||
"require-atomic-updates": 0,
|
||||
"@typescript-eslint/ban-ts-comment": 0,
|
||||
"@typescript-eslint/camelcase": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||
"@typescript-eslint/no-empty-function": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/no-unused-vars": 2,
|
||||
"@typescript-eslint/no-use-before-define": 0,
|
||||
"@typescript-eslint/no-var-requires": 0,
|
||||
"jest/no-disabled-tests": 2,
|
||||
"jest/no-focused-tests": 2
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"packages/cli/**/*"
|
||||
],
|
||||
"rules": {
|
||||
"lines-between-class-members": 0,
|
||||
"no-async-promise-executor": 0,
|
||||
"no-control-regex": 0,
|
||||
"no-empty": 0,
|
||||
"prefer-const": 0,
|
||||
"prefer-destructuring": 0,
|
||||
"@typescript-eslint/ban-types": 0,
|
||||
"@typescript-eslint/consistent-type-assertions": 0,
|
||||
"@typescript-eslint/member-delimiter-style": 0,
|
||||
"@typescript-eslint/no-empty-function": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/no-inferrable-types": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"packages/client/**/*"
|
||||
],
|
||||
"rules": {
|
||||
"prefer-const": 0,
|
||||
"require-atomic-updates": 0,
|
||||
"@typescript-eslint/ban-ts-ignore": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"prettier": "@vercel/style-guide/prettier"
|
||||
}
|
||||
|
||||
20
packages/build-utils/.eslintrc.cjs
Normal file
20
packages/build-utils/.eslintrc.cjs
Normal file
@@ -0,0 +1,20 @@
|
||||
const { resolve } = require('path');
|
||||
|
||||
const baseConfig = require.resolve('../../.eslintrc.cjs');
|
||||
|
||||
const project = resolve(__dirname, 'tsconfig.json');
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [baseConfig],
|
||||
parserOptions: {
|
||||
project,
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
const { join } = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const execa = require('execa');
|
||||
const { join } = require('path');
|
||||
|
||||
async function main() {
|
||||
const outDir = join(__dirname, 'dist');
|
||||
@@ -24,7 +24,8 @@ async function main() {
|
||||
await fs.remove(mainDir);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
main().catch((err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "5.3.2",
|
||||
"version": "5.4.3",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
29
packages/build-utils/src/clone-env.ts
Normal file
29
packages/build-utils/src/clone-env.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { Env } from './types';
|
||||
|
||||
const { hasOwnProperty } = Object.prototype;
|
||||
|
||||
/**
|
||||
* Clones zero or more objects into a single new object while ensuring that the
|
||||
* `PATH` environment variable is defined when the `PATH` or `Path` environment
|
||||
* variables are defined.
|
||||
*
|
||||
* @param {Object} [...envs] Objects and/or `process.env` to clone and merge
|
||||
* @returns {Object} The new object
|
||||
*/
|
||||
export function cloneEnv(...envs: (Env | undefined)[]): Env {
|
||||
return envs.reduce((obj: Env, env) => {
|
||||
if (!env) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
// the system path is called `Path` on Windows and Node.js will
|
||||
// automatically return the system path when accessing `PATH`,
|
||||
// however we lose this proxied value when we destructure and
|
||||
// thus we must explicitly copy it
|
||||
if (hasOwnProperty.call(env, 'PATH') || hasOwnProperty.call(env, 'Path')) {
|
||||
obj.PATH = env.PATH;
|
||||
}
|
||||
|
||||
return Object.assign(obj, env);
|
||||
}, {});
|
||||
}
|
||||
@@ -3,5 +3,7 @@ import { getPlatformEnv } from './get-platform-env';
|
||||
export default function debug(message: string, ...additional: any[]) {
|
||||
if (getPlatformEnv('BUILDER_DEBUG')) {
|
||||
console.log(message, ...additional);
|
||||
} else if (process.env.VERCEL_DEBUG_PREFIX) {
|
||||
console.log(`${process.env.VERCEL_DEBUG_PREFIX}${message}`, ...additional);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export function getPrettyError(obj: {
|
||||
|
||||
return new NowBuildError({
|
||||
code: 'DEV_VALIDATE_CONFIG',
|
||||
message: message,
|
||||
message,
|
||||
link: prop ? `${docsUrl}#project/${prop.toLowerCase()}` : docsUrl,
|
||||
action: 'View Documentation',
|
||||
});
|
||||
@@ -91,7 +91,7 @@ function getTopLevelPropertyName(dataPath?: string): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
const mapTypoToSuggestion: { [key: string]: { [key: string]: string } } = {
|
||||
const mapTypoToSuggestion: Record<string, Record<string, string>> = {
|
||||
'': {
|
||||
builder: 'builds',
|
||||
'build.env': '{ "build": { "env": {"name": "value"} } }',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import assert from 'assert';
|
||||
import intoStream from 'into-stream';
|
||||
import { FileBase } from './types';
|
||||
import type { FileBase } from './types';
|
||||
|
||||
interface FileBlobOptions {
|
||||
mode?: number;
|
||||
@@ -39,8 +39,8 @@ export default class FileBlob implements FileBase {
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
stream.on('data', chunk => chunks.push(Buffer.from(chunk)));
|
||||
stream.on('error', error => reject(error));
|
||||
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
||||
stream.on('error', (error) => reject(error));
|
||||
stream.on('end', () => resolve());
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import assert from 'assert';
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import multiStream from 'multistream';
|
||||
import path from 'path';
|
||||
import Sema from 'async-sema';
|
||||
import { FileBase } from './types';
|
||||
import type { FileBase } from './types';
|
||||
|
||||
const semaToPreventEMFILE = new Sema(20);
|
||||
|
||||
@@ -84,16 +84,15 @@ class FileFsRef implements FileBase {
|
||||
toStream(): NodeJS.ReadableStream {
|
||||
let flag = false;
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return multiStream(cb => {
|
||||
return multiStream((cb) => {
|
||||
if (flag) return cb(null, null);
|
||||
flag = true;
|
||||
|
||||
this.toStreamAsync()
|
||||
.then(stream => {
|
||||
.then((stream) => {
|
||||
cb(null, stream);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
cb(error, null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import fetch from 'node-fetch';
|
||||
import multiStream from 'multistream';
|
||||
import retry from 'async-retry';
|
||||
import Sema from 'async-sema';
|
||||
import { FileBase } from './types';
|
||||
import type { FileBase } from './types';
|
||||
|
||||
interface FileRefOptions {
|
||||
mode?: number;
|
||||
@@ -73,14 +73,14 @@ export default class FileRef implements FileBase {
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) {
|
||||
const error = new BailableError(
|
||||
`download: ${resp.status} ${resp.statusText} for ${url}`
|
||||
`download: ${resp.status} ${resp.statusText} for ${url}`,
|
||||
);
|
||||
if (resp.status === 403) error.bail = true;
|
||||
throw error;
|
||||
}
|
||||
return resp.body;
|
||||
},
|
||||
{ factor: 1, retries: 3 }
|
||||
{ factor: 1, retries: 3 },
|
||||
);
|
||||
} finally {
|
||||
// console.timeEnd(`downloading ${url}`);
|
||||
@@ -91,16 +91,15 @@ export default class FileRef implements FileBase {
|
||||
toStream(): NodeJS.ReadableStream {
|
||||
let flag = false;
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return multiStream(cb => {
|
||||
return multiStream((cb) => {
|
||||
if (flag) return cb(null, null);
|
||||
flag = true;
|
||||
|
||||
this.toStreamAsync()
|
||||
.then(stream => {
|
||||
.then((stream) => {
|
||||
cb(null, stream);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
cb(error, null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import path from 'path';
|
||||
import { remove, mkdirp, readlink, symlink } from 'fs-extra';
|
||||
import debug from '../debug';
|
||||
import FileFsRef from '../file-fs-ref';
|
||||
import { File, Files, Meta } from '../types';
|
||||
import { remove, mkdirp, readlink, symlink } from 'fs-extra';
|
||||
import streamToBuffer from './stream-to-buffer';
|
||||
import type { File, Files, Meta } from '../types';
|
||||
|
||||
export interface DownloadedFiles {
|
||||
[filePath: string]: FileFsRef;
|
||||
}
|
||||
export type DownloadedFiles = Record<string, FileFsRef>;
|
||||
|
||||
const S_IFMT = 61440; /* 0170000 type of file */
|
||||
const S_IFLNK = 40960; /* 0120000 symbolic link */
|
||||
@@ -18,7 +16,7 @@ export function isSymbolicLink(mode: number): boolean {
|
||||
|
||||
async function prepareSymlinkTarget(
|
||||
file: File,
|
||||
fsPath: string
|
||||
fsPath: string,
|
||||
): Promise<string> {
|
||||
const mkdirPromise = mkdirp(path.dirname(fsPath));
|
||||
if (file.type === 'FileFsRef') {
|
||||
@@ -36,13 +34,13 @@ async function prepareSymlinkTarget(
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`file.type "${(file as any).type}" not supported for symlink`
|
||||
`file.type "${(file as any).type}" not supported for symlink`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function downloadFile(
|
||||
file: File,
|
||||
fsPath: string
|
||||
fsPath: string,
|
||||
): Promise<FileFsRef> {
|
||||
const { mode } = file;
|
||||
|
||||
@@ -68,7 +66,7 @@ async function removeFile(basePath: string, fileMatched: string) {
|
||||
export default async function download(
|
||||
files: Files,
|
||||
basePath: string,
|
||||
meta?: Meta
|
||||
meta?: Meta,
|
||||
): Promise<DownloadedFiles> {
|
||||
const {
|
||||
isDev = false,
|
||||
@@ -90,7 +88,7 @@ export default async function download(
|
||||
const filenames = Object.keys(files);
|
||||
|
||||
await Promise.all(
|
||||
filenames.map(async name => {
|
||||
filenames.map(async (name) => {
|
||||
// If the file does not exist anymore, remove it.
|
||||
if (Array.isArray(filesRemoved) && filesRemoved.includes(name)) {
|
||||
await removeFile(basePath, name);
|
||||
@@ -112,17 +110,19 @@ export default async function download(
|
||||
const parent = files[dir];
|
||||
if (parent && isSymbolicLink(parent.mode)) {
|
||||
console.warn(
|
||||
`Warning: file "${name}" is within a symlinked directory "${dir}" and will be ignored`
|
||||
`Warning: file "${name}" is within a symlinked directory "${dir}" and will be ignored`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const file = files[name];
|
||||
if (!file) return;
|
||||
|
||||
const fsPath = path.join(basePath, name);
|
||||
|
||||
files2[name] = await downloadFile(file, fsPath);
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const duration = Date.now() - start;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import path from 'path';
|
||||
import assert from 'assert';
|
||||
import vanillaGlob_ from 'glob';
|
||||
import { promisify } from 'util';
|
||||
import { lstat, Stats } from 'fs-extra';
|
||||
import { normalizePath } from './normalize-path';
|
||||
import vanillaGlob_ from 'glob';
|
||||
import { lstat } from 'fs-extra';
|
||||
import FileFsRef from '../file-fs-ref';
|
||||
import { normalizePath } from './normalize-path';
|
||||
import type { Stats } from 'fs-extra';
|
||||
|
||||
export type GlobOptions = vanillaGlob_.IOptions;
|
||||
|
||||
@@ -13,7 +14,7 @@ const vanillaGlob = promisify(vanillaGlob_);
|
||||
export default async function glob(
|
||||
pattern: string,
|
||||
opts: GlobOptions | string,
|
||||
mountpoint?: string
|
||||
mountpoint?: string,
|
||||
): Promise<Record<string, FileFsRef>> {
|
||||
let options: GlobOptions;
|
||||
if (typeof opts === 'string') {
|
||||
@@ -24,7 +25,7 @@ export default async function glob(
|
||||
|
||||
if (!options.cwd) {
|
||||
throw new Error(
|
||||
'Second argument (basePath) must be specified for names of resulting files'
|
||||
'Second argument (basePath) must be specified for names of resulting files',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,9 +48,9 @@ export default async function glob(
|
||||
let stat = statCache[fsPath];
|
||||
assert(
|
||||
stat,
|
||||
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`
|
||||
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`,
|
||||
);
|
||||
const isSymlink = options.symlinks![fsPath];
|
||||
const isSymlink = options.symlinks[fsPath];
|
||||
if (isSymlink || stat.isFile()) {
|
||||
if (isSymlink) {
|
||||
stat = await lstat(fsPath);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { intersects, validRange } from 'semver';
|
||||
import { NodeVersion } from '../types';
|
||||
import { NowBuildError } from '../errors';
|
||||
import debug from '../debug';
|
||||
import type { NodeVersion } from '../types';
|
||||
|
||||
const allOptions = [
|
||||
{ major: 16, range: '16.x', runtime: 'nodejs16.x' },
|
||||
@@ -43,14 +43,14 @@ export function getDiscontinuedNodeVersions(): NodeVersion[] {
|
||||
|
||||
export async function getSupportedNodeVersion(
|
||||
engineRange: string | undefined,
|
||||
isAuto = false
|
||||
isAuto = false,
|
||||
): Promise<NodeVersion> {
|
||||
let selection: NodeVersion = getLatestNodeVersion();
|
||||
|
||||
if (engineRange) {
|
||||
const found =
|
||||
validRange(engineRange) &&
|
||||
allOptions.some(o => {
|
||||
allOptions.some((o) => {
|
||||
// the array is already in order so return the first
|
||||
// match which will be the newest version of node
|
||||
selection = o;
|
||||
@@ -61,7 +61,7 @@ export async function getSupportedNodeVersion(
|
||||
code: 'BUILD_UTILS_NODE_VERSION_INVALID',
|
||||
link: 'http://vercel.link/node-version',
|
||||
message: `Found invalid Node.js Version: "${engineRange}". ${getHint(
|
||||
isAuto
|
||||
isAuto,
|
||||
)}`,
|
||||
});
|
||||
}
|
||||
@@ -84,8 +84,8 @@ export async function getSupportedNodeVersion(
|
||||
`Error: Node.js version ${
|
||||
selection.range
|
||||
} has reached End-of-Life. Deployments created on or after ${d} will fail to build. ${getHint(
|
||||
isAuto
|
||||
)}`
|
||||
isAuto,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Files } from '../types';
|
||||
import type { Files } from '../types';
|
||||
|
||||
type Delegate = (name: string) => string;
|
||||
|
||||
export default function rename(files: Files, delegate: Delegate): Files {
|
||||
@@ -7,6 +8,6 @@ export default function rename(files: Files, delegate: Delegate): Files {
|
||||
...newFiles,
|
||||
[delegate(name)]: files[name],
|
||||
}),
|
||||
{}
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import assert from 'assert';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { deprecate } from 'util';
|
||||
import fs from 'fs-extra';
|
||||
import Sema from 'async-sema';
|
||||
import spawn from 'cross-spawn';
|
||||
import { coerce, intersects, validRange } from 'semver';
|
||||
import { SpawnOptions } from 'child_process';
|
||||
import { deprecate } from 'util';
|
||||
import debug from '../debug';
|
||||
import { NowBuildError } from '../errors';
|
||||
import { Meta, PackageJson, NodeVersion, Config } from '../types';
|
||||
import { cloneEnv } from '../clone-env';
|
||||
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
|
||||
import { readConfigFile } from './read-config-file';
|
||||
import type { Meta, PackageJson, NodeVersion, Config } from '../types';
|
||||
import type { SpawnOptions } from 'child_process';
|
||||
|
||||
// Only allow one `runNpmInstall()` invocation to run concurrently
|
||||
const runNpmInstallSema = new Sema(1);
|
||||
@@ -23,8 +24,7 @@ export interface ScanParentDirsResult {
|
||||
*/
|
||||
cliType: CliType;
|
||||
/**
|
||||
* The file path of found `package.json` file, or `undefined` if none was
|
||||
* found.
|
||||
* The file path of found `package.json` file, or `undefined` if not found.
|
||||
*/
|
||||
packageJsonPath?: string;
|
||||
/**
|
||||
@@ -33,8 +33,13 @@ export interface ScanParentDirsResult {
|
||||
*/
|
||||
packageJson?: PackageJson;
|
||||
/**
|
||||
* The `lockfileVersion` number from the `package-lock.json` file,
|
||||
* when present.
|
||||
* The file path of the lockfile (`yarn.lock`, `package-lock.json`, or `pnpm-lock.yaml`)
|
||||
* or `undefined` if not found.
|
||||
*/
|
||||
lockfilePath?: string;
|
||||
/**
|
||||
* The `lockfileVersion` number from lockfile (`package-lock.json` or `pnpm-lock.yaml`),
|
||||
* or `undefined` if not found.
|
||||
*/
|
||||
lockfileVersion?: number;
|
||||
}
|
||||
@@ -73,7 +78,7 @@ export interface SpawnOptionsExtended extends SpawnOptions {
|
||||
export function spawnAsync(
|
||||
command: string,
|
||||
args: string[],
|
||||
opts: SpawnOptionsExtended = {}
|
||||
opts: SpawnOptionsExtended = {},
|
||||
) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const stderrLogs: Buffer[] = [];
|
||||
@@ -81,7 +86,7 @@ export function spawnAsync(
|
||||
const child = spawn(command, args, opts);
|
||||
|
||||
if (opts.stdio === 'pipe' && child.stderr) {
|
||||
child.stderr.on('data', data => stderrLogs.push(data));
|
||||
child.stderr.on('data', (data) => stderrLogs.push(data));
|
||||
}
|
||||
|
||||
child.on('error', reject);
|
||||
@@ -99,8 +104,8 @@ export function spawnAsync(
|
||||
message:
|
||||
opts.stdio === 'inherit'
|
||||
? `${cmd} exited with ${code || signal}`
|
||||
: stderrLogs.map(line => line.toString()).join(''),
|
||||
})
|
||||
: stderrLogs.map((line) => line.toString()).join(''),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -109,7 +114,7 @@ export function spawnAsync(
|
||||
export function execAsync(
|
||||
command: string,
|
||||
args: string[],
|
||||
opts: SpawnOptionsExtended = {}
|
||||
opts: SpawnOptionsExtended = {},
|
||||
) {
|
||||
return new Promise<{ stdout: string; stderr: string; code: number }>(
|
||||
(resolve, reject) => {
|
||||
@@ -120,11 +125,11 @@ export function execAsync(
|
||||
|
||||
const child = spawn(command, args, opts);
|
||||
|
||||
child.stderr!.on('data', data => {
|
||||
child.stderr!.on('data', (data) => {
|
||||
stderrList.push(data);
|
||||
});
|
||||
|
||||
child.stdout!.on('data', data => {
|
||||
child.stdout!.on('data', (data) => {
|
||||
stdoutList.push(data);
|
||||
});
|
||||
|
||||
@@ -146,10 +151,10 @@ export function execAsync(
|
||||
new NowBuildError({
|
||||
code: `BUILD_UTILS_EXEC_${code || signal}`,
|
||||
message: `${cmd} exited with ${code || signal}`,
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -178,25 +183,9 @@ export async function getNodeBinPath({
|
||||
}: {
|
||||
cwd: string;
|
||||
}): Promise<string> {
|
||||
const { code, stdout, stderr } = await execAsync('npm', ['bin'], {
|
||||
cwd,
|
||||
prettyCommand: 'npm bin',
|
||||
|
||||
// in some rare cases, we saw `npm bin` exit with a non-0 code, but still
|
||||
// output the right bin path, so we ignore the exit code
|
||||
ignoreNon0Exit: true,
|
||||
});
|
||||
|
||||
const nodeBinPath = stdout.trim();
|
||||
|
||||
if (path.isAbsolute(nodeBinPath)) {
|
||||
return nodeBinPath;
|
||||
}
|
||||
|
||||
throw new NowBuildError({
|
||||
code: `BUILD_UTILS_GET_NODE_BIN_PATH`,
|
||||
message: `Running \`npm bin\` failed to return a valid bin path (code=${code}, stdout=${stdout}, stderr=${stderr})`,
|
||||
});
|
||||
const { lockfilePath } = await scanParentDirs(cwd);
|
||||
const dir = path.dirname(lockfilePath || cwd);
|
||||
return path.join(dir, 'node_modules', '.bin');
|
||||
}
|
||||
|
||||
async function chmodPlusX(fsPath: string) {
|
||||
@@ -210,7 +199,7 @@ async function chmodPlusX(fsPath: string) {
|
||||
export async function runShellScript(
|
||||
fsPath: string,
|
||||
args: string[] = [],
|
||||
spawnOpts?: SpawnOptions
|
||||
spawnOpts?: SpawnOptions,
|
||||
) {
|
||||
assert(path.isAbsolute(fsPath));
|
||||
const destPath = path.dirname(fsPath);
|
||||
@@ -226,17 +215,17 @@ export async function runShellScript(
|
||||
|
||||
export function getSpawnOptions(
|
||||
meta: Meta,
|
||||
nodeVersion: NodeVersion
|
||||
nodeVersion: NodeVersion,
|
||||
): SpawnOptions {
|
||||
const opts = {
|
||||
env: { ...process.env },
|
||||
env: cloneEnv(process.env),
|
||||
};
|
||||
|
||||
if (!meta.isDev) {
|
||||
let found = false;
|
||||
const oldPath = opts.env.PATH || process.env.PATH || '';
|
||||
|
||||
const pathSegments = oldPath.split(path.delimiter).map(segment => {
|
||||
const pathSegments = oldPath.split(path.delimiter).map((segment) => {
|
||||
if (/^\/node[0-9]+\/bin/.test(segment)) {
|
||||
found = true;
|
||||
return `/node${nodeVersion.major}/bin`;
|
||||
@@ -259,7 +248,7 @@ export async function getNodeVersion(
|
||||
destPath: string,
|
||||
_nodeVersion?: string,
|
||||
config: Config = {},
|
||||
meta: Meta = {}
|
||||
meta: Meta = {},
|
||||
): Promise<NodeVersion> {
|
||||
const latest = getLatestNodeVersion();
|
||||
if (meta && meta.isDev) {
|
||||
@@ -278,11 +267,11 @@ export async function getNodeVersion(
|
||||
!meta.isDev
|
||||
) {
|
||||
console.warn(
|
||||
`Warning: Due to "engines": { "node": "${node}" } in your \`package.json\` file, the Node.js Version defined in your Project Settings ("${nodeVersion}") will not apply. Learn More: http://vercel.link/node-version`
|
||||
`Warning: Due to "engines": { "node": "${node}" } in your \`package.json\` file, the Node.js Version defined in your Project Settings ("${nodeVersion}") will not apply. Learn More: http://vercel.link/node-version`,
|
||||
);
|
||||
} else if (coerce(node)?.raw === node && !meta.isDev) {
|
||||
console.warn(
|
||||
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` with major.minor.patch, but only major Node.js Version can be selected. Learn More: http://vercel.link/node-version`
|
||||
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` with major.minor.patch, but only major Node.js Version can be selected. Learn More: http://vercel.link/node-version`,
|
||||
);
|
||||
} else if (
|
||||
validRange(node) &&
|
||||
@@ -290,7 +279,7 @@ export async function getNodeVersion(
|
||||
!meta.isDev
|
||||
) {
|
||||
console.warn(
|
||||
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version`
|
||||
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version`,
|
||||
);
|
||||
}
|
||||
nodeVersion = node;
|
||||
@@ -301,7 +290,7 @@ export async function getNodeVersion(
|
||||
|
||||
export async function scanParentDirs(
|
||||
destPath: string,
|
||||
readPackageJson = false
|
||||
readPackageJson = false,
|
||||
): Promise<ScanParentDirsResult> {
|
||||
assert(path.isAbsolute(destPath));
|
||||
|
||||
@@ -319,6 +308,7 @@ export async function scanParentDirs(
|
||||
start: destPath,
|
||||
filenames: ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml'],
|
||||
});
|
||||
let lockfilePath: string | undefined;
|
||||
let lockfileVersion: number | undefined;
|
||||
let cliType: CliType = 'yarn';
|
||||
|
||||
@@ -335,17 +325,25 @@ export async function scanParentDirs(
|
||||
// Priority order is Yarn > pnpm > npm
|
||||
if (hasYarnLock) {
|
||||
cliType = 'yarn';
|
||||
lockfilePath = yarnLockPath;
|
||||
} else if (pnpmLockYaml) {
|
||||
cliType = 'pnpm';
|
||||
// just ensure that it is read as a number and not a string
|
||||
lockfilePath = pnpmLockPath;
|
||||
lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
|
||||
} else if (packageLockJson) {
|
||||
cliType = 'npm';
|
||||
lockfilePath = npmLockPath;
|
||||
lockfileVersion = packageLockJson.lockfileVersion;
|
||||
}
|
||||
|
||||
const packageJsonPath = pkgJsonPath || undefined;
|
||||
return { cliType, packageJson, lockfileVersion, packageJsonPath };
|
||||
return {
|
||||
cliType,
|
||||
packageJson,
|
||||
lockfilePath,
|
||||
lockfileVersion,
|
||||
packageJsonPath,
|
||||
};
|
||||
}
|
||||
|
||||
export async function walkParentDirs({
|
||||
@@ -387,11 +385,11 @@ async function walkParentDirsMulti({
|
||||
}): Promise<(string | undefined)[]> {
|
||||
let parent = '';
|
||||
for (let current = start; base.length <= current.length; current = parent) {
|
||||
const fullPaths = filenames.map(f => path.join(current, f));
|
||||
const fullPaths = filenames.map((f) => path.join(current, f));
|
||||
const existResults = await Promise.all(
|
||||
fullPaths.map(f => fs.pathExists(f))
|
||||
fullPaths.map((f) => fs.pathExists(f)),
|
||||
);
|
||||
const foundOneOrMore = existResults.some(b => b);
|
||||
const foundOneOrMore = existResults.some((b) => b);
|
||||
|
||||
if (foundOneOrMore) {
|
||||
return fullPaths.map((f, i) => (existResults[i] ? f : undefined));
|
||||
@@ -417,7 +415,7 @@ export async function runNpmInstall(
|
||||
args: string[] = [],
|
||||
spawnOpts?: SpawnOptions,
|
||||
meta?: Meta,
|
||||
nodeVersion?: NodeVersion
|
||||
nodeVersion?: NodeVersion,
|
||||
): Promise<boolean> {
|
||||
if (meta?.isDev) {
|
||||
debug('Skipping dependency installation because dev mode is enabled');
|
||||
@@ -429,7 +427,7 @@ export async function runNpmInstall(
|
||||
try {
|
||||
await runNpmInstallSema.acquire();
|
||||
const { cliType, packageJsonPath, lockfileVersion } = await scanParentDirs(
|
||||
destPath
|
||||
destPath,
|
||||
);
|
||||
|
||||
// Only allow `runNpmInstall()` to run once per `package.json`
|
||||
@@ -441,9 +439,8 @@ export async function runNpmInstall(
|
||||
if (isSet<string>(meta.runNpmInstallSet)) {
|
||||
if (meta.runNpmInstallSet.has(packageJsonPath)) {
|
||||
return false;
|
||||
} else {
|
||||
meta.runNpmInstallSet.add(packageJsonPath);
|
||||
}
|
||||
meta.runNpmInstallSet.add(packageJsonPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,7 +449,7 @@ export async function runNpmInstall(
|
||||
debug(`Installing to ${destPath}`);
|
||||
|
||||
const opts: SpawnOptionsExtended = { cwd: destPath, ...spawnOpts };
|
||||
const env = opts.env ? { ...opts.env } : { ...process.env };
|
||||
const env = cloneEnv(opts.env || process.env);
|
||||
delete env.NODE_ENV;
|
||||
opts.env = getEnvForPackageManager({
|
||||
cliType,
|
||||
@@ -465,14 +462,27 @@ export async function runNpmInstall(
|
||||
if (cliType === 'npm') {
|
||||
opts.prettyCommand = 'npm install';
|
||||
commandArgs = args
|
||||
.filter(a => a !== '--prefer-offline')
|
||||
.filter((a) => a !== '--prefer-offline')
|
||||
.concat(['install', '--no-audit', '--unsafe-perm']);
|
||||
if (
|
||||
nodeVersion?.major === 16 &&
|
||||
spawnOpts?.env?.VERCEL_NPM_LEGACY_PEER_DEPS === '1' &&
|
||||
spawnOpts.env.ENABLE_EXPERIMENTAL_COREPACK !== '1'
|
||||
) {
|
||||
// Starting in npm@8.6.0, if you ran `npm install --legacy-peer-deps`,
|
||||
// and then later ran `npm install`, it would fail. So the only way
|
||||
// to safely upgrade npm from npm@8.5.0 is to set this flag. The docs
|
||||
// say this flag is not recommended so its is behind a feature flag
|
||||
// so we can remove it in node@18, which can introduce breaking changes.
|
||||
// See https://docs.npmjs.com/cli/v8/using-npm/config#legacy-peer-deps
|
||||
commandArgs.push('--legacy-peer-deps');
|
||||
}
|
||||
} else if (cliType === 'pnpm') {
|
||||
// PNPM's install command is similar to NPM's but without the audit nonsense
|
||||
// @see options https://pnpm.io/cli/install
|
||||
opts.prettyCommand = 'pnpm install';
|
||||
commandArgs = args
|
||||
.filter(a => a !== '--prefer-offline')
|
||||
.filter((a) => a !== '--prefer-offline')
|
||||
.concat(['install', '--unsafe-perm']);
|
||||
} else {
|
||||
opts.prettyCommand = 'yarn install';
|
||||
@@ -500,10 +510,10 @@ export function getEnvForPackageManager({
|
||||
cliType: CliType;
|
||||
lockfileVersion: number | undefined;
|
||||
nodeVersion: NodeVersion | undefined;
|
||||
env: { [x: string]: string | undefined };
|
||||
env: Record<string, string | undefined>;
|
||||
}) {
|
||||
const newEnv: { [x: string]: string | undefined } = { ...env };
|
||||
const oldPath = env.PATH + '';
|
||||
const newEnv: Record<string, string | undefined> = { ...env };
|
||||
const oldPath = `${env.PATH}`;
|
||||
const npm7 = '/node16/bin-npm7';
|
||||
const pnpm7 = '/pnpm7/node_modules/.bin';
|
||||
const corepackEnabled = env.ENABLE_EXPERIMENTAL_COREPACK === '1';
|
||||
@@ -559,7 +569,7 @@ export async function runCustomInstallCommand({
|
||||
nodeVersion,
|
||||
env: spawnOpts?.env || {},
|
||||
});
|
||||
debug(`Running with $PATH:`, env?.PATH || '');
|
||||
debug(`Running with $PATH:`, env.PATH || '');
|
||||
await execCommand(installCommand, {
|
||||
...spawnOpts,
|
||||
env,
|
||||
@@ -570,17 +580,17 @@ export async function runCustomInstallCommand({
|
||||
export async function runPackageJsonScript(
|
||||
destPath: string,
|
||||
scriptNames: string | Iterable<string>,
|
||||
spawnOpts?: SpawnOptions
|
||||
spawnOpts?: SpawnOptions,
|
||||
) {
|
||||
assert(path.isAbsolute(destPath));
|
||||
|
||||
const { packageJson, cliType, lockfileVersion } = await scanParentDirs(
|
||||
destPath,
|
||||
true
|
||||
true,
|
||||
);
|
||||
const scriptName = getScriptName(
|
||||
packageJson,
|
||||
typeof scriptNames === 'string' ? [scriptNames] : scriptNames
|
||||
typeof scriptNames === 'string' ? [scriptNames] : scriptNames,
|
||||
);
|
||||
if (!scriptName) return false;
|
||||
|
||||
@@ -594,10 +604,7 @@ export async function runPackageJsonScript(
|
||||
cliType,
|
||||
lockfileVersion,
|
||||
nodeVersion: undefined,
|
||||
env: {
|
||||
...process.env,
|
||||
...spawnOpts?.env,
|
||||
},
|
||||
env: cloneEnv(process.env, spawnOpts?.env),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -620,7 +627,7 @@ export async function runBundleInstall(
|
||||
destPath: string,
|
||||
args: string[] = [],
|
||||
spawnOpts?: SpawnOptions,
|
||||
meta?: Meta
|
||||
meta?: Meta,
|
||||
) {
|
||||
if (meta && meta.isDev) {
|
||||
debug('Skipping dependency installation because dev mode is enabled');
|
||||
@@ -637,7 +644,7 @@ export async function runPipInstall(
|
||||
destPath: string,
|
||||
args: string[] = [],
|
||||
spawnOpts?: SpawnOptions,
|
||||
meta?: Meta
|
||||
meta?: Meta,
|
||||
) {
|
||||
if (meta && meta.isDev) {
|
||||
debug('Skipping dependency installation because dev mode is enabled');
|
||||
@@ -650,13 +657,13 @@ export async function runPipInstall(
|
||||
await spawnAsync(
|
||||
'pip3',
|
||||
['install', '--disable-pip-version-check', ...args],
|
||||
opts
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
export function getScriptName(
|
||||
pkg: Pick<PackageJson, 'scripts'> | null | undefined,
|
||||
possibleNames: Iterable<string>
|
||||
possibleNames: Iterable<string>,
|
||||
): string | null {
|
||||
if (pkg?.scripts) {
|
||||
for (const name of possibleNames) {
|
||||
@@ -674,5 +681,5 @@ export function getScriptName(
|
||||
*/
|
||||
export const installDependencies = deprecate(
|
||||
runNpmInstall,
|
||||
'installDependencies() is deprecated. Please use runNpmInstall() instead.'
|
||||
'installDependencies() is deprecated. Please use runNpmInstall() instead.',
|
||||
);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import eos from 'end-of-stream';
|
||||
|
||||
export default function streamToBuffer(
|
||||
stream: NodeJS.ReadableStream
|
||||
stream: NodeJS.ReadableStream,
|
||||
): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
const buffers: Buffer[] = [];
|
||||
|
||||
stream.on('data', buffers.push.bind(buffers));
|
||||
|
||||
eos(stream, err => {
|
||||
eos(stream, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
@@ -18,7 +18,7 @@ export default function streamToBuffer(
|
||||
resolve(Buffer.allocUnsafe(0));
|
||||
break;
|
||||
case 1:
|
||||
resolve(buffers[0]);
|
||||
resolve(buffers[0]!);
|
||||
break;
|
||||
default:
|
||||
resolve(Buffer.concat(buffers));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
type Envs = { [key: string]: string | undefined };
|
||||
type Envs = Record<string, string | undefined>;
|
||||
|
||||
/**
|
||||
* Get the framework-specific prefixed System Environment Variables.
|
||||
@@ -17,8 +17,8 @@ export function getPrefixedEnvVars({
|
||||
const newEnvs: Envs = {};
|
||||
if (envPrefix && envs.VERCEL_URL) {
|
||||
Object.keys(envs)
|
||||
.filter(key => key.startsWith(vercelSystemEnvPrefix))
|
||||
.forEach(key => {
|
||||
.filter((key) => key.startsWith(vercelSystemEnvPrefix))
|
||||
.forEach((key) => {
|
||||
const newKey = `${envPrefix}${key}`;
|
||||
if (!(newKey in envs)) {
|
||||
newEnvs[newKey] = envs[key];
|
||||
|
||||
@@ -4,13 +4,9 @@ import FileRef from './file-ref';
|
||||
import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
|
||||
import { NodejsLambda } from './nodejs-lambda';
|
||||
import { Prerender } from './prerender';
|
||||
import download, {
|
||||
downloadFile,
|
||||
DownloadedFiles,
|
||||
isSymbolicLink,
|
||||
} from './fs/download';
|
||||
import download, { downloadFile, isSymbolicLink } from './fs/download';
|
||||
import getWriteableDirectory from './fs/get-writable-directory';
|
||||
import glob, { GlobOptions } from './fs/glob';
|
||||
import glob from './fs/glob';
|
||||
import rename from './fs/rename';
|
||||
import {
|
||||
execAsync,
|
||||
@@ -41,6 +37,9 @@ import debug from './debug';
|
||||
import getIgnoreFilter from './get-ignore-filter';
|
||||
import { getPlatformEnv } from './get-platform-env';
|
||||
import { getPrefixedEnvVars } from './get-prefixed-env-vars';
|
||||
import { cloneEnv } from './clone-env';
|
||||
import type { GlobOptions } from './fs/glob';
|
||||
import type { DownloadedFiles } from './fs/download';
|
||||
|
||||
export {
|
||||
FileBlob,
|
||||
@@ -52,10 +51,8 @@ export {
|
||||
Prerender,
|
||||
download,
|
||||
downloadFile,
|
||||
DownloadedFiles,
|
||||
getWriteableDirectory,
|
||||
glob,
|
||||
GlobOptions,
|
||||
rename,
|
||||
execAsync,
|
||||
spawnAsync,
|
||||
@@ -84,8 +81,11 @@ export {
|
||||
getLambdaOptionsFromFunction,
|
||||
scanParentDirs,
|
||||
getIgnoreFilter,
|
||||
cloneEnv,
|
||||
};
|
||||
|
||||
export type { DownloadedFiles, GlobOptions };
|
||||
|
||||
export { EdgeFunction } from './edge-function';
|
||||
export { readConfigFile } from './fs/read-config-file';
|
||||
export { normalizePath } from './fs/normalize-path';
|
||||
|
||||
@@ -7,9 +7,7 @@ import { isSymbolicLink } from './fs/download';
|
||||
import streamToBuffer from './fs/stream-to-buffer';
|
||||
import type { Files, Config } from './types';
|
||||
|
||||
interface Environment {
|
||||
[key: string]: string;
|
||||
}
|
||||
type Environment = Record<string, string>;
|
||||
|
||||
export type LambdaOptions = LambdaOptionsWithFiles | LambdaOptionsWithZipBuffer;
|
||||
|
||||
@@ -94,30 +92,30 @@ export class Lambda {
|
||||
if (allowQuery !== undefined) {
|
||||
assert(Array.isArray(allowQuery), '"allowQuery" is not an Array');
|
||||
assert(
|
||||
allowQuery.every(q => typeof q === 'string'),
|
||||
'"allowQuery" is not a string Array'
|
||||
allowQuery.every((q) => typeof q === 'string'),
|
||||
'"allowQuery" is not a string Array',
|
||||
);
|
||||
}
|
||||
|
||||
if (supportsMultiPayloads !== undefined) {
|
||||
assert(
|
||||
typeof supportsMultiPayloads === 'boolean',
|
||||
'"supportsMultiPayloads" is not a boolean'
|
||||
'"supportsMultiPayloads" is not a boolean',
|
||||
);
|
||||
}
|
||||
|
||||
if (supportsWrapper !== undefined) {
|
||||
assert(
|
||||
typeof supportsWrapper === 'boolean',
|
||||
'"supportsWrapper" is not a boolean'
|
||||
'"supportsWrapper" is not a boolean',
|
||||
);
|
||||
}
|
||||
|
||||
if (regions !== undefined) {
|
||||
assert(Array.isArray(regions), '"regions" is not an Array');
|
||||
assert(
|
||||
regions.every(r => typeof r === 'string'),
|
||||
'"regions" is not a string Array'
|
||||
regions.every((r) => typeof r === 'string'),
|
||||
'"regions" is not a string Array',
|
||||
);
|
||||
}
|
||||
this.type = 'Lambda';
|
||||
@@ -172,6 +170,7 @@ export async function createZip(files: Files): Promise<Buffer> {
|
||||
const symlinkTargets = new Map<string, string>();
|
||||
for (const name of names) {
|
||||
const file = files[name];
|
||||
if (!file) continue;
|
||||
if (file.mode && isSymbolicLink(file.mode) && file.type === 'FileFsRef') {
|
||||
const symlinkTarget = await readlink(file.fsPath);
|
||||
symlinkTargets.set(name, symlinkTarget);
|
||||
@@ -182,6 +181,7 @@ export async function createZip(files: Files): Promise<Buffer> {
|
||||
const zipBuffer = await new Promise<Buffer>((resolve, reject) => {
|
||||
for (const name of names) {
|
||||
const file = files[name];
|
||||
if (!file) continue;
|
||||
const opts = { mode: file.mode, mtime };
|
||||
const symlinkTarget = symlinkTargets.get(name);
|
||||
if (typeof symlinkTarget === 'string') {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Lambda, LambdaOptionsWithFiles } from './lambda';
|
||||
import { Lambda } from './lambda';
|
||||
import type { LambdaOptionsWithFiles } from './lambda';
|
||||
|
||||
interface NodejsLambdaOptions extends LambdaOptionsWithFiles {
|
||||
shouldAddHelpers: boolean;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { File } from './types';
|
||||
import { Lambda } from './lambda';
|
||||
import type { File } from './types';
|
||||
import type { Lambda } from './lambda';
|
||||
|
||||
interface PrerenderOptions {
|
||||
expiration: number | false;
|
||||
@@ -36,30 +36,30 @@ export class Prerender {
|
||||
(group <= 0 || !Number.isInteger(group))
|
||||
) {
|
||||
throw new Error(
|
||||
'The `group` argument for `Prerender` needs to be a natural number.'
|
||||
'The `group` argument for `Prerender` needs to be a natural number.',
|
||||
);
|
||||
}
|
||||
this.group = group;
|
||||
|
||||
if (bypassToken == null) {
|
||||
if (typeof bypassToken === 'undefined' || bypassToken === null) {
|
||||
this.bypassToken = null;
|
||||
} else if (typeof bypassToken === 'string') {
|
||||
if (bypassToken.length < 32) {
|
||||
// Enforce 128 bits of entropy for safety reasons (UUIDv4 size)
|
||||
throw new Error(
|
||||
'The `bypassToken` argument for `Prerender` must be 32 characters or more.'
|
||||
'The `bypassToken` argument for `Prerender` must be 32 characters or more.',
|
||||
);
|
||||
}
|
||||
this.bypassToken = bypassToken;
|
||||
} else {
|
||||
throw new Error(
|
||||
'The `bypassToken` argument for `Prerender` must be a `string`.'
|
||||
'The `bypassToken` argument for `Prerender` must be a `string`.',
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof fallback === 'undefined') {
|
||||
throw new Error(
|
||||
'The `fallback` argument for `Prerender` needs to be a `FileBlob`, `FileFsRef`, `FileRef`, or null.'
|
||||
'The `fallback` argument for `Prerender` needs to be a `FileBlob`, `FileFsRef`, `FileRef`, or null.',
|
||||
);
|
||||
}
|
||||
this.fallback = fallback;
|
||||
@@ -67,12 +67,12 @@ export class Prerender {
|
||||
if (allowQuery !== undefined) {
|
||||
if (!Array.isArray(allowQuery)) {
|
||||
throw new Error(
|
||||
'The `allowQuery` argument for `Prerender` must be Array.'
|
||||
'The `allowQuery` argument for `Prerender` must be Array.',
|
||||
);
|
||||
}
|
||||
if (!allowQuery.every(q => typeof q === 'string')) {
|
||||
if (!allowQuery.every((q) => typeof q === 'string')) {
|
||||
throw new Error(
|
||||
'The `allowQuery` argument for `Prerender` must be Array of strings.'
|
||||
'The `allowQuery` argument for `Prerender` must be Array of strings.',
|
||||
);
|
||||
}
|
||||
this.allowQuery = allowQuery;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { parse } from 'path';
|
||||
import { normalizePath } from './fs/normalize-path';
|
||||
import type FileFsRef from './file-fs-ref';
|
||||
import type { ShouldServe } from './types';
|
||||
|
||||
@@ -7,21 +8,28 @@ export const shouldServe: ShouldServe = ({
|
||||
files,
|
||||
requestPath,
|
||||
}) => {
|
||||
requestPath = requestPath.replace(/\/$/, ''); // sanitize trailing '/'
|
||||
entrypoint = entrypoint.replace(/\\/, '/'); // windows compatibility
|
||||
const normalizedRequestPath = requestPath.replace(/\/$/, ''); // sanitize trailing '/'
|
||||
const normalizedEntrypoint = normalizePath(entrypoint);
|
||||
|
||||
if (entrypoint === requestPath && hasProp(files, entrypoint)) {
|
||||
if (
|
||||
normalizedEntrypoint === normalizedRequestPath &&
|
||||
hasProp(files, normalizedEntrypoint)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { dir, name } = parse(entrypoint);
|
||||
if (name === 'index' && dir === requestPath && hasProp(files, entrypoint)) {
|
||||
const { dir, name } = parse(normalizedEntrypoint);
|
||||
if (
|
||||
name === 'index' &&
|
||||
dir === normalizedRequestPath &&
|
||||
hasProp(files, normalizedEntrypoint)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
function hasProp(obj: { [path: string]: FileFsRef }, key: string): boolean {
|
||||
function hasProp(obj: Record<string, FileFsRef>, key: string): boolean {
|
||||
return Object.hasOwnProperty.call(obj, key);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@ import type { Lambda } from './lambda';
|
||||
import type { Prerender } from './prerender';
|
||||
import type { EdgeFunction } from './edge-function';
|
||||
|
||||
export interface Env {
|
||||
[name: string]: string | undefined;
|
||||
}
|
||||
export type Env = Record<string, string | undefined>;
|
||||
|
||||
export type File = FileRef | FileFsRef | FileBlob;
|
||||
export interface FileBase {
|
||||
@@ -18,9 +16,7 @@ export interface FileBase {
|
||||
toStreamAsync?: () => Promise<NodeJS.ReadableStream>;
|
||||
}
|
||||
|
||||
export interface Files {
|
||||
[filePath: string]: File;
|
||||
}
|
||||
export type Files = Record<string, File>;
|
||||
|
||||
export interface Config {
|
||||
maxLambdaSize?: string;
|
||||
@@ -32,7 +28,7 @@ export interface Config {
|
||||
rust?: string;
|
||||
debug?: boolean;
|
||||
zeroConfig?: boolean;
|
||||
import?: { [key: string]: string };
|
||||
import?: Record<string, string>;
|
||||
functions?: BuilderFunctions;
|
||||
projectSettings?: ProjectSettings;
|
||||
outputDirectory?: string;
|
||||
@@ -41,6 +37,7 @@ export interface Config {
|
||||
devCommand?: string;
|
||||
framework?: string | null;
|
||||
nodeVersion?: string;
|
||||
middleware?: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
@@ -149,9 +146,7 @@ export interface ShouldServeOptions {
|
||||
/**
|
||||
* All source files of the project
|
||||
*/
|
||||
files: {
|
||||
[path: string]: FileFsRef;
|
||||
};
|
||||
files: Record<string, FileFsRef>;
|
||||
|
||||
/**
|
||||
* A writable temporary directory where you are encouraged to perform your
|
||||
@@ -209,9 +204,7 @@ export namespace PackageJson {
|
||||
/**
|
||||
* A map of exposed bin commands
|
||||
*/
|
||||
export interface BinMap {
|
||||
[commandName: string]: string;
|
||||
}
|
||||
export type BinMap = Record<string, string>;
|
||||
|
||||
/**
|
||||
* A bugs link
|
||||
@@ -229,9 +222,7 @@ export namespace PackageJson {
|
||||
/**
|
||||
* A map of dependencies
|
||||
*/
|
||||
export interface DependencyMap {
|
||||
[dependencyName: string]: string;
|
||||
}
|
||||
export type DependencyMap = Record<string, string>;
|
||||
|
||||
/**
|
||||
* CommonJS package structure
|
||||
@@ -261,9 +252,7 @@ export namespace PackageJson {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface ScriptsMap {
|
||||
[scriptName: string]: string;
|
||||
}
|
||||
export type ScriptsMap = Record<string, string>;
|
||||
}
|
||||
|
||||
export interface PackageJson {
|
||||
@@ -311,15 +300,16 @@ export interface Builder {
|
||||
config?: Config;
|
||||
}
|
||||
|
||||
export interface BuilderFunctions {
|
||||
[key: string]: {
|
||||
export type BuilderFunctions = Record<
|
||||
string,
|
||||
{
|
||||
memory?: number;
|
||||
maxDuration?: number;
|
||||
runtime?: string;
|
||||
includeFiles?: string;
|
||||
excludeFiles?: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
>;
|
||||
|
||||
export interface ProjectSettings {
|
||||
framework?: string | null;
|
||||
@@ -341,6 +331,7 @@ export interface BuilderV2 {
|
||||
version: 2;
|
||||
build: BuildV2;
|
||||
prepareCache?: PrepareCache;
|
||||
shouldServe?: ShouldServe;
|
||||
}
|
||||
|
||||
export interface BuilderV3 {
|
||||
@@ -353,7 +344,7 @@ export interface BuilderV3 {
|
||||
|
||||
type ImageFormat = 'image/avif' | 'image/webp';
|
||||
|
||||
export type RemotePattern = {
|
||||
export interface RemotePattern {
|
||||
/**
|
||||
* Must be `http` or `https`.
|
||||
*/
|
||||
@@ -378,7 +369,7 @@ export type RemotePattern = {
|
||||
* Double `**` matches any number of path segments.
|
||||
*/
|
||||
pathname?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Images {
|
||||
domains: string[];
|
||||
@@ -416,13 +407,11 @@ export interface BuildResultV2Typical {
|
||||
// TODO: use proper `Route` type from `routing-utils` (perhaps move types to a common package)
|
||||
routes?: any[];
|
||||
images?: Images;
|
||||
output: {
|
||||
[key: string]: File | Lambda | Prerender | EdgeFunction;
|
||||
};
|
||||
wildcard?: Array<{
|
||||
output: Record<string, File | Lambda | Prerender | EdgeFunction>;
|
||||
wildcard?: {
|
||||
domain: string;
|
||||
value: string;
|
||||
}>;
|
||||
}[];
|
||||
}
|
||||
|
||||
export type BuildResultV2 = BuildResultV2Typical | BuildResultBuildOutput;
|
||||
@@ -437,8 +426,8 @@ export type BuildV2 = (options: BuildOptions) => Promise<BuildResultV2>;
|
||||
export type BuildV3 = (options: BuildOptions) => Promise<BuildResultV3>;
|
||||
export type PrepareCache = (options: PrepareCacheOptions) => Promise<Files>;
|
||||
export type ShouldServe = (
|
||||
options: ShouldServeOptions
|
||||
options: ShouldServeOptions,
|
||||
) => boolean | Promise<boolean>;
|
||||
export type StartDevServer = (
|
||||
options: StartDevServerOptions
|
||||
options: StartDevServerOptions,
|
||||
) => Promise<StartDevServerResult>;
|
||||
|
||||
1
packages/build-utils/test/fixtures/20-npm-7/package-lock.json
generated
vendored
1
packages/build-utils/test/fixtures/20-npm-7/package-lock.json
generated
vendored
@@ -5,6 +5,7 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "20-npm-7",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
17
packages/build-utils/test/integration.test.ts
vendored
17
packages/build-utils/test/integration.test.ts
vendored
@@ -2,7 +2,7 @@ import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import {
|
||||
testDeployment,
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
} from '../../../test/lib/deployment/test-deployment';
|
||||
|
||||
jest.setTimeout(4 * 60 * 1000);
|
||||
@@ -21,18 +21,18 @@ const skipFixtures: string[] = [
|
||||
'08-zero-config-middleman',
|
||||
'21-npm-workspaces',
|
||||
'23-pnpm-workspaces',
|
||||
'41-nx-monorepo',
|
||||
'42-npm-workspace-with-nx',
|
||||
];
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const fixture of fs.readdirSync(fixturesPath)) {
|
||||
if (skipFixtures.includes(fixture)) {
|
||||
continue; // eslint-disable-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-loop-func
|
||||
it(`Should build "${fixture}"`, async () => {
|
||||
await expect(
|
||||
testDeployment(path.join(fixturesPath, fixture))
|
||||
testDeployment(path.join(fixturesPath, fixture)),
|
||||
).resolves.toBeDefined();
|
||||
});
|
||||
}
|
||||
@@ -41,21 +41,18 @@ for (const fixture of fs.readdirSync(fixturesPath)) {
|
||||
|
||||
const buildersToTestWith = ['node'];
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const builder of buildersToTestWith) {
|
||||
const fixturesPath2 = path.resolve(
|
||||
__dirname,
|
||||
`../../${builder}/test/fixtures`
|
||||
`../../${builder}/test/fixtures`,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const fixture of fs.readdirSync(fixturesPath2)) {
|
||||
// don't run all foreign fixtures, just some
|
||||
if (['01-cowsay', '01-cache-headers', '03-env-vars'].includes(fixture)) {
|
||||
// eslint-disable-next-line no-loop-func
|
||||
it(`Should build "${builder}/${fixture}"`, async () => {
|
||||
await expect(
|
||||
testDeployment(path.join(fixturesPath2, fixture))
|
||||
testDeployment(path.join(fixturesPath2, fixture)),
|
||||
).resolves.toBeDefined();
|
||||
});
|
||||
}
|
||||
|
||||
4
packages/build-utils/test/tsconfig.json
vendored
4
packages/build-utils/test/tsconfig.json
vendored
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"include": ["*.test.ts"]
|
||||
}
|
||||
120
packages/build-utils/test/unit.clone-env.test.ts
vendored
Normal file
120
packages/build-utils/test/unit.clone-env.test.ts
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
import { cloneEnv } from '../src';
|
||||
|
||||
it('should clone env with Path', () => {
|
||||
expect(
|
||||
cloneEnv(
|
||||
new Proxy(
|
||||
{
|
||||
foo: 'bar',
|
||||
Path: 'baz',
|
||||
},
|
||||
{
|
||||
get(target: typeof process.env, prop: string) {
|
||||
if (prop === 'PATH') {
|
||||
return target.PATH ?? target.Path;
|
||||
}
|
||||
return target[prop];
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
).toEqual({
|
||||
foo: 'bar',
|
||||
Path: 'baz',
|
||||
PATH: 'baz',
|
||||
});
|
||||
});
|
||||
|
||||
it('should clone env with PATH', () => {
|
||||
expect(
|
||||
cloneEnv({
|
||||
foo: 'bar',
|
||||
PATH: 'baz',
|
||||
})
|
||||
).toEqual({
|
||||
foo: 'bar',
|
||||
PATH: 'baz',
|
||||
});
|
||||
});
|
||||
|
||||
it('should clone and merge multiple env objects', () => {
|
||||
// note: this also tests the last object doesn't overwrite `PATH` with
|
||||
// `undefined`
|
||||
expect(
|
||||
cloneEnv(
|
||||
{
|
||||
foo: 'bar',
|
||||
},
|
||||
{
|
||||
PATH: 'baz',
|
||||
},
|
||||
{
|
||||
baz: 'wiz',
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
foo: 'bar',
|
||||
PATH: 'baz',
|
||||
baz: 'wiz',
|
||||
});
|
||||
});
|
||||
|
||||
it('should clone the actual process.env object', () => {
|
||||
expect(cloneEnv(process.env).PATH).toEqual(process.env.PATH);
|
||||
});
|
||||
|
||||
it('should overwrite PATH with last value', () => {
|
||||
expect(
|
||||
cloneEnv(
|
||||
new Proxy(
|
||||
{
|
||||
Path: 'foo',
|
||||
},
|
||||
{
|
||||
get(target: typeof process.env, prop: string) {
|
||||
if (prop === 'PATH') {
|
||||
return target.PATH ?? target.Path;
|
||||
}
|
||||
return target[prop];
|
||||
},
|
||||
}
|
||||
),
|
||||
{
|
||||
PATH: 'bar',
|
||||
},
|
||||
{
|
||||
PATH: undefined,
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
Path: 'foo',
|
||||
PATH: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle process.env at any argument position', () => {
|
||||
expect(
|
||||
cloneEnv(
|
||||
{
|
||||
foo: 'bar',
|
||||
},
|
||||
new Proxy(
|
||||
{
|
||||
Path: 'baz',
|
||||
},
|
||||
{
|
||||
get(target: typeof process.env, prop: string) {
|
||||
if (prop === 'PATH') {
|
||||
return target.PATH ?? target.Path;
|
||||
}
|
||||
return target[prop];
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
).toEqual({
|
||||
foo: 'bar',
|
||||
Path: 'baz',
|
||||
PATH: 'baz',
|
||||
});
|
||||
});
|
||||
@@ -3,11 +3,11 @@ import { delimiter } from 'path';
|
||||
import { getEnvForPackageManager } from '../src';
|
||||
|
||||
describe('Test `getEnvForPackageManager()`', () => {
|
||||
const cases: Array<{
|
||||
const cases: {
|
||||
name: string;
|
||||
args: Parameters<typeof getEnvForPackageManager>[0];
|
||||
want: unknown;
|
||||
}> = [
|
||||
}[] = [
|
||||
{
|
||||
name: 'should do nothing to env for npm < 6 and node < 16',
|
||||
args: {
|
||||
@@ -190,7 +190,7 @@ describe('Test `getEnvForPackageManager()`', () => {
|
||||
nodeVersion: args.nodeVersion,
|
||||
env: args.env,
|
||||
}),
|
||||
want
|
||||
want,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
46
packages/build-utils/test/unit.get-npm-bin-path.test.ts
vendored
Normal file
46
packages/build-utils/test/unit.get-npm-bin-path.test.ts
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
import { join, parse } from 'path';
|
||||
import { getNodeBinPath } from '../src';
|
||||
|
||||
describe('Test `getNodeBinPath()`', () => {
|
||||
it('should work with npm7', async () => {
|
||||
const cwd = join(__dirname, 'fixtures', '20-npm-7');
|
||||
const result = await getNodeBinPath({ cwd });
|
||||
expect(result).toBe(join(cwd, 'node_modules', '.bin'));
|
||||
});
|
||||
|
||||
it('should work with yarn', async () => {
|
||||
const cwd = join(__dirname, 'fixtures', '19-yarn-v2');
|
||||
const result = await getNodeBinPath({ cwd });
|
||||
expect(result).toBe(join(cwd, 'node_modules', '.bin'));
|
||||
});
|
||||
|
||||
it('should work with npm 6', async () => {
|
||||
const cwd = join(__dirname, 'fixtures', '08-yarn-npm/with-npm');
|
||||
const result = await getNodeBinPath({ cwd });
|
||||
expect(result).toBe(join(cwd, 'node_modules', '.bin'));
|
||||
});
|
||||
|
||||
it('should work with npm workspaces', async () => {
|
||||
const cwd = join(__dirname, 'fixtures', '21-npm-workspaces/a');
|
||||
const result = await getNodeBinPath({ cwd });
|
||||
expect(result).toBe(join(cwd, '..', 'node_modules', '.bin'));
|
||||
});
|
||||
|
||||
it('should work with pnpm', async () => {
|
||||
const cwd = join(__dirname, 'fixtures', '22-pnpm');
|
||||
const result = await getNodeBinPath({ cwd });
|
||||
expect(result).toBe(join(cwd, 'node_modules', '.bin'));
|
||||
});
|
||||
|
||||
it('should work with pnpm workspaces', async () => {
|
||||
const cwd = join(__dirname, 'fixtures', '23-pnpm-workspaces/c');
|
||||
const result = await getNodeBinPath({ cwd });
|
||||
expect(result).toBe(join(cwd, '..', 'node_modules', '.bin'));
|
||||
});
|
||||
|
||||
it('should fallback to cwd if no lockfile found', async () => {
|
||||
const cwd = parse(process.cwd()).root;
|
||||
const result = await getNodeBinPath({ cwd });
|
||||
expect(result).toBe(join(cwd, 'node_modules', '.bin'));
|
||||
});
|
||||
});
|
||||
@@ -38,8 +38,8 @@ describe('Test `getPlatformEnv()`', () => {
|
||||
}
|
||||
assert(err);
|
||||
assert.equal(
|
||||
err!.message,
|
||||
'Both "VERCEL_FOO" and "NOW_FOO" env vars are defined. Please only define the "VERCEL_FOO" env var.'
|
||||
err.message,
|
||||
'Both "VERCEL_FOO" and "NOW_FOO" env vars are defined. Please only define the "VERCEL_FOO" env var.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { getPrefixedEnvVars } from '../src';
|
||||
|
||||
describe('Test `getPrefixedEnvVars()`', () => {
|
||||
const cases: Array<{
|
||||
const cases: {
|
||||
name: string;
|
||||
args: Parameters<typeof getPrefixedEnvVars>[0];
|
||||
want: ReturnType<typeof getPrefixedEnvVars>;
|
||||
}> = [
|
||||
}[] = [
|
||||
{
|
||||
name: 'should work with NEXT_PUBLIC_',
|
||||
args: {
|
||||
|
||||
@@ -12,12 +12,12 @@ describe('Test `getSpawnOptions()`', () => {
|
||||
process.env.PATH = origProcessEnvPath;
|
||||
});
|
||||
|
||||
const cases: Array<{
|
||||
const cases: {
|
||||
name: string;
|
||||
args: Parameters<typeof getSpawnOptions>;
|
||||
envPath: string | undefined;
|
||||
want: string | undefined;
|
||||
}> = [
|
||||
}[] = [
|
||||
{
|
||||
name: 'should do nothing when isDev and node14',
|
||||
args: [
|
||||
|
||||
172
packages/build-utils/test/unit.run-npm-install.test.ts
vendored
Normal file
172
packages/build-utils/test/unit.run-npm-install.test.ts
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
import path from 'path';
|
||||
import { runNpmInstall, cloneEnv } from '../src';
|
||||
import type { Meta } from '../src/types';
|
||||
|
||||
const spawnMock = jest.fn();
|
||||
jest.mock('cross-spawn', () => {
|
||||
const spawn = (...args: any) => {
|
||||
spawnMock(...args);
|
||||
const child = {
|
||||
on: (type: string, fn: (code: number) => void) => {
|
||||
if (type === 'close') {
|
||||
return fn(0);
|
||||
}
|
||||
},
|
||||
};
|
||||
return child;
|
||||
};
|
||||
return spawn;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
spawnMock.mockClear();
|
||||
});
|
||||
|
||||
function getTestSpawnOpts(env: Record<string, string>) {
|
||||
return { env: cloneEnv(process.env, env) };
|
||||
}
|
||||
|
||||
function getNodeVersion(major: number) {
|
||||
return { major, range: `${major}.x`, runtime: `nodejs${major}.x` };
|
||||
}
|
||||
|
||||
it('should not include peer dependencies when missing VERCEL_NPM_LEGACY_PEER_DEPS on node16', async () => {
|
||||
const fixture = path.join(__dirname, 'fixtures', '20-npm-7');
|
||||
const meta: Meta = {};
|
||||
const spawnOpts = getTestSpawnOpts({});
|
||||
const nodeVersion = { major: 16 } as any;
|
||||
await runNpmInstall(fixture, [], spawnOpts, meta, nodeVersion);
|
||||
expect(spawnMock.mock.calls.length).toBe(1);
|
||||
const args = spawnMock.mock.calls[0];
|
||||
expect(args[0]).toEqual('npm');
|
||||
expect(args[1]).toEqual(['install', '--no-audit', '--unsafe-perm']);
|
||||
expect(args[2]).toEqual({
|
||||
cwd: fixture,
|
||||
prettyCommand: 'npm install',
|
||||
stdio: 'inherit',
|
||||
env: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
it('should include peer dependencies when VERCEL_NPM_LEGACY_PEER_DEPS=1 on node16', async () => {
|
||||
const fixture = path.join(__dirname, 'fixtures', '20-npm-7');
|
||||
const meta: Meta = {};
|
||||
const spawnOpts = getTestSpawnOpts({ VERCEL_NPM_LEGACY_PEER_DEPS: '1' });
|
||||
const nodeVersion = getNodeVersion(16);
|
||||
await runNpmInstall(fixture, [], spawnOpts, meta, nodeVersion);
|
||||
expect(spawnMock.mock.calls.length).toBe(1);
|
||||
const args = spawnMock.mock.calls[0];
|
||||
expect(args[0]).toEqual('npm');
|
||||
expect(args[1]).toEqual([
|
||||
'install',
|
||||
'--no-audit',
|
||||
'--unsafe-perm',
|
||||
'--legacy-peer-deps',
|
||||
]);
|
||||
expect(args[2]).toEqual({
|
||||
cwd: fixture,
|
||||
prettyCommand: 'npm install',
|
||||
stdio: 'inherit',
|
||||
env: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include peer dependencies when VERCEL_NPM_LEGACY_PEER_DEPS=1 on node14', async () => {
|
||||
const fixture = path.join(__dirname, 'fixtures', '20-npm-7');
|
||||
const meta: Meta = {};
|
||||
const spawnOpts = getTestSpawnOpts({ VERCEL_NPM_LEGACY_PEER_DEPS: '1' });
|
||||
const nodeVersion = getNodeVersion(14);
|
||||
await runNpmInstall(fixture, [], spawnOpts, meta, nodeVersion);
|
||||
expect(spawnMock.mock.calls.length).toBe(1);
|
||||
const args = spawnMock.mock.calls[0];
|
||||
expect(args[0]).toEqual('npm');
|
||||
expect(args[1]).toEqual(['install', '--no-audit', '--unsafe-perm']);
|
||||
expect(args[2]).toEqual({
|
||||
cwd: fixture,
|
||||
prettyCommand: 'npm install',
|
||||
stdio: 'inherit',
|
||||
env: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include peer dependencies when VERCEL_NPM_LEGACY_PEER_DEPS=1 on node16 with corepack enabled', async () => {
|
||||
const fixture = path.join(__dirname, 'fixtures', '20-npm-7');
|
||||
const meta: Meta = {};
|
||||
const spawnOpts = getTestSpawnOpts({
|
||||
VERCEL_NPM_LEGACY_PEER_DEPS: '1',
|
||||
ENABLE_EXPERIMENTAL_COREPACK: '1',
|
||||
});
|
||||
const nodeVersion = getNodeVersion(16);
|
||||
await runNpmInstall(fixture, [], spawnOpts, meta, nodeVersion);
|
||||
expect(spawnMock.mock.calls.length).toBe(1);
|
||||
const args = spawnMock.mock.calls[0];
|
||||
expect(args[0]).toEqual('npm');
|
||||
expect(args[1]).toEqual(['install', '--no-audit', '--unsafe-perm']);
|
||||
expect(args[2]).toEqual({
|
||||
cwd: fixture,
|
||||
prettyCommand: 'npm install',
|
||||
stdio: 'inherit',
|
||||
env: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
it('should only invoke `runNpmInstall()` once per `package.json` file (serial)', async () => {
|
||||
const meta: Meta = {};
|
||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
||||
const apiDir = path.join(fixture, 'api');
|
||||
|
||||
const run1 = await runNpmInstall(apiDir, [], undefined, meta);
|
||||
expect(run1).toEqual(true);
|
||||
expect(
|
||||
(meta.runNpmInstallSet as Set<string>).has(
|
||||
path.join(fixture, 'package.json'),
|
||||
),
|
||||
).toEqual(true);
|
||||
|
||||
const run2 = await runNpmInstall(apiDir, [], undefined, meta);
|
||||
expect(run2).toEqual(false);
|
||||
|
||||
const run3 = await runNpmInstall(fixture, [], undefined, meta);
|
||||
expect(run3).toEqual(false);
|
||||
|
||||
expect(spawnMock.mock.calls.length).toBe(1);
|
||||
const args = spawnMock.mock.calls[0];
|
||||
expect(args[0]).toEqual('yarn');
|
||||
expect(args[1]).toEqual(['install']);
|
||||
expect(args[2]).toEqual({
|
||||
cwd: apiDir,
|
||||
prettyCommand: 'yarn install',
|
||||
stdio: 'inherit',
|
||||
env: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
it('should only invoke `runNpmInstall()` once per `package.json` file (parallel)', async () => {
|
||||
const meta: Meta = {};
|
||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
||||
const apiDir = path.join(fixture, 'api');
|
||||
const [run1, run2, run3] = await Promise.all([
|
||||
runNpmInstall(apiDir, [], undefined, meta),
|
||||
runNpmInstall(apiDir, [], undefined, meta),
|
||||
runNpmInstall(fixture, [], undefined, meta),
|
||||
]);
|
||||
expect(run1).toEqual(true);
|
||||
expect(run2).toEqual(false);
|
||||
expect(run3).toEqual(false);
|
||||
expect(
|
||||
(meta.runNpmInstallSet as Set<string>).has(
|
||||
path.join(fixture, 'package.json'),
|
||||
),
|
||||
).toEqual(true);
|
||||
|
||||
expect(spawnMock.mock.calls.length).toBe(1);
|
||||
const args = spawnMock.mock.calls[0];
|
||||
expect(args[0]).toEqual('yarn');
|
||||
expect(args[1]).toEqual(['install']);
|
||||
expect(args[2]).toEqual({
|
||||
cwd: apiDir,
|
||||
prettyCommand: 'yarn install',
|
||||
stdio: 'inherit',
|
||||
env: expect.any(Object),
|
||||
});
|
||||
});
|
||||
145
packages/build-utils/test/unit.test.ts
vendored
145
packages/build-utils/test/unit.test.ts
vendored
@@ -1,8 +1,7 @@
|
||||
import ms from 'ms';
|
||||
import path from 'path';
|
||||
import fs, { readlink } from 'fs-extra';
|
||||
import retry from 'async-retry';
|
||||
import { strict as assert, strictEqual } from 'assert';
|
||||
import ms from 'ms';
|
||||
import fs, { readlink } from 'fs-extra';
|
||||
import { createZip } from '../src/lambda';
|
||||
import { getSupportedNodeVersion } from '../src/fs/node-version';
|
||||
import download from '../src/fs/download';
|
||||
@@ -16,7 +15,6 @@ import {
|
||||
runPackageJsonScript,
|
||||
scanParentDirs,
|
||||
FileBlob,
|
||||
Meta,
|
||||
} from '../src';
|
||||
|
||||
jest.setTimeout(10 * 1000);
|
||||
@@ -31,11 +29,11 @@ async function expectBuilderError(promise: Promise<any>, pattern: string) {
|
||||
assert('message' in result, `Expected error message but found ${result}`);
|
||||
assert(
|
||||
typeof result.message === 'string',
|
||||
`Expected error to be a string but found ${typeof result.message}`
|
||||
`Expected error to be a string but found ${typeof result.message}`,
|
||||
);
|
||||
assert(
|
||||
result.message.includes(pattern),
|
||||
`Expected ${pattern} but found "${result.message}"`
|
||||
`Expected ${pattern} but found "${result.message}"`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +41,7 @@ let warningMessages: string[];
|
||||
const originalConsoleWarn = console.warn;
|
||||
beforeEach(() => {
|
||||
warningMessages = [];
|
||||
console.warn = m => {
|
||||
console.warn = (m) => {
|
||||
warningMessages.push(m);
|
||||
};
|
||||
});
|
||||
@@ -220,22 +218,22 @@ it('should download symlinks even with incorrect file', async () => {
|
||||
it('should only match supported node versions, otherwise throw an error', async () => {
|
||||
expect(await getSupportedNodeVersion('12.x', false)).toHaveProperty(
|
||||
'major',
|
||||
12
|
||||
12,
|
||||
);
|
||||
expect(await getSupportedNodeVersion('14.x', false)).toHaveProperty(
|
||||
'major',
|
||||
14
|
||||
14,
|
||||
);
|
||||
expect(await getSupportedNodeVersion('16.x', false)).toHaveProperty(
|
||||
'major',
|
||||
16
|
||||
16,
|
||||
);
|
||||
|
||||
const autoMessage =
|
||||
'Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.';
|
||||
await expectBuilderError(
|
||||
getSupportedNodeVersion('8.11.x', true),
|
||||
autoMessage
|
||||
autoMessage,
|
||||
);
|
||||
await expectBuilderError(getSupportedNodeVersion('6.x', true), autoMessage);
|
||||
await expectBuilderError(getSupportedNodeVersion('999.x', true), autoMessage);
|
||||
@@ -244,32 +242,32 @@ it('should only match supported node versions, otherwise throw an error', async
|
||||
|
||||
expect(await getSupportedNodeVersion('12.x', true)).toHaveProperty(
|
||||
'major',
|
||||
12
|
||||
12,
|
||||
);
|
||||
expect(await getSupportedNodeVersion('14.x', true)).toHaveProperty(
|
||||
'major',
|
||||
14
|
||||
14,
|
||||
);
|
||||
expect(await getSupportedNodeVersion('16.x', true)).toHaveProperty(
|
||||
'major',
|
||||
16
|
||||
16,
|
||||
);
|
||||
|
||||
const foundMessage =
|
||||
'Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.';
|
||||
await expectBuilderError(
|
||||
getSupportedNodeVersion('8.11.x', false),
|
||||
foundMessage
|
||||
foundMessage,
|
||||
);
|
||||
await expectBuilderError(getSupportedNodeVersion('6.x', false), foundMessage);
|
||||
await expectBuilderError(
|
||||
getSupportedNodeVersion('999.x', false),
|
||||
foundMessage
|
||||
foundMessage,
|
||||
);
|
||||
await expectBuilderError(getSupportedNodeVersion('foo', false), foundMessage);
|
||||
await expectBuilderError(
|
||||
getSupportedNodeVersion('=> 10', false),
|
||||
foundMessage
|
||||
foundMessage,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -281,17 +279,17 @@ it('should match all semver ranges', async () => {
|
||||
expect(await getSupportedNodeVersion('>=10.3.0')).toHaveProperty('major', 16);
|
||||
expect(await getSupportedNodeVersion('11.5.0 - 12.5.0')).toHaveProperty(
|
||||
'major',
|
||||
12
|
||||
12,
|
||||
);
|
||||
expect(await getSupportedNodeVersion('>=9.5.0 <=12.5.0')).toHaveProperty(
|
||||
'major',
|
||||
12
|
||||
12,
|
||||
);
|
||||
expect(await getSupportedNodeVersion('~12.5.0')).toHaveProperty('major', 12);
|
||||
expect(await getSupportedNodeVersion('^12.5.0')).toHaveProperty('major', 12);
|
||||
expect(await getSupportedNodeVersion('12.5.0 - 14.5.0')).toHaveProperty(
|
||||
'major',
|
||||
14
|
||||
14,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -301,14 +299,14 @@ it('should ignore node version in vercel dev getNodeVersion()', async () => {
|
||||
'/tmp',
|
||||
undefined,
|
||||
{ nodeVersion: '1' },
|
||||
{ isDev: true }
|
||||
)
|
||||
{ isDev: true },
|
||||
),
|
||||
).toHaveProperty('runtime', 'nodejs');
|
||||
});
|
||||
|
||||
it('should select project setting from config when no package.json is found', async () => {
|
||||
expect(
|
||||
await getNodeVersion('/tmp', undefined, { nodeVersion: '16.x' }, {})
|
||||
await getNodeVersion('/tmp', undefined, { nodeVersion: '16.x' }, {}),
|
||||
).toHaveProperty('range', '16.x');
|
||||
expect(warningMessages).toStrictEqual([]);
|
||||
});
|
||||
@@ -319,8 +317,8 @@ it('should prefer package.json engines over project setting from config and warn
|
||||
path.join(__dirname, 'pkg-engine-node'),
|
||||
undefined,
|
||||
{ nodeVersion: '12.x' },
|
||||
{}
|
||||
)
|
||||
{},
|
||||
),
|
||||
).toHaveProperty('range', '14.x');
|
||||
expect(warningMessages).toStrictEqual([
|
||||
'Warning: Due to "engines": { "node": "14.x" } in your `package.json` file, the Node.js Version defined in your Project Settings ("12.x") will not apply. Learn More: http://vercel.link/node-version',
|
||||
@@ -333,8 +331,8 @@ it('should warn when package.json engines is exact version', async () => {
|
||||
path.join(__dirname, 'pkg-engine-node-exact'),
|
||||
undefined,
|
||||
{},
|
||||
{}
|
||||
)
|
||||
{},
|
||||
),
|
||||
).toHaveProperty('range', '16.x');
|
||||
expect(warningMessages).toStrictEqual([
|
||||
'Warning: Detected "engines": { "node": "16.14.0" } in your `package.json` with major.minor.patch, but only major Node.js Version can be selected. Learn More: http://vercel.link/node-version',
|
||||
@@ -347,8 +345,8 @@ it('should warn when package.json engines is greater than', async () => {
|
||||
path.join(__dirname, 'pkg-engine-node-greaterthan'),
|
||||
undefined,
|
||||
{},
|
||||
{}
|
||||
)
|
||||
{},
|
||||
),
|
||||
).toHaveProperty('range', '16.x');
|
||||
expect(warningMessages).toStrictEqual([
|
||||
'Warning: Detected "engines": { "node": ">=16" } in your `package.json` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version',
|
||||
@@ -361,8 +359,8 @@ it('should not warn when package.json engines matches project setting from confi
|
||||
path.join(__dirname, 'pkg-engine-node'),
|
||||
undefined,
|
||||
{ nodeVersion: '14' },
|
||||
{}
|
||||
)
|
||||
{},
|
||||
),
|
||||
).toHaveProperty('range', '14.x');
|
||||
expect(warningMessages).toStrictEqual([]);
|
||||
|
||||
@@ -371,8 +369,8 @@ it('should not warn when package.json engines matches project setting from confi
|
||||
path.join(__dirname, 'pkg-engine-node'),
|
||||
undefined,
|
||||
{ nodeVersion: '14.x' },
|
||||
{}
|
||||
)
|
||||
{},
|
||||
),
|
||||
).toHaveProperty('range', '14.x');
|
||||
expect(warningMessages).toStrictEqual([]);
|
||||
|
||||
@@ -381,8 +379,8 @@ it('should not warn when package.json engines matches project setting from confi
|
||||
path.join(__dirname, 'pkg-engine-node'),
|
||||
undefined,
|
||||
{ nodeVersion: '<15' },
|
||||
{}
|
||||
)
|
||||
{},
|
||||
),
|
||||
).toHaveProperty('range', '14.x');
|
||||
expect(warningMessages).toStrictEqual([]);
|
||||
});
|
||||
@@ -419,19 +417,19 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
|
||||
|
||||
expect(await getSupportedNodeVersion('10.x', false)).toHaveProperty(
|
||||
'major',
|
||||
10
|
||||
10,
|
||||
);
|
||||
expect(await getSupportedNodeVersion('10.x', true)).toHaveProperty(
|
||||
'major',
|
||||
10
|
||||
10,
|
||||
);
|
||||
expect(await getSupportedNodeVersion('12.x', false)).toHaveProperty(
|
||||
'major',
|
||||
12
|
||||
12,
|
||||
);
|
||||
expect(await getSupportedNodeVersion('12.x', true)).toHaveProperty(
|
||||
'major',
|
||||
12
|
||||
12,
|
||||
);
|
||||
expect(warningMessages).toStrictEqual([
|
||||
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
||||
@@ -493,7 +491,7 @@ it(
|
||||
const out = await fs.readFile(path.join(fixture, 'env.txt'), 'utf8');
|
||||
expect(out.trim()).toBeTruthy();
|
||||
},
|
||||
ms('1m')
|
||||
ms('1m'),
|
||||
);
|
||||
|
||||
it('should return lockfileVersion 2 with npm7', async () => {
|
||||
@@ -501,6 +499,7 @@ it('should return lockfileVersion 2 with npm7', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('npm');
|
||||
expect(result.lockfileVersion).toEqual(2);
|
||||
expect(result.lockfilePath).toEqual(path.join(fixture, 'package-lock.json'));
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
@@ -509,6 +508,7 @@ it('should not return lockfileVersion with yarn', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('yarn');
|
||||
expect(result.lockfileVersion).toEqual(undefined);
|
||||
expect(result.lockfilePath).toEqual(path.join(fixture, 'yarn.lock'));
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
@@ -517,6 +517,7 @@ it('should return lockfileVersion 1 with older versions of npm', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('npm');
|
||||
expect(result.lockfileVersion).toEqual(1);
|
||||
expect(result.lockfilePath).toEqual(path.join(fixture, 'package-lock.json'));
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
@@ -525,6 +526,9 @@ it('should detect npm Workspaces', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('npm');
|
||||
expect(result.lockfileVersion).toEqual(2);
|
||||
expect(result.lockfilePath).toEqual(
|
||||
path.join(fixture, '..', 'package-lock.json'),
|
||||
);
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
@@ -533,6 +537,7 @@ it('should detect pnpm without workspace', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('pnpm');
|
||||
expect(result.lockfileVersion).toEqual(5.3);
|
||||
expect(result.lockfilePath).toEqual(path.join(fixture, 'pnpm-lock.yaml'));
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
@@ -541,78 +546,32 @@ it('should detect pnpm with workspaces', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('pnpm');
|
||||
expect(result.lockfileVersion).toEqual(5.3);
|
||||
expect(result.lockfilePath).toEqual(
|
||||
path.join(fixture, '..', 'pnpm-lock.yaml'),
|
||||
);
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
it('should detect package.json in nested backend', async () => {
|
||||
const fixture = path.join(
|
||||
__dirname,
|
||||
'../../node/test/fixtures/18.1-nested-packagejson/backend'
|
||||
'../../node/test/fixtures/18.1-nested-packagejson/backend',
|
||||
);
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('yarn');
|
||||
expect(result.lockfileVersion).toEqual(undefined);
|
||||
// There is no lockfile but this test will pick up vercel/vercel/yarn.lock
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
it('should detect package.json in nested frontend', async () => {
|
||||
const fixture = path.join(
|
||||
__dirname,
|
||||
'../../node/test/fixtures/18.1-nested-packagejson/frontend'
|
||||
'../../node/test/fixtures/18.1-nested-packagejson/frontend',
|
||||
);
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('yarn');
|
||||
expect(result.lockfileVersion).toEqual(undefined);
|
||||
// There is no lockfile but this test will pick up vercel/vercel/yarn.lock
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
it('should only invoke `runNpmInstall()` once per `package.json` file (serial)', async () => {
|
||||
const meta: Meta = {};
|
||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
||||
const apiDir = path.join(fixture, 'api');
|
||||
const retryOpts = { maxRetryTime: 1000 };
|
||||
let run1, run2, run3;
|
||||
await retry(async () => {
|
||||
run1 = await runNpmInstall(apiDir, [], undefined, meta);
|
||||
expect(run1).toEqual(true);
|
||||
expect(
|
||||
(meta.runNpmInstallSet as Set<string>).has(
|
||||
path.join(fixture, 'package.json')
|
||||
)
|
||||
).toEqual(true);
|
||||
}, retryOpts);
|
||||
await retry(async () => {
|
||||
run2 = await runNpmInstall(apiDir, [], undefined, meta);
|
||||
expect(run2).toEqual(false);
|
||||
}, retryOpts);
|
||||
await retry(async () => {
|
||||
run3 = await runNpmInstall(fixture, [], undefined, meta);
|
||||
expect(run3).toEqual(false);
|
||||
}, retryOpts);
|
||||
});
|
||||
|
||||
it('should only invoke `runNpmInstall()` once per `package.json` file (parallel)', async () => {
|
||||
const meta: Meta = {};
|
||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
||||
const apiDir = path.join(fixture, 'api');
|
||||
let results: [boolean, boolean, boolean] | undefined;
|
||||
await retry(
|
||||
async () => {
|
||||
results = await Promise.all([
|
||||
runNpmInstall(apiDir, [], undefined, meta),
|
||||
runNpmInstall(apiDir, [], undefined, meta),
|
||||
runNpmInstall(fixture, [], undefined, meta),
|
||||
]);
|
||||
},
|
||||
{ maxRetryTime: 3000 }
|
||||
);
|
||||
const [run1, run2, run3] = results || [];
|
||||
expect(run1).toEqual(true);
|
||||
expect(run2).toEqual(false);
|
||||
expect(run3).toEqual(false);
|
||||
expect(
|
||||
(meta.runNpmInstallSet as Set<string>).has(
|
||||
path.join(fixture, 'package.json')
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
43
packages/build-utils/test/unit.walk.test.ts
vendored
43
packages/build-utils/test/unit.walk.test.ts
vendored
@@ -1,31 +1,38 @@
|
||||
import { walkParentDirs } from '../src';
|
||||
import { strict } from 'assert';
|
||||
import { join } from 'path';
|
||||
import { promises } from 'fs';
|
||||
const { deepEqual, notDeepEqual, fail } = strict;
|
||||
import { walkParentDirs } from '../src';
|
||||
|
||||
const { deepEqual, fail } = strict;
|
||||
const { readFile } = promises;
|
||||
const fixture = (name: string) => join(__dirname, 'walk', name);
|
||||
const filename = 'file.txt';
|
||||
|
||||
async function assertContent(target: string | null, contents: string) {
|
||||
notDeepEqual(target, null);
|
||||
const actual = await readFile(target!, 'utf8');
|
||||
async function expectContent(target: string | null, contents: string) {
|
||||
if (typeof target !== 'string') {
|
||||
throw new Error(`Expected target to be a string`);
|
||||
}
|
||||
const actual = await readFile(target, 'utf8');
|
||||
expect;
|
||||
deepEqual(actual.trim(), contents.trim());
|
||||
}
|
||||
|
||||
describe('Test `walkParentDirs`', () => {
|
||||
describe('walkParentDirs()', () => {
|
||||
it('should throw when `base` is relative', async () => {
|
||||
let err: Error | undefined;
|
||||
const base = './relative';
|
||||
const start = __dirname;
|
||||
try {
|
||||
await walkParentDirs({ base, start, filename });
|
||||
fail('Expected error');
|
||||
} catch (error) {
|
||||
deepEqual(
|
||||
(error as Error).message,
|
||||
'Expected "base" to be absolute path'
|
||||
);
|
||||
} catch (_err) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
err = _err;
|
||||
}
|
||||
if (!err) {
|
||||
throw new Error('Expected `err` to be defined');
|
||||
}
|
||||
expect(err.message).toEqual('Expected "base" to be absolute path');
|
||||
});
|
||||
|
||||
it('should throw when `start` is relative', async () => {
|
||||
@@ -37,7 +44,7 @@ describe('Test `walkParentDirs`', () => {
|
||||
} catch (error) {
|
||||
deepEqual(
|
||||
(error as Error).message,
|
||||
'Expected "start" to be absolute path'
|
||||
'Expected "start" to be absolute path',
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -46,21 +53,21 @@ describe('Test `walkParentDirs`', () => {
|
||||
const base = fixture('every-directory');
|
||||
const start = base;
|
||||
const target = await walkParentDirs({ base, start, filename });
|
||||
await assertContent(target, 'First');
|
||||
await expectContent(target, 'First');
|
||||
});
|
||||
|
||||
it('should find nested two', async () => {
|
||||
const base = fixture('every-directory');
|
||||
const start = join(base, 'two');
|
||||
const target = await walkParentDirs({ base, start, filename });
|
||||
await assertContent(target, 'Second');
|
||||
await expectContent(target, 'Second');
|
||||
});
|
||||
|
||||
it('should find nested three', async () => {
|
||||
const base = fixture('every-directory');
|
||||
const start = join(base, 'two', 'three');
|
||||
const target = await walkParentDirs({ base, start, filename });
|
||||
await assertContent(target, 'Third');
|
||||
await expectContent(target, 'Third');
|
||||
});
|
||||
|
||||
it('should not find nested one', async () => {
|
||||
@@ -88,20 +95,20 @@ describe('Test `walkParentDirs`', () => {
|
||||
const base = fixture('only-one');
|
||||
const start = join(base, 'two', 'three');
|
||||
const target = await walkParentDirs({ base, start, filename });
|
||||
await assertContent(target, 'First');
|
||||
await expectContent(target, 'First');
|
||||
});
|
||||
|
||||
it('should find only two', async () => {
|
||||
const base = fixture('only-two');
|
||||
const start = join(base, 'two', 'three');
|
||||
const target = await walkParentDirs({ base, start, filename });
|
||||
await assertContent(target, 'Second');
|
||||
await expectContent(target, 'Second');
|
||||
});
|
||||
|
||||
it('should find only three', async () => {
|
||||
const base = fixture('only-three');
|
||||
const start = join(base, 'two', 'three');
|
||||
const target = await walkParentDirs({ base, start, filename });
|
||||
await assertContent(target, 'Third');
|
||||
await expectContent(target, 'Third');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
{
|
||||
"extends": "@vercel/style-guide/typescript",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["ES2020"],
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"noEmitOnError": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"outDir": "./dist",
|
||||
"types": ["node", "jest"],
|
||||
"strict": true,
|
||||
"target": "ES2020"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"include": ["src/**/*", "test/*.test.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
4
packages/cli/.eslintignore
Normal file
4
packages/cli/.eslintignore
Normal file
@@ -0,0 +1,4 @@
|
||||
dist
|
||||
test/fixtures
|
||||
test/dev/fixtures
|
||||
src/util/dev/templates/*.ts
|
||||
20
packages/cli/.eslintrc.cjs
Normal file
20
packages/cli/.eslintrc.cjs
Normal file
@@ -0,0 +1,20 @@
|
||||
const { resolve } = require('path');
|
||||
|
||||
const baseConfig = require.resolve('../../.eslintrc.cjs');
|
||||
|
||||
const project = resolve(__dirname, 'tsconfig.json');
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [baseConfig],
|
||||
parserOptions: {
|
||||
project,
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "28.1.4",
|
||||
"version": "28.3.0",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -41,16 +41,16 @@
|
||||
"node": ">= 14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "5.3.2",
|
||||
"@vercel/go": "2.2.2",
|
||||
"@vercel/hydrogen": "0.0.15",
|
||||
"@vercel/next": "3.1.21",
|
||||
"@vercel/node": "2.5.10",
|
||||
"@vercel/python": "3.1.11",
|
||||
"@vercel/redwood": "1.0.19",
|
||||
"@vercel/remix": "1.0.20",
|
||||
"@vercel/ruby": "1.3.28",
|
||||
"@vercel/static-build": "1.0.19",
|
||||
"@vercel/build-utils": "5.4.3",
|
||||
"@vercel/go": "2.2.6",
|
||||
"@vercel/hydrogen": "0.0.19",
|
||||
"@vercel/next": "3.1.26",
|
||||
"@vercel/node": "2.5.15",
|
||||
"@vercel/python": "3.1.15",
|
||||
"@vercel/redwood": "1.0.24",
|
||||
"@vercel/remix": "1.0.25",
|
||||
"@vercel/ruby": "1.3.32",
|
||||
"@vercel/static-build": "1.0.24",
|
||||
"update-notifier": "5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -85,7 +85,6 @@
|
||||
"@types/node-fetch": "2.5.10",
|
||||
"@types/npm-package-arg": "6.1.0",
|
||||
"@types/pluralize": "0.0.29",
|
||||
"@types/progress": "2.0.3",
|
||||
"@types/psl": "1.1.0",
|
||||
"@types/semver": "6.0.1",
|
||||
"@types/tar-fs": "1.16.1",
|
||||
@@ -96,9 +95,9 @@
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@types/yauzl-promise": "2.1.0",
|
||||
"@vercel/client": "12.2.1",
|
||||
"@vercel/frameworks": "1.1.3",
|
||||
"@vercel/fs-detectors": "2.0.5",
|
||||
"@vercel/client": "12.2.5",
|
||||
"@vercel/frameworks": "1.1.6",
|
||||
"@vercel/fs-detectors": "3.3.0",
|
||||
"@vercel/fun": "1.0.4",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
@@ -153,7 +152,6 @@
|
||||
"ora": "3.4.0",
|
||||
"pcre-to-regexp": "1.0.0",
|
||||
"pluralize": "7.0.0",
|
||||
"progress": "2.0.3",
|
||||
"promisepipe": "3.0.0",
|
||||
"psl": "1.1.31",
|
||||
"qr-image": "3.2.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const execa = require('execa');
|
||||
const { join } = require('path');
|
||||
const execa = require('execa');
|
||||
const { readFile, writeFile, readdir, remove } = require('fs-extra');
|
||||
|
||||
async function main() {
|
||||
@@ -14,7 +14,7 @@ async function main() {
|
||||
});
|
||||
|
||||
const files = await readdir(templatesDir);
|
||||
const compiledFiles = files.filter(f => f.endsWith('.js'));
|
||||
const compiledFiles = files.filter((f) => f.endsWith('.js'));
|
||||
|
||||
// Prettier
|
||||
console.log('\nMaking the compiled template functions prettier...');
|
||||
@@ -25,7 +25,7 @@ async function main() {
|
||||
{
|
||||
cwd: templatesDir,
|
||||
stdio: 'inherit',
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
console.log('\nConverting template functions to TypeScript');
|
||||
@@ -36,9 +36,7 @@ async function main() {
|
||||
const def = await readFile(fnPath.replace(/\.js$/, '.tsdef'), 'utf8');
|
||||
const interfaceName = def.match(/interface (\w+)/)[1];
|
||||
|
||||
const lines = require(fnPath)
|
||||
.toString()
|
||||
.split('\n');
|
||||
const lines = require(fnPath).toString().split('\n');
|
||||
let errorHtmlStart = -1;
|
||||
let errorHtmlEnd = -1;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
@@ -56,35 +54,35 @@ async function main() {
|
||||
|
||||
lines[0] = `export default ${lines[0].replace(
|
||||
'(it)',
|
||||
`(it: ${interfaceName}): string`
|
||||
`(it: ${interfaceName}): string`,
|
||||
)}`;
|
||||
|
||||
lines.unshift(
|
||||
"import encodeHTML from 'escape-html';",
|
||||
'',
|
||||
...def.split('\n')
|
||||
...def.split('\n'),
|
||||
);
|
||||
|
||||
await Promise.all([writeFile(tsPath, lines.join('\n')), remove(fnPath)]);
|
||||
console.log(
|
||||
`${file} -> ${file.replace(/\.js$/, '.ts')} (${Date.now() - start}ms)`
|
||||
`${file} -> ${file.replace(/\.js$/, '.ts')} (${Date.now() - start}ms)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', err => {
|
||||
process.on('unhandledRejection', (err) => {
|
||||
console.error('Unhandled Rejection:');
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', err => {
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error('Uncaught Exception:');
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
main().catch(err => {
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ const { statSync } = require('fs');
|
||||
const pkg = require('../package');
|
||||
|
||||
function error(command) {
|
||||
console.error('> Error!', command);
|
||||
console.error('> Error:', command);
|
||||
}
|
||||
|
||||
function debug(str) {
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { handleError } from '../../util/error';
|
||||
|
||||
import Client from '../../util/client';
|
||||
import getArgs from '../../util/get-args';
|
||||
import getSubcommand from '../../util/get-subcommand';
|
||||
import logo from '../../util/output/logo';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
import ls from './ls';
|
||||
import rm from './rm';
|
||||
import set from './set';
|
||||
import type Client from '../../util/client';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -26,14 +23,14 @@ const help = () => {
|
||||
|
||||
-h, --help Output usage information
|
||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||
'FILE'
|
||||
'FILE',
|
||||
)} Path to the local ${'`vercel.json`'} file
|
||||
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
|
||||
'DIR'
|
||||
'DIR',
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
'TOKEN',
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
-N, --next Show next page of results
|
||||
@@ -45,23 +42,23 @@ const help = () => {
|
||||
|
||||
${chalk.cyan(
|
||||
`$ ${getPkgName()} alias set ${chalk.underline(
|
||||
'api-ownv3nc9f8.vercel.app'
|
||||
)} ${chalk.underline('my-api.vercel.app')}`
|
||||
'api-ownv3nc9f8.vercel.app',
|
||||
)} ${chalk.underline('my-api.vercel.app')}`,
|
||||
)}
|
||||
|
||||
Custom domains work as alias targets
|
||||
|
||||
${chalk.cyan(
|
||||
`$ ${getPkgName()} alias set ${chalk.underline(
|
||||
'api-ownv3nc9f8.vercel.app'
|
||||
)} ${chalk.underline('my-api.com')}`
|
||||
'api-ownv3nc9f8.vercel.app',
|
||||
)} ${chalk.underline('my-api.com')}`,
|
||||
)}
|
||||
|
||||
${chalk.dim('–')} The subcommand ${chalk.dim(
|
||||
'`set`'
|
||||
'`set`',
|
||||
)} is the default and can be skipped.
|
||||
${chalk.dim('–')} ${chalk.dim(
|
||||
'Protocols'
|
||||
'Protocols',
|
||||
)} in the URLs are unneeded and ignored.
|
||||
`);
|
||||
};
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import table from 'text-table';
|
||||
import Client from '../../util/client';
|
||||
import getAliases from '../../util/alias/get-aliases';
|
||||
import getScope from '../../util/get-scope';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import strlen from '../../util/strlen';
|
||||
import getCommandFlags from '../../util/get-command-flags';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
|
||||
import { Alias } from '../../types';
|
||||
import type Client from '../../util/client';
|
||||
import type { Alias } from '../../types';
|
||||
|
||||
interface Options {
|
||||
'--next'?: number;
|
||||
@@ -18,7 +17,7 @@ interface Options {
|
||||
export default async function ls(
|
||||
client: Client,
|
||||
opts: Options,
|
||||
args: string[]
|
||||
args: string[],
|
||||
) {
|
||||
const { output } = client;
|
||||
const { '--next': nextTimestamp } = opts;
|
||||
@@ -34,8 +33,8 @@ export default async function ls(
|
||||
if (args.length > 0) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
`${getCommandName('alias ls')}`
|
||||
)}`
|
||||
`${getCommandName('alias ls')}`,
|
||||
)}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -46,7 +45,7 @@ export default async function ls(
|
||||
const { aliases, pagination } = await getAliases(
|
||||
client,
|
||||
undefined,
|
||||
nextTimestamp
|
||||
nextTimestamp,
|
||||
);
|
||||
output.log(`aliases found under ${chalk.bold(contextName)} ${lsStamp()}`);
|
||||
console.log(printAliasTable(aliases));
|
||||
@@ -55,8 +54,8 @@ export default async function ls(
|
||||
const flags = getCommandFlags(opts, ['_', '--next']);
|
||||
output.log(
|
||||
`To display the next page run ${getCommandName(
|
||||
`alias ls${flags} --next ${pagination.next}`
|
||||
)}`
|
||||
`alias ls${flags} --next ${pagination.next}`,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,8 +65,8 @@ export default async function ls(
|
||||
function printAliasTable(aliases: Alias[]) {
|
||||
return `${table(
|
||||
[
|
||||
['source', 'url', 'age'].map(header => chalk.gray(header)),
|
||||
...aliases.map(a => [
|
||||
['source', 'url', 'age'].map((header) => chalk.gray(header)),
|
||||
...aliases.map((a) => [
|
||||
// for legacy reasons, we might have situations
|
||||
// where the deployment was deleted and the alias
|
||||
// not collected appropriately, and we need to handle it
|
||||
@@ -80,6 +79,6 @@ function printAliasTable(aliases: Alias[]) {
|
||||
align: ['l', 'l', 'r'],
|
||||
hsep: ' '.repeat(4),
|
||||
stringLength: strlen,
|
||||
}
|
||||
},
|
||||
).replace(/^/gm, ' ')}\n\n`;
|
||||
}
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import table from 'text-table';
|
||||
import Client from '../../util/client';
|
||||
import getScope from '../../util/get-scope';
|
||||
import removeAliasById from '../../util/alias/remove-alias-by-id';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import strlen from '../../util/strlen';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import findAliasByAliasOrId from '../../util/alias/find-alias-by-alias-or-id';
|
||||
|
||||
import { Alias } from '../../types';
|
||||
import { isValidName } from '../../util/is-valid-name';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import type { Alias } from '../../types';
|
||||
import type Client from '../../util/client';
|
||||
|
||||
type Options = {
|
||||
interface Options {
|
||||
'--yes': boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export default async function rm(
|
||||
client: Client,
|
||||
opts: Partial<Options>,
|
||||
args: string[]
|
||||
args: string[],
|
||||
) {
|
||||
const { output } = client;
|
||||
const { contextName } = await getScope(client);
|
||||
@@ -30,8 +29,8 @@ export default async function rm(
|
||||
if (args.length !== 1) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
`${getCommandName('alias rm <alias>')}`
|
||||
)}`
|
||||
`${getCommandName('alias rm <alias>')}`,
|
||||
)}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -51,7 +50,7 @@ export default async function rm(
|
||||
|
||||
if (!alias) {
|
||||
output.error(
|
||||
`Alias not found by "${aliasOrId}" under ${chalk.bold(contextName)}`
|
||||
`Alias not found by "${aliasOrId}" under ${chalk.bold(contextName)}`,
|
||||
);
|
||||
output.log(`Run ${getCommandName('alias ls')} to see your aliases.`);
|
||||
return 1;
|
||||
@@ -66,8 +65,8 @@ export default async function rm(
|
||||
await removeAliasById(client, alias.uid);
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Alias ${chalk.bold(
|
||||
alias.alias
|
||||
)} removed ${removeStamp()}`
|
||||
alias.alias,
|
||||
)} removed ${removeStamp()}`,
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
@@ -88,7 +87,7 @@ async function confirmAliasRemove(client: Client, alias: Alias) {
|
||||
align: ['l', 'l', 'r'],
|
||||
hsep: ' '.repeat(4),
|
||||
stringLength: strlen,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
client.output.log(`The following alias will be removed permanently`);
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import chalk from 'chalk';
|
||||
import { SetDifference } from 'utility-types';
|
||||
import { AliasRecord } from '../../util/alias/create-alias';
|
||||
import { Domain } from '../../types';
|
||||
import { Output } from '../../util/output';
|
||||
import * as ERRORS from '../../util/errors-ts';
|
||||
import assignAlias from '../../util/alias/assign-alias';
|
||||
import Client from '../../util/client';
|
||||
import getDeploymentByIdOrHost from '../../util/deploy/get-deployment-by-id-or-host';
|
||||
import { getDeploymentForAlias } from '../../util/alias/get-deployment-by-alias';
|
||||
import getScope from '../../util/get-scope';
|
||||
import setupDomain from '../../util/domains/setup-domain';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import { isValidName } from '../../util/is-valid-name';
|
||||
import handleCertError from '../../util/certs/handle-cert-error';
|
||||
@@ -17,17 +11,23 @@ import isWildcardAlias from '../../util/alias/is-wildcard-alias';
|
||||
import link from '../../util/output/link';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import toHost from '../../util/to-host';
|
||||
import type setupDomain from '../../util/domains/setup-domain';
|
||||
import type Client from '../../util/client';
|
||||
import type { Output } from '../../util/output';
|
||||
import type { Domain } from '../../types';
|
||||
import type { AliasRecord } from '../../util/alias/create-alias';
|
||||
import type { SetDifference } from 'utility-types';
|
||||
import type { VercelConfig } from '@vercel/client';
|
||||
|
||||
type Options = {
|
||||
interface Options {
|
||||
'--debug': boolean;
|
||||
'--local-config': string;
|
||||
};
|
||||
}
|
||||
|
||||
export default async function set(
|
||||
client: Client,
|
||||
opts: Partial<Options>,
|
||||
args: string[]
|
||||
args: string[],
|
||||
) {
|
||||
const setStamp = stamp();
|
||||
const { output, localConfig } = client;
|
||||
@@ -37,15 +37,15 @@ export default async function set(
|
||||
if (args.length > 2) {
|
||||
output.error(
|
||||
`${getCommandName(
|
||||
`alias <deployment> <target>`
|
||||
)} accepts at most two arguments`
|
||||
`alias <deployment> <target>`,
|
||||
)} accepts at most two arguments`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (args.length >= 1 && !isValidName(args[0])) {
|
||||
output.error(
|
||||
`The provided argument "${args[0]}" is not a valid deployment`
|
||||
`The provided argument "${args[0]}" is not a valid deployment`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -58,8 +58,8 @@ export default async function set(
|
||||
if (args.length === 0) {
|
||||
output.error(
|
||||
`To ship to production, optionally configure your domains (${link(
|
||||
'https://vercel.link/domain-configuration'
|
||||
)}) and run ${getCommandName(`--prod`)}.`
|
||||
'https://vercel.link/domain-configuration',
|
||||
)}) and run ${getCommandName(`--prod`)}.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -75,8 +75,8 @@ export default async function set(
|
||||
opts['--local-config'],
|
||||
user,
|
||||
contextName,
|
||||
localConfig
|
||||
)
|
||||
localConfig,
|
||||
),
|
||||
);
|
||||
|
||||
if (deployment === 1) {
|
||||
@@ -90,7 +90,7 @@ export default async function set(
|
||||
|
||||
if (!deployment) {
|
||||
output.error(
|
||||
`Couldn't find a deployment to alias. Please provide one as an argument.`
|
||||
`Couldn't find a deployment to alias. Please provide one as an argument.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -111,12 +111,12 @@ export default async function set(
|
||||
client,
|
||||
deployment,
|
||||
target,
|
||||
contextName
|
||||
contextName,
|
||||
);
|
||||
|
||||
const handleResult = handleSetupDomainError(
|
||||
output,
|
||||
handleCreateAliasError(output, record)
|
||||
handleCreateAliasError(output, record),
|
||||
);
|
||||
|
||||
if (handleResult === 1) {
|
||||
@@ -125,8 +125,8 @@ export default async function set(
|
||||
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} ${chalk.bold(
|
||||
`${isWildcardAlias(target) ? '' : 'https://'}${handleResult.alias}`
|
||||
)} now points to https://${deployment.url} ${setStamp()}`
|
||||
`${isWildcardAlias(target) ? '' : 'https://'}${handleResult.alias}`,
|
||||
)} now points to https://${deployment.url} ${setStamp()}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ export default async function set(
|
||||
const [deploymentIdOrHost, aliasTarget] = args;
|
||||
const deployment = handleCertError(
|
||||
output,
|
||||
await getDeploymentByIdOrHost(client, contextName, deploymentIdOrHost)
|
||||
await getDeploymentByIdOrHost(client, contextName, deploymentIdOrHost),
|
||||
);
|
||||
|
||||
if (deployment === 1) {
|
||||
@@ -146,8 +146,8 @@ export default async function set(
|
||||
if (deployment instanceof ERRORS.DeploymentNotFound) {
|
||||
output.error(
|
||||
`Failed to find deployment "${deployment.meta.id}" under ${chalk.bold(
|
||||
contextName
|
||||
)}`
|
||||
contextName,
|
||||
)}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -156,7 +156,7 @@ export default async function set(
|
||||
output.error(
|
||||
`No permission to access deployment "${
|
||||
deployment.meta.id
|
||||
}" under ${chalk.bold(deployment.meta.context)}`
|
||||
}" under ${chalk.bold(deployment.meta.context)}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -168,7 +168,7 @@ export default async function set(
|
||||
|
||||
if (deployment === null) {
|
||||
output.error(
|
||||
`Couldn't find a deployment to alias. Please provide one as an argument.`
|
||||
`Couldn't find a deployment to alias. Please provide one as an argument.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -181,11 +181,11 @@ export default async function set(
|
||||
client,
|
||||
deployment,
|
||||
aliasTarget,
|
||||
contextName
|
||||
contextName,
|
||||
);
|
||||
const handleResult = handleSetupDomainError(
|
||||
output,
|
||||
handleCreateAliasError(output, record)
|
||||
handleCreateAliasError(output, record),
|
||||
);
|
||||
if (handleResult === 1) {
|
||||
return 1;
|
||||
@@ -195,8 +195,8 @@ export default async function set(
|
||||
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} ${chalk.bold(
|
||||
`${prefix}${handleResult.alias}`
|
||||
)} now points to https://${deployment.url} ${setStamp()}`
|
||||
`${prefix}${handleResult.alias}`,
|
||||
)} now points to https://${deployment.url} ${setStamp()}`,
|
||||
);
|
||||
|
||||
return 0;
|
||||
@@ -208,13 +208,13 @@ type SetupDomainError = Exclude<SetupDomainResolve, Domain>;
|
||||
|
||||
function handleSetupDomainError<T>(
|
||||
output: Output,
|
||||
error: SetupDomainError | T
|
||||
error: SetupDomainError | T,
|
||||
): T | 1 {
|
||||
if (error instanceof ERRORS.DomainPermissionDenied) {
|
||||
output.error(
|
||||
`You don't have permissions over domain ${chalk.underline(
|
||||
error.meta.domain
|
||||
)} under ${chalk.bold(error.meta.context)}.`
|
||||
error.meta.domain,
|
||||
)} under ${chalk.bold(error.meta.context)}.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -231,28 +231,28 @@ function handleSetupDomainError<T>(
|
||||
|
||||
if (error instanceof ERRORS.UnsupportedTLD) {
|
||||
output.error(
|
||||
`The TLD for domain name ${error.meta.domain} is not supported.`
|
||||
`The TLD for domain name ${error.meta.domain} is not supported.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (error instanceof ERRORS.InvalidDomain) {
|
||||
output.error(
|
||||
`The domain ${error.meta.domain} used for the alias is not valid.`
|
||||
`The domain ${error.meta.domain} used for the alias is not valid.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (error instanceof ERRORS.DomainNotAvailable) {
|
||||
output.error(
|
||||
`The domain ${error.meta.domain} is not available to be purchased.`
|
||||
`The domain ${error.meta.domain} is not available to be purchased.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (error instanceof ERRORS.DomainServiceNotAvailable) {
|
||||
output.error(
|
||||
`The domain purchase service is not available. Try again later.`
|
||||
`The domain purchase service is not available. Try again later.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -264,24 +264,24 @@ function handleSetupDomainError<T>(
|
||||
|
||||
if (error instanceof ERRORS.DomainAlreadyExists) {
|
||||
output.error(
|
||||
`The domain ${error.meta.domain} exists for a different account.`
|
||||
`The domain ${error.meta.domain} exists for a different account.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (error instanceof ERRORS.DomainPurchasePending) {
|
||||
output.error(
|
||||
`The domain ${error.meta.domain} is processing and will be available once the order is completed.`
|
||||
`The domain ${error.meta.domain} is processing and will be available once the order is completed.`,
|
||||
);
|
||||
output.print(
|
||||
` An email will be sent upon completion so you can alias to your new domain.\n`
|
||||
` An email will be sent upon completion so you can alias to your new domain.\n`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (error instanceof ERRORS.SourceNotFound) {
|
||||
output.error(
|
||||
`You can't purchase the domain you're aliasing to since you have no valid payment method.`
|
||||
`You can't purchase the domain you're aliasing to since you have no valid payment method.`,
|
||||
);
|
||||
output.print(` Please add a valid payment method and retry.\n`);
|
||||
return 1;
|
||||
@@ -289,7 +289,7 @@ function handleSetupDomainError<T>(
|
||||
|
||||
if (error instanceof ERRORS.DomainPaymentError) {
|
||||
output.error(
|
||||
`You can't purchase the domain you're aliasing to since your card was declined.`
|
||||
`You can't purchase the domain you're aliasing to since your card was declined.`,
|
||||
);
|
||||
output.print(` Please add a valid payment method and retry.\n`);
|
||||
return 1;
|
||||
@@ -307,7 +307,7 @@ type RemainingAssignAliasErrors = SetDifference<
|
||||
|
||||
function handleCreateAliasError<T>(
|
||||
output: Output,
|
||||
errorOrResult: RemainingAssignAliasErrors | T
|
||||
errorOrResult: RemainingAssignAliasErrors | T,
|
||||
): 1 | T {
|
||||
const error = handleCertError(output, errorOrResult);
|
||||
if (error === 1) {
|
||||
@@ -317,8 +317,8 @@ function handleCreateAliasError<T>(
|
||||
if (error instanceof ERRORS.AliasInUse) {
|
||||
output.error(
|
||||
`The alias ${chalk.dim(
|
||||
error.meta.alias
|
||||
)} is a deployment URL or it's in use by a different team.`
|
||||
error.meta.alias,
|
||||
)} is a deployment URL or it's in use by a different team.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -326,41 +326,41 @@ function handleCreateAliasError<T>(
|
||||
if (error instanceof ERRORS.DeploymentNotFound) {
|
||||
output.error(
|
||||
`Failed to find deployment ${chalk.dim(error.meta.id)} under ${chalk.bold(
|
||||
error.meta.context
|
||||
)}`
|
||||
error.meta.context,
|
||||
)}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
if (error instanceof ERRORS.InvalidAlias) {
|
||||
output.error(
|
||||
`Invalid alias. Please confirm that the alias you provided is a valid hostname. Note: For \`vercel.app\`, only sub and sub-sub domains are supported.`
|
||||
`Invalid alias. Please confirm that the alias you provided is a valid hostname. Note: For \`vercel.app\`, only sub and sub-sub domains are supported.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
if (error instanceof ERRORS.DeploymentPermissionDenied) {
|
||||
output.error(
|
||||
`No permission to access deployment ${chalk.dim(
|
||||
error.meta.id
|
||||
)} under ${chalk.bold(error.meta.context)}`
|
||||
error.meta.id,
|
||||
)} under ${chalk.bold(error.meta.context)}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (error instanceof ERRORS.CertMissing) {
|
||||
output.error(
|
||||
`There is no certificate for the domain ${error.meta.domain} and it could not be created.`
|
||||
`There is no certificate for the domain ${error.meta.domain} and it could not be created.`,
|
||||
);
|
||||
output.log(
|
||||
`Please generate a new certificate manually with ${getCommandName(
|
||||
`certs issue ${error.meta.domain}`
|
||||
)}`
|
||||
`certs issue ${error.meta.domain}`,
|
||||
)}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (error instanceof ERRORS.InvalidDomain) {
|
||||
output.error(
|
||||
`The domain ${error.meta.domain} used for the alias is not valid.`
|
||||
`The domain ${error.meta.domain} used for the alias is not valid.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -385,8 +385,8 @@ function handleCreateAliasError<T>(
|
||||
function getTargetsForAlias(args: string[], { alias }: VercelConfig = {}) {
|
||||
if (args.length) {
|
||||
return [args[args.length - 1]]
|
||||
.map(target => (target.indexOf('.') !== -1 ? toHost(target) : target))
|
||||
.filter((x): x is string => !!x && typeof x === 'string');
|
||||
.map((target) => (target.includes('.') ? toHost(target) : target))
|
||||
.filter((x): x is string => Boolean(x) && typeof x === 'string');
|
||||
}
|
||||
|
||||
if (!alias) {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { resolve } from 'path';
|
||||
import { URLSearchParams, parse } from 'url';
|
||||
import open from 'open';
|
||||
import boxen from 'boxen';
|
||||
import execa from 'execa';
|
||||
import plural from 'pluralize';
|
||||
import inquirer from 'inquirer';
|
||||
import { resolve } from 'path';
|
||||
import chalk, { Chalk } from 'chalk';
|
||||
import { URLSearchParams, parse } from 'url';
|
||||
|
||||
import chalk from 'chalk';
|
||||
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 '../../types';
|
||||
import { normalizeURL } from '../../util/bisect/normalize-url';
|
||||
import type { Chalk } from 'chalk';
|
||||
import type Client from '../../util/client';
|
||||
import type { Deployment, PaginationOptions } from '../../types';
|
||||
|
||||
interface DeploymentV6
|
||||
extends Pick<
|
||||
@@ -108,8 +108,8 @@ export default async function main(client: Client): Promise<number> {
|
||||
if (subpath && subpath !== parsed.path) {
|
||||
output.note(
|
||||
`Ignoring subpath ${chalk.bold(
|
||||
parsed.path
|
||||
)} in favor of \`--path\` argument ${chalk.bold(subpath)}`
|
||||
parsed.path,
|
||||
)} in favor of \`--path\` argument ${chalk.bold(subpath)}`,
|
||||
);
|
||||
} else {
|
||||
subpath = parsed.path;
|
||||
@@ -131,22 +131,22 @@ export default async function main(client: Client): Promise<number> {
|
||||
) {
|
||||
output.note(
|
||||
`Ignoring subpath ${chalk.bold(
|
||||
parsed.path
|
||||
)} which does not match ${chalk.bold(subpath)}`
|
||||
parsed.path,
|
||||
)} which does not match ${chalk.bold(subpath)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!subpath) {
|
||||
subpath = await prompt(
|
||||
client,
|
||||
`Specify the URL subpath where the bug occurs:`
|
||||
`Specify the URL subpath where the bug occurs:`,
|
||||
);
|
||||
}
|
||||
|
||||
output.spinner('Retrieving deployments…');
|
||||
|
||||
// `getDeployment` cannot be parallelized because it might prompt for login
|
||||
const badDeployment = await getDeployment(client, bad).catch(err => err);
|
||||
const badDeployment = await getDeployment(client, bad).catch((err) => err);
|
||||
|
||||
if (badDeployment) {
|
||||
if (badDeployment instanceof Error) {
|
||||
@@ -161,7 +161,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
}
|
||||
|
||||
// `getDeployment` cannot be parallelized because it might prompt for login
|
||||
const goodDeployment = await getDeployment(client, good).catch(err => err);
|
||||
const goodDeployment = await getDeployment(client, good).catch((err) => err);
|
||||
|
||||
if (goodDeployment) {
|
||||
if (goodDeployment instanceof Error) {
|
||||
@@ -172,7 +172,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
good = goodDeployment.url;
|
||||
} else {
|
||||
output.error(
|
||||
`Failed to retrieve ${chalk.bold('good')} Deployment: ${good}`
|
||||
`Failed to retrieve ${chalk.bold('good')} Deployment: ${good}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -195,7 +195,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
badDeployment.target || 'preview'
|
||||
}" does not match good deployment target "${
|
||||
goodDeployment.target || 'preview'
|
||||
}"`
|
||||
}"`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -242,7 +242,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
|
||||
if (!deployments.length) {
|
||||
output.error(
|
||||
'Cannot bisect because this project does not have any deployments'
|
||||
'Cannot bisect because this project does not have any deployments',
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -262,10 +262,10 @@ export default async function main(client: Client): Promise<number> {
|
||||
output.log(
|
||||
chalk.magenta(
|
||||
`${chalk.bold(
|
||||
'Bisecting:'
|
||||
)} ${rem} left to test after this (roughly ${pSteps})`
|
||||
'Bisecting:',
|
||||
)} ${rem} left to test after this (roughly ${pSteps})`,
|
||||
),
|
||||
chalk.magenta
|
||||
chalk.magenta,
|
||||
);
|
||||
const testUrl = `https://${deployment.url}${subpath}`;
|
||||
output.log(`${chalk.bold('Deployment URL:')} ${link(testUrl)}`);
|
||||
@@ -309,8 +309,8 @@ export default async function main(client: Client): Promise<number> {
|
||||
}
|
||||
output.log(
|
||||
`Run script returned exit code ${chalk.bold(String(exitCode))}: ${color(
|
||||
action
|
||||
)}`
|
||||
action,
|
||||
)}`,
|
||||
);
|
||||
} else {
|
||||
if (openEnabled) {
|
||||
@@ -341,9 +341,9 @@ export default async function main(client: Client): Promise<number> {
|
||||
|
||||
output.print('\n');
|
||||
|
||||
let result = [
|
||||
const result = [
|
||||
chalk.bold(
|
||||
`The first bad deployment is: ${link(`https://${lastBad.url}`)}`
|
||||
`The first bad deployment is: ${link(`https://${lastBad.url}`)}`,
|
||||
),
|
||||
'',
|
||||
` ${chalk.bold('Date:')} ${formatDate(lastBad.createdAt)}`,
|
||||
@@ -366,7 +366,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
|
||||
function getDeployment(
|
||||
client: Client,
|
||||
hostname: string
|
||||
hostname: string,
|
||||
): Promise<DeploymentV6> {
|
||||
const query = new URLSearchParams();
|
||||
query.set('url', hostname);
|
||||
@@ -377,14 +377,14 @@ function getDeployment(
|
||||
|
||||
function getCommit(deployment: DeploymentV6) {
|
||||
const sha =
|
||||
deployment.meta?.githubCommitSha ||
|
||||
deployment.meta?.gitlabCommitSha ||
|
||||
deployment.meta?.bitbucketCommitSha;
|
||||
deployment.meta.githubCommitSha ||
|
||||
deployment.meta.gitlabCommitSha ||
|
||||
deployment.meta.bitbucketCommitSha;
|
||||
if (!sha) return null;
|
||||
const message =
|
||||
deployment.meta?.githubCommitMessage ||
|
||||
deployment.meta?.gitlabCommitMessage ||
|
||||
deployment.meta?.bitbucketCommitMessage;
|
||||
deployment.meta.githubCommitMessage ||
|
||||
deployment.meta.gitlabCommitMessage ||
|
||||
deployment.meta.bitbucketCommitMessage;
|
||||
return { sha, message };
|
||||
}
|
||||
|
||||
@@ -398,8 +398,7 @@ async function prompt(client: Client, message: string): Promise<string> {
|
||||
});
|
||||
if (val) {
|
||||
return val;
|
||||
} else {
|
||||
client.output.error('A value must be specified');
|
||||
}
|
||||
client.output.error('A value must be specified');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,41 @@
|
||||
import fs from 'fs-extra';
|
||||
import chalk from 'chalk';
|
||||
import dotenv from 'dotenv';
|
||||
import { join, normalize, relative, resolve } from 'path';
|
||||
import dotenv from 'dotenv';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import { normalizePath, FileFsRef, NowBuildError } from '@vercel/build-utils';
|
||||
import { detectBuilders } from '@vercel/fs-detectors';
|
||||
import minimatch from 'minimatch';
|
||||
import {
|
||||
normalizePath,
|
||||
appendRoutesToPhase,
|
||||
getTransformedRoutes,
|
||||
mergeRoutes,
|
||||
} from '@vercel/routing-utils';
|
||||
import { staticFiles as getFiles } from '../util/get-files';
|
||||
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,
|
||||
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';
|
||||
import { OUTPUT_DIR, 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 pull from './pull';
|
||||
import type { PathOverride } from '../util/build/write-build-result';
|
||||
import type { ProjectLinkAndSettings } from '../util/projects/project-settings';
|
||||
import type Client from '../util/client';
|
||||
import type {
|
||||
Files,
|
||||
FileFsRef,
|
||||
PackageJson,
|
||||
BuildOptions,
|
||||
Config,
|
||||
@@ -14,46 +44,10 @@ import {
|
||||
BuildResultV2,
|
||||
BuildResultV2Typical,
|
||||
BuildResultV3,
|
||||
NowBuildError,
|
||||
} from '@vercel/build-utils';
|
||||
import { detectBuilders } from '@vercel/fs-detectors';
|
||||
import minimatch from 'minimatch';
|
||||
import {
|
||||
appendRoutesToPhase,
|
||||
getTransformedRoutes,
|
||||
mergeRoutes,
|
||||
MergeRoutesProps,
|
||||
Route,
|
||||
} from '@vercel/routing-utils';
|
||||
import type { MergeRoutesProps, Route } from '@vercel/routing-utils';
|
||||
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 {
|
||||
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';
|
||||
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';
|
||||
|
||||
type BuildResult = BuildResultV2 | BuildResultV3;
|
||||
|
||||
interface SerializedBuilder extends Builder {
|
||||
@@ -82,10 +76,10 @@ const help = () => {
|
||||
|
||||
-h, --help Output usage information
|
||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||
'FILE'
|
||||
'FILE',
|
||||
)} Path to the local ${'`vercel.json`'} file
|
||||
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
|
||||
'DIR'
|
||||
'DIR',
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
--cwd [path] The current working directory
|
||||
--output [path] Directory where built assets should be written to
|
||||
@@ -109,18 +103,17 @@ export default async function main(client: Client): Promise<number> {
|
||||
if (process.env.__VERCEL_BUILD_RUNNING) {
|
||||
output.error(
|
||||
`${cmd(
|
||||
`${cli.name} build`
|
||||
`${cli.name} build`,
|
||||
)} must not recursively invoke itself. Check the Build Command in the Project Settings or the ${cmd(
|
||||
'build'
|
||||
)} script in ${cmd('package.json')}`
|
||||
'build',
|
||||
)} script in ${cmd('package.json')}`,
|
||||
);
|
||||
output.error(
|
||||
`Learn More: https://vercel.link/recursive-invocation-of-commands`
|
||||
`Learn More: https://vercel.link/recursive-invocation-of-commands`,
|
||||
);
|
||||
return 1;
|
||||
} else {
|
||||
process.env.__VERCEL_BUILD_RUNNING = '1';
|
||||
}
|
||||
process.env.__VERCEL_BUILD_RUNNING = '1';
|
||||
|
||||
// Parse CLI args
|
||||
const argv = getArgs(client.argv.slice(2), {
|
||||
@@ -156,8 +149,8 @@ export default async function main(client: Client): Promise<number> {
|
||||
if (!isTTY) {
|
||||
client.output.print(
|
||||
`No Project Settings found locally. Run ${cli.getCommandName(
|
||||
'pull --yes'
|
||||
)} to retrieve them.`
|
||||
'pull --yes',
|
||||
)} to retrieve them.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -165,9 +158,9 @@ export default async function main(client: Client): Promise<number> {
|
||||
confirmed = await confirm(
|
||||
client,
|
||||
`No Project Settings found locally. Run ${cli.getCommandName(
|
||||
'pull'
|
||||
'pull',
|
||||
)} for retrieving them?`,
|
||||
true
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (!confirmed) {
|
||||
@@ -212,7 +205,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
});
|
||||
if (dotenvResult.error) {
|
||||
output.debug(
|
||||
`Failed loading environment variables: ${dotenvResult.error}`
|
||||
`Failed loading environment variables: ${dotenvResult.error}`,
|
||||
);
|
||||
} else if (dotenvResult.parsed) {
|
||||
for (const key of Object.keys(dotenvResult.parsed)) {
|
||||
@@ -263,7 +256,7 @@ async function doBuild(
|
||||
project: ProjectLinkAndSettings,
|
||||
buildsJson: BuildsManifest,
|
||||
cwd: string,
|
||||
outputDir: string
|
||||
outputDir: string,
|
||||
): Promise<number> {
|
||||
const { output } = client;
|
||||
const workPath = join(cwd, project.settings.rootDirectory || '.');
|
||||
@@ -272,15 +265,21 @@ async function doBuild(
|
||||
const [pkg, vercelConfig] = await Promise.all([
|
||||
readJSONFile<PackageJson>(join(workPath, 'package.json')),
|
||||
readJSONFile<VercelConfig>(join(workPath, 'vercel.json')).then(
|
||||
config => config || readJSONFile<VercelConfig>(join(workPath, 'now.json'))
|
||||
(config) =>
|
||||
config || readJSONFile<VercelConfig>(join(workPath, 'now.json')),
|
||||
),
|
||||
]);
|
||||
if (pkg instanceof CantParseJSONFile) throw pkg;
|
||||
if (vercelConfig instanceof CantParseJSONFile) throw vercelConfig;
|
||||
|
||||
const projectSettings = {
|
||||
...project.settings,
|
||||
...pickOverrides(vercelConfig || {}),
|
||||
};
|
||||
|
||||
// Get a list of source files
|
||||
const files = (await getFiles(workPath, client)).map(f =>
|
||||
normalizePath(relative(workPath, f))
|
||||
const files = (await getFiles(workPath, client)).map((f) =>
|
||||
normalizePath(relative(workPath, f)),
|
||||
);
|
||||
|
||||
const routesResult = getTransformedRoutes(vercelConfig || {});
|
||||
@@ -303,9 +302,9 @@ async function doBuild(
|
||||
|
||||
if (builds.length > 0) {
|
||||
output.warn(
|
||||
'Due to `builds` existing in your configuration file, the Build and Development Settings defined in your Project Settings will not apply. Learn More: https://vercel.link/unused-build-settings'
|
||||
'Due to `builds` existing in your configuration file, the Build and Development Settings defined in your Project Settings will not apply. Learn More: https://vercel.link/unused-build-settings',
|
||||
);
|
||||
builds = builds.map(b => expandBuild(files, b)).flat();
|
||||
builds = builds.map((b) => expandBuild(files, b)).flat();
|
||||
} else {
|
||||
// Zero config
|
||||
isZeroConfig = true;
|
||||
@@ -313,7 +312,7 @@ async function doBuild(
|
||||
// Detect the Vercel Builders that will need to be invoked
|
||||
const detectedBuilders = await detectBuilders(files, pkg, {
|
||||
...vercelConfig,
|
||||
projectSettings: project.settings,
|
||||
projectSettings,
|
||||
ignoreBuildScript: true,
|
||||
featHandleMiss: true,
|
||||
});
|
||||
@@ -324,7 +323,7 @@ async function doBuild(
|
||||
|
||||
for (const w of detectedBuilders.warnings) {
|
||||
console.log(
|
||||
`Warning: ${w.message} ${w.action || 'Learn More'}: ${w.link}`
|
||||
`Warning: ${w.message} ${w.action || 'Learn More'}: ${w.link}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -340,7 +339,7 @@ async function doBuild(
|
||||
routes: [],
|
||||
newRoutes: detectedBuilders.rewriteRoutes,
|
||||
phase: 'filesystem',
|
||||
})
|
||||
}),
|
||||
);
|
||||
zeroConfigRoutes = appendRoutesToPhase({
|
||||
routes: zeroConfigRoutes,
|
||||
@@ -350,7 +349,7 @@ async function doBuild(
|
||||
zeroConfigRoutes.push(...(detectedBuilders.defaultRoutes || []));
|
||||
}
|
||||
|
||||
const builderSpecs = new Set(builds.map(b => b.use));
|
||||
const builderSpecs = new Set(builds.map((b) => b.use));
|
||||
|
||||
const buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
|
||||
|
||||
@@ -371,7 +370,7 @@ async function doBuild(
|
||||
|
||||
// Write the `detectedBuilders` result to output dir
|
||||
const buildsJsonBuilds = new Map<Builder, SerializedBuilder>(
|
||||
builds.map(build => {
|
||||
builds.map((build) => {
|
||||
const builderWithPkg = buildersWithPkgs.get(build.use);
|
||||
if (!builderWithPkg) {
|
||||
throw new Error(`Failed to load Builder "${build.use}"`);
|
||||
@@ -386,7 +385,7 @@ async function doBuild(
|
||||
...build,
|
||||
},
|
||||
];
|
||||
})
|
||||
}),
|
||||
);
|
||||
buildsJson.builds = Array.from(buildsJsonBuilds.values());
|
||||
const buildsJsonPath = join(outputDir, 'builds.json');
|
||||
@@ -425,14 +424,14 @@ async function doBuild(
|
||||
|
||||
const buildConfig: Config = isZeroConfig
|
||||
? {
|
||||
outputDirectory: project.settings.outputDirectory ?? undefined,
|
||||
outputDirectory: projectSettings.outputDirectory ?? undefined,
|
||||
...build.config,
|
||||
projectSettings: project.settings,
|
||||
installCommand: project.settings.installCommand ?? undefined,
|
||||
devCommand: project.settings.devCommand ?? undefined,
|
||||
buildCommand: project.settings.buildCommand ?? undefined,
|
||||
framework: project.settings.framework,
|
||||
nodeVersion: project.settings.nodeVersion,
|
||||
projectSettings,
|
||||
installCommand: projectSettings.installCommand ?? undefined,
|
||||
devCommand: projectSettings.devCommand ?? undefined,
|
||||
buildCommand: projectSettings.buildCommand ?? undefined,
|
||||
framework: projectSettings.framework,
|
||||
nodeVersion: projectSettings.nodeVersion,
|
||||
}
|
||||
: build.config || {};
|
||||
const buildOptions: BuildOptions = {
|
||||
@@ -444,7 +443,7 @@ async function doBuild(
|
||||
meta,
|
||||
};
|
||||
output.debug(
|
||||
`Building entrypoint "${build.src}" with "${builderPkg.name}"`
|
||||
`Building entrypoint "${build.src}" with "${builderPkg.name}"`,
|
||||
);
|
||||
const buildResult = await builder.build(buildOptions);
|
||||
|
||||
@@ -460,13 +459,13 @@ async function doBuild(
|
||||
build,
|
||||
builder,
|
||||
builderPkg,
|
||||
vercelConfig
|
||||
vercelConfig,
|
||||
).then(
|
||||
override => {
|
||||
(override) => {
|
||||
if (override) overrides.push(override);
|
||||
},
|
||||
err => err
|
||||
)
|
||||
(err) => err,
|
||||
),
|
||||
);
|
||||
} catch (err: any) {
|
||||
output.prettyError(err);
|
||||
@@ -474,7 +473,7 @@ async function doBuild(
|
||||
const writeConfigJsonPromise = fs.writeJSON(
|
||||
join(outputDir, 'config.json'),
|
||||
{ version: 3 },
|
||||
{ spaces: 2 }
|
||||
{ spaces: 2 },
|
||||
);
|
||||
|
||||
await Promise.all([writeBuildsJsonPromise, writeConfigJsonPromise]);
|
||||
@@ -527,10 +526,10 @@ async function doBuild(
|
||||
}
|
||||
|
||||
const builderRoutes: MergeRoutesProps['builds'] = Array.from(
|
||||
buildResults.entries()
|
||||
buildResults.entries(),
|
||||
)
|
||||
.filter(b => 'routes' in b[1] && Array.isArray(b[1].routes))
|
||||
.map(b => {
|
||||
.filter((b) => 'routes' in b[1] && Array.isArray(b[1].routes))
|
||||
.map((b) => {
|
||||
return {
|
||||
use: b[0].use,
|
||||
entrypoint: b[0].src!,
|
||||
@@ -549,7 +548,150 @@ async function doBuild(
|
||||
builds: builderRoutes,
|
||||
});
|
||||
|
||||
const mergedImages = mergeImages(buildResults.values());
|
||||
const images = vercelConfig?.images;
|
||||
if (images) {
|
||||
if (typeof images !== 'object') {
|
||||
throw new Error(
|
||||
`vercel.json "images" should be an object received ${typeof images}.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!Array.isArray(images.domains)) {
|
||||
throw new Error(
|
||||
`vercel.json "images.domains" should be an Array received ${typeof images.domains}.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (images.domains.length > 50) {
|
||||
throw new Error(
|
||||
`vercel.json "images.domains" exceeds length of 50 received length (${images.domains.length}).`,
|
||||
);
|
||||
}
|
||||
|
||||
const invalidImageDomains = images.domains.filter(
|
||||
(d: unknown) => typeof d !== 'string',
|
||||
);
|
||||
if (invalidImageDomains.length > 0) {
|
||||
throw new Error(
|
||||
`vercel.json "images.domains" should be an Array of strings received invalid values (${invalidImageDomains.join(
|
||||
', ',
|
||||
)}).`,
|
||||
);
|
||||
}
|
||||
|
||||
if (images.remotePatterns) {
|
||||
if (!Array.isArray(images.remotePatterns)) {
|
||||
throw new Error(
|
||||
`vercel.json "images.remotePatterns" should be an Array received ${typeof images.remotePatterns}.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (images.remotePatterns.length > 50) {
|
||||
throw new Error(
|
||||
`vercel.json "images.remotePatterns" exceeds length of 50, received length (${images.remotePatterns.length}).`,
|
||||
);
|
||||
}
|
||||
|
||||
const validProps = new Set(['protocol', 'hostname', 'pathname', 'port']);
|
||||
const requiredProps = ['hostname'];
|
||||
const invalidPatterns = images.remotePatterns.filter(
|
||||
(d: unknown) =>
|
||||
!d ||
|
||||
typeof d !== 'object' ||
|
||||
Object.entries(d).some(
|
||||
([k, v]) => !validProps.has(k) || typeof v !== 'string',
|
||||
) ||
|
||||
requiredProps.some((k) => !(k in d)),
|
||||
);
|
||||
if (invalidPatterns.length > 0) {
|
||||
throw new Error(
|
||||
`vercel.json "images.remotePatterns" received invalid values:\n${invalidPatterns
|
||||
.map((item) => JSON.stringify(item))
|
||||
.join(
|
||||
'\n',
|
||||
)}\n\nremotePatterns value must follow format { protocol: 'https', hostname: 'example.com', port: '', pathname: '/imgs/**' }.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Array.isArray(images.sizes)) {
|
||||
throw new Error(
|
||||
`vercel.json "images.sizes" should be an Array received ${typeof images.sizes}.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (images.sizes.length < 1 || images.sizes.length > 50) {
|
||||
throw new Error(
|
||||
`vercel.json "images.sizes" should be an Array of length between 1 to 50 received length (${images.sizes.length}).`,
|
||||
);
|
||||
}
|
||||
|
||||
const invalidImageSizes = images.sizes.filter((d: unknown) => {
|
||||
return typeof d !== 'number' || d < 1 || d > 10000;
|
||||
});
|
||||
if (invalidImageSizes.length > 0) {
|
||||
throw new Error(
|
||||
`vercel.json "images.sizes" should be an Array of numbers that are between 1 and 10000, received invalid values (${invalidImageSizes.join(
|
||||
', ',
|
||||
)}).`,
|
||||
);
|
||||
}
|
||||
|
||||
if (images.minimumCacheTTL) {
|
||||
if (
|
||||
!Number.isInteger(images.minimumCacheTTL) ||
|
||||
images.minimumCacheTTL < 0
|
||||
) {
|
||||
throw new Error(
|
||||
`vercel.json "images.minimumCacheTTL" should be an integer 0 or more received (${images.minimumCacheTTL}).`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (images.formats) {
|
||||
if (!Array.isArray(images.formats)) {
|
||||
throw new Error(
|
||||
`vercel.json "images.formats" should be an Array received ${typeof images.formats}.`,
|
||||
);
|
||||
}
|
||||
if (images.formats.length < 1 || images.formats.length > 2) {
|
||||
throw new Error(
|
||||
`vercel.json "images.formats" must be length 1 or 2, received length (${images.formats.length}).`,
|
||||
);
|
||||
}
|
||||
|
||||
const invalid = images.formats.filter((f) => {
|
||||
return f !== 'image/avif' && f !== 'image/webp';
|
||||
});
|
||||
if (invalid.length > 0) {
|
||||
throw new Error(
|
||||
`vercel.json "images.formats" should be an Array of mime type strings, received invalid values (${invalid.join(
|
||||
', ',
|
||||
)}).`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
typeof images.dangerouslyAllowSVG !== 'undefined' &&
|
||||
typeof images.dangerouslyAllowSVG !== 'boolean'
|
||||
) {
|
||||
throw new Error(
|
||||
`vercel.json "images.dangerouslyAllowSVG" should be a boolean received (${images.dangerouslyAllowSVG}).`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
typeof images.contentSecurityPolicy !== 'undefined' &&
|
||||
typeof images.contentSecurityPolicy !== 'string'
|
||||
) {
|
||||
throw new Error(
|
||||
`vercel.json "images.contentSecurityPolicy" should be a string received ${images.contentSecurityPolicy}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mergedImages = mergeImages(images, buildResults.values());
|
||||
const mergedWildcard = mergeWildcard(buildResults.values());
|
||||
const mergedOverrides: Record<string, PathOverride> =
|
||||
overrides.length > 0 ? Object.assign({}, ...overrides) : undefined;
|
||||
@@ -570,10 +712,10 @@ async function doBuild(
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`Build Completed in ${chalk.bold(
|
||||
relOutputDir.startsWith('..') ? outputDir : relOutputDir
|
||||
relOutputDir.startsWith('..') ? outputDir : relOutputDir,
|
||||
)} ${chalk.gray(buildStamp())}`,
|
||||
emoji('success')
|
||||
)}\n`
|
||||
emoji('success'),
|
||||
)}\n`,
|
||||
);
|
||||
|
||||
return 0;
|
||||
@@ -599,17 +741,17 @@ function expandBuild(files: string[], build: Builder): Builder[] {
|
||||
});
|
||||
}
|
||||
|
||||
if (src[0] === '/') {
|
||||
if (src.startsWith('/')) {
|
||||
// Remove a leading slash so that the globbing is relative
|
||||
// to `cwd` instead of the root of the filesystem.
|
||||
src = src.substring(1);
|
||||
}
|
||||
|
||||
const matches = files.filter(
|
||||
name => name === src || minimatch(name, src, { dot: true })
|
||||
(name) => name === src || minimatch(name, src, { dot: true }),
|
||||
);
|
||||
|
||||
return matches.map(m => {
|
||||
return matches.map((m) => {
|
||||
return {
|
||||
...build,
|
||||
src: m,
|
||||
@@ -618,21 +760,21 @@ function expandBuild(files: string[], build: Builder): Builder[] {
|
||||
}
|
||||
|
||||
function mergeImages(
|
||||
buildResults: Iterable<BuildResult>
|
||||
images: BuildResultV2Typical['images'],
|
||||
buildResults: Iterable<BuildResult>,
|
||||
): BuildResultV2Typical['images'] {
|
||||
let images: BuildResultV2Typical['images'] = undefined;
|
||||
for (const result of buildResults) {
|
||||
if ('images' in result && result.images) {
|
||||
images = Object.assign({}, images, result.images);
|
||||
images = { ...images, ...result.images };
|
||||
}
|
||||
}
|
||||
return images;
|
||||
}
|
||||
|
||||
function mergeWildcard(
|
||||
buildResults: Iterable<BuildResult>
|
||||
buildResults: Iterable<BuildResult>,
|
||||
): BuildResultV2Typical['wildcard'] {
|
||||
let wildcard: BuildResultV2Typical['wildcard'] = undefined;
|
||||
let wildcard: BuildResultV2Typical['wildcard'];
|
||||
for (const result of buildResults) {
|
||||
if ('wildcard' in result && result.wildcard) {
|
||||
if (!wildcard) wildcard = [];
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import chalk from 'chalk';
|
||||
import Client from '../../util/client';
|
||||
import getScope from '../../util/get-scope';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import createCertFromFile from '../../util/certs/create-cert-from-file';
|
||||
import createCertForCns from '../../util/certs/create-cert-for-cns';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { Cert } from '../../types';
|
||||
import type Client from '../../util/client';
|
||||
import type { Cert } from '../../types';
|
||||
|
||||
interface Options {
|
||||
'--overwrite'?: boolean;
|
||||
@@ -17,7 +17,7 @@ interface Options {
|
||||
async function add(
|
||||
client: Client,
|
||||
opts: Options,
|
||||
args: string[]
|
||||
args: string[],
|
||||
): Promise<number> {
|
||||
const { output } = client;
|
||||
const addStamp = stamp();
|
||||
@@ -41,14 +41,14 @@ async function add(
|
||||
if (crtPath || keyPath || caPath) {
|
||||
if (args.length !== 0 || !crtPath || !keyPath || !caPath) {
|
||||
output.error(
|
||||
`Invalid number of arguments to create a custom certificate entry. Usage:`
|
||||
`Invalid number of arguments to create a custom certificate entry. Usage:`,
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.cyan(
|
||||
`${getCommandName(
|
||||
'certs add --crt <domain.crt> --key <domain.key> --ca <ca.crt>'
|
||||
)}`
|
||||
)}\n`
|
||||
'certs add --crt <domain.crt> --key <domain.key> --ca <ca.crt>',
|
||||
)}`,
|
||||
)}\n`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -58,18 +58,18 @@ async function add(
|
||||
} else {
|
||||
output.warn(
|
||||
`${chalk.cyan(
|
||||
getCommandName('certs add')
|
||||
getCommandName('certs add'),
|
||||
)} will be soon deprecated. Please use ${chalk.cyan(
|
||||
getCommandName('certs issue <cn> <cns>')
|
||||
)} instead`
|
||||
getCommandName('certs issue <cn> <cns>'),
|
||||
)} instead`,
|
||||
);
|
||||
|
||||
if (args.length < 1) {
|
||||
output.error(
|
||||
`Invalid number of arguments to create a custom certificate entry. Usage:`
|
||||
`Invalid number of arguments to create a custom certificate entry. Usage:`,
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.cyan(getCommandName('certs add <cn>[, <cn>]'))}\n`
|
||||
` ${chalk.cyan(getCommandName('certs add <cn>[, <cn>]'))}\n`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -77,10 +77,10 @@ async function add(
|
||||
// Create the certificate from the given array of CNs
|
||||
const cns = args.reduce<string[]>(
|
||||
(res, item) => res.concat(item.split(',')),
|
||||
[]
|
||||
[],
|
||||
);
|
||||
output.spinner(
|
||||
`Generating a certificate for ${chalk.bold(cns.join(', '))}`
|
||||
`Generating a certificate for ${chalk.bold(cns.join(', '))}`,
|
||||
);
|
||||
|
||||
cert = await createCertForCns(client, cns, contextName);
|
||||
@@ -90,14 +90,13 @@ async function add(
|
||||
if (cert instanceof Error) {
|
||||
output.error(cert.message);
|
||||
return 1;
|
||||
} else {
|
||||
// Print success message
|
||||
output.success(
|
||||
`Certificate entry for ${chalk.bold(
|
||||
cert.cns.join(', ')
|
||||
)} created ${addStamp()}`
|
||||
);
|
||||
}
|
||||
// Print success message
|
||||
output.success(
|
||||
`Certificate entry for ${chalk.bold(
|
||||
cert.cns.join(', '),
|
||||
)} created ${addStamp()}`,
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import { handleError } from '../../util/error';
|
||||
|
||||
import getArgs from '../../util/get-args';
|
||||
import getSubcommand from '../../util/get-subcommand';
|
||||
import logo from '../../util/output/logo';
|
||||
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
import add from './add';
|
||||
import issue from './issue';
|
||||
import ls from './ls';
|
||||
import rm from './rm';
|
||||
import Client from '../../util/client';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
import type Client from '../../util/client';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -31,30 +29,30 @@ const help = () => {
|
||||
|
||||
-h, --help Output usage information
|
||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||
'FILE'
|
||||
'FILE',
|
||||
)} Path to the local ${'`vercel.json`'} file
|
||||
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
|
||||
'DIR'
|
||||
'DIR',
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
'TOKEN',
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
--challenge-only Only show challenges needed to issue a cert
|
||||
--crt ${chalk.bold.underline('FILE')} Certificate file
|
||||
--key ${chalk.bold.underline(
|
||||
'FILE'
|
||||
'FILE',
|
||||
)} Certificate key file
|
||||
--ca ${chalk.bold.underline(
|
||||
'FILE'
|
||||
'FILE',
|
||||
)} CA certificate chain file
|
||||
-N, --next Show next page of results
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray(
|
||||
'–'
|
||||
'–',
|
||||
)} Generate a certificate with the cnames "acme.com" and "www.acme.com"
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} certs issue acme.com www.acme.com`)}
|
||||
@@ -64,7 +62,7 @@ const help = () => {
|
||||
${chalk.cyan(`$ ${getPkgName()} certs rm id`)}
|
||||
|
||||
${chalk.gray('–')} Paginate results, where ${chalk.dim(
|
||||
'`1584722256178`'
|
||||
'`1584722256178`',
|
||||
)} is the time in milliseconds since the UNIX epoch.
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} certs ls --next 1584722256178`)}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { parse } from 'psl';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { Output } from '../../util/output';
|
||||
import * as ERRORS from '../../util/errors-ts';
|
||||
import Client from '../../util/client';
|
||||
import createCertForCns from '../../util/certs/create-cert-for-cns';
|
||||
import createCertFromFile from '../../util/certs/create-cert-from-file';
|
||||
import dnsTable from '../../util/format-dns-table';
|
||||
@@ -14,19 +11,21 @@ import stamp from '../../util/output/stamp';
|
||||
import startCertOrder from '../../util/certs/start-cert-order';
|
||||
import handleCertError from '../../util/certs/handle-cert-error';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import type Client from '../../util/client';
|
||||
import type { Output } from '../../util/output';
|
||||
|
||||
type Options = {
|
||||
interface Options {
|
||||
'--ca': string;
|
||||
'--challenge-only': boolean;
|
||||
'--crt': string;
|
||||
'--key': string;
|
||||
'--overwrite': boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export default async function issue(
|
||||
client: Client,
|
||||
opts: Partial<Options>,
|
||||
args: string[]
|
||||
args: string[],
|
||||
) {
|
||||
let cert;
|
||||
const { output } = client;
|
||||
@@ -49,14 +48,14 @@ export default async function issue(
|
||||
if (crtPath || keyPath || caPath) {
|
||||
if (args.length !== 0 || !crtPath || !keyPath || !caPath) {
|
||||
output.error(
|
||||
`Invalid number of arguments to create a custom certificate entry. Usage:`
|
||||
`Invalid number of arguments to create a custom certificate entry. Usage:`,
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.cyan(
|
||||
getCommandName(
|
||||
'certs issue --crt <domain.crt> --key <domain.key> --ca <ca.crt>'
|
||||
)
|
||||
)}\n`
|
||||
'certs issue --crt <domain.crt> --key <domain.key> --ca <ca.crt>',
|
||||
),
|
||||
)}\n`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -72,18 +71,18 @@ export default async function issue(
|
||||
// Print success message
|
||||
output.success(
|
||||
`Certificate entry for ${chalk.bold(
|
||||
cert.cns.join(', ')
|
||||
)} created ${addStamp()}`
|
||||
cert.cns.join(', '),
|
||||
)} created ${addStamp()}`,
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (args.length < 1) {
|
||||
output.error(
|
||||
`Invalid number of arguments to create a custom certificate entry. Usage:`
|
||||
`Invalid number of arguments to create a custom certificate entry. Usage:`,
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.cyan(getCommandName('certs issue <cn>[, <cn>]'))}\n`
|
||||
` ${chalk.cyan(getCommandName('certs issue <cn>[, <cn>]'))}\n`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -120,16 +119,16 @@ export default async function issue(
|
||||
if (handledResult instanceof ERRORS.DomainPermissionDenied) {
|
||||
output.error(
|
||||
`You do not have permissions over domain ${chalk.underline(
|
||||
handledResult.meta.domain
|
||||
)} under ${chalk.bold(handledResult.meta.context)}.`
|
||||
handledResult.meta.domain,
|
||||
)} under ${chalk.bold(handledResult.meta.context)}.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
output.success(
|
||||
`Certificate entry for ${chalk.bold(
|
||||
handledResult.cns.join(', ')
|
||||
)} created ${addStamp()}`
|
||||
handledResult.cns.join(', '),
|
||||
)} created ${addStamp()}`,
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
@@ -140,48 +139,48 @@ async function runStartOrder(
|
||||
cns: string[],
|
||||
contextName: string,
|
||||
stamp: () => string,
|
||||
{ fallingBack = false } = {}
|
||||
{ fallingBack = false } = {},
|
||||
) {
|
||||
const { challengesToResolve } = await startCertOrder(
|
||||
client,
|
||||
cns,
|
||||
contextName
|
||||
contextName,
|
||||
);
|
||||
const pendingChallenges = challengesToResolve.filter(
|
||||
challenge => challenge.status === 'pending'
|
||||
(challenge) => challenge.status === 'pending',
|
||||
);
|
||||
|
||||
if (fallingBack) {
|
||||
output.warn(
|
||||
`To generate a wildcard certificate for domain for an external domain you must solve challenges manually.`
|
||||
`To generate a wildcard certificate for domain for an external domain you must solve challenges manually.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (pendingChallenges.length === 0) {
|
||||
output.log(
|
||||
`A certificate issuance for ${chalk.bold(
|
||||
cns.join(', ')
|
||||
)} has been started ${stamp()}`
|
||||
cns.join(', '),
|
||||
)} has been started ${stamp()}`,
|
||||
);
|
||||
output.print(
|
||||
` There are no pending challenges. Finish the issuance by running: \n`
|
||||
` There are no pending challenges. Finish the issuance by running: \n`,
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.cyan(getCommandName(`certs issue ${cns.join(' ')}`))}\n`
|
||||
` ${chalk.cyan(getCommandName(`certs issue ${cns.join(' ')}`))}\n`,
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
output.log(
|
||||
`A certificate issuance for ${chalk.bold(
|
||||
cns.join(', ')
|
||||
)} has been started ${stamp()}`
|
||||
cns.join(', '),
|
||||
)} has been started ${stamp()}`,
|
||||
);
|
||||
output.print(
|
||||
` Add the following TXT records with your registrar to be able to the solve the DNS challenge:\n\n`
|
||||
` Add the following TXT records with your registrar to be able to the solve the DNS challenge:\n\n`,
|
||||
);
|
||||
const [header, ...rows] = dnsTable(
|
||||
pendingChallenges.map(challenge => {
|
||||
pendingChallenges.map((challenge) => {
|
||||
const parsedDomain = parse(challenge.domain);
|
||||
if (parsedDomain.error) {
|
||||
throw new ERRORS.InvalidDomain(challenge.domain);
|
||||
@@ -193,17 +192,17 @@ async function runStartOrder(
|
||||
'TXT',
|
||||
challenge.value,
|
||||
];
|
||||
})
|
||||
}),
|
||||
).split('\n');
|
||||
|
||||
output.print(`${header}\n`);
|
||||
process.stdout.write(`${rows.join('\n')}\n\n`);
|
||||
output.log(`To issue the certificate once the records are added, run:`);
|
||||
output.print(
|
||||
` ${chalk.cyan(getCommandName(`certs issue ${cns.join(' ')}`))}\n`
|
||||
` ${chalk.cyan(getCommandName(`certs issue ${cns.join(' ')}`))}\n`,
|
||||
);
|
||||
output.print(
|
||||
' Read more: https://err.sh/vercel/solve-challenges-manually\n'
|
||||
' Read more: https://err.sh/vercel/solve-challenges-manually\n',
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import table from 'text-table';
|
||||
import Client from '../../util/client';
|
||||
import getScope from '../../util/get-scope';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import getCerts from '../../util/certs/get-certs';
|
||||
import strlen from '../../util/strlen';
|
||||
import { Cert } from '../../types';
|
||||
import getCommandFlags from '../../util/get-command-flags';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import type { Cert } from '../../types';
|
||||
import type Client from '../../util/client';
|
||||
|
||||
interface Options {
|
||||
'--next'?: number;
|
||||
@@ -17,7 +17,7 @@ interface Options {
|
||||
async function ls(
|
||||
client: Client,
|
||||
opts: Options,
|
||||
args: string[]
|
||||
args: string[],
|
||||
): Promise<number> {
|
||||
const { output } = client;
|
||||
const { '--next': nextTimestamp } = opts;
|
||||
@@ -32,21 +32,21 @@ async function ls(
|
||||
if (args.length !== 0) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
`${getCommandName('certs ls')}`
|
||||
)}`
|
||||
`${getCommandName('certs ls')}`,
|
||||
)}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Get the list of certificates
|
||||
const { certs, pagination } = await getCerts(client, nextTimestamp).catch(
|
||||
err => err
|
||||
(err) => err,
|
||||
);
|
||||
|
||||
output.log(
|
||||
`${
|
||||
certs.length > 0 ? 'Certificates' : 'No certificates'
|
||||
} found under ${chalk.bold(contextName)} ${lsStamp()}`
|
||||
} found under ${chalk.bold(contextName)} ${lsStamp()}`,
|
||||
);
|
||||
|
||||
if (certs.length > 0) {
|
||||
@@ -57,8 +57,8 @@ async function ls(
|
||||
const flags = getCommandFlags(opts, ['_', '--next']);
|
||||
output.log(
|
||||
`To display the next page run ${getCommandName(
|
||||
`certs ls${flags} --next ${pagination.next}`
|
||||
)}`
|
||||
`certs ls${flags} --next ${pagination.next}`,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ function formatCertsTable(certsList: Cert[]) {
|
||||
align: ['l', 'l', 'r', 'c', 'r'],
|
||||
hsep: ' '.repeat(2),
|
||||
stringLength: strlen,
|
||||
}
|
||||
},
|
||||
).replace(/^(.*)/gm, ' $1')}\n`;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ function formatCertsTableBody(certsList: Cert[]) {
|
||||
const now = new Date();
|
||||
return certsList.reduce<string[][]>(
|
||||
(result, cert) => result.concat(formatCert(now, cert)),
|
||||
[]
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ function formatCert(time: Date, cert: Cert) {
|
||||
return cert.cns.map((cn, idx) =>
|
||||
idx === 0
|
||||
? formatCertFirstCn(time, cert, cn, cert.cns.length > 1)
|
||||
: formatCertNonFirstCn(cn, cert.cns.length > 1)
|
||||
: formatCertNonFirstCn(cn, cert.cns.length > 1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ function formatCertFirstCn(
|
||||
time: Date,
|
||||
cert: Cert,
|
||||
cn: string,
|
||||
multiple: boolean
|
||||
multiple: boolean,
|
||||
): string[] {
|
||||
return [
|
||||
cert.uid,
|
||||
|
||||
@@ -2,19 +2,19 @@ import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import plural from 'pluralize';
|
||||
import table from 'text-table';
|
||||
import { Cert } from '../../types';
|
||||
import * as ERRORS from '../../util/errors-ts';
|
||||
import { Output } from '../../util/output';
|
||||
import deleteCertById from '../../util/certs/delete-cert-by-id';
|
||||
import getCertById from '../../util/certs/get-cert-by-id';
|
||||
import { getCustomCertsForDomain } from '../../util/certs/get-custom-certs-for-domain';
|
||||
import Client from '../../util/client';
|
||||
import getScope from '../../util/get-scope';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import param from '../../util/output/param';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import type Client from '../../util/client';
|
||||
import type { Output } from '../../util/output';
|
||||
import type { Cert } from '../../types';
|
||||
|
||||
type Options = {};
|
||||
interface Options {}
|
||||
|
||||
async function rm(client: Client, opts: Options, args: string[]) {
|
||||
const rmStamp = stamp();
|
||||
@@ -24,8 +24,8 @@ async function rm(client: Client, opts: Options, args: string[]) {
|
||||
if (args.length !== 1) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
`${getCommandName('certs rm <id or cn>')}`
|
||||
)}`
|
||||
`${getCommandName('certs rm <id or cn>')}`,
|
||||
)}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ async function rm(client: Client, opts: Options, args: string[]) {
|
||||
const certs = await getCertsToDelete(output, client, contextName, id);
|
||||
if (certs instanceof ERRORS.CertsPermissionDenied) {
|
||||
output.error(
|
||||
`You don't have access to ${param(id)}'s certs under ${contextName}.`
|
||||
`You don't have access to ${param(id)}'s certs under ${contextName}.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -43,12 +43,12 @@ async function rm(client: Client, opts: Options, args: string[]) {
|
||||
if (id.includes('.')) {
|
||||
output.error(
|
||||
`No custom certificates found for "${id}" under ${chalk.bold(
|
||||
contextName
|
||||
)}`
|
||||
contextName,
|
||||
)}`,
|
||||
);
|
||||
} else {
|
||||
output.error(
|
||||
`No certificates found by id "${id}" under ${chalk.bold(contextName)}`
|
||||
`No certificates found by id "${id}" under ${chalk.bold(contextName)}`,
|
||||
);
|
||||
}
|
||||
return 1;
|
||||
@@ -57,7 +57,7 @@ async function rm(client: Client, opts: Options, args: string[]) {
|
||||
const yes = await readConfirmation(
|
||||
output,
|
||||
'The following certificates will be removed permanently',
|
||||
certs
|
||||
certs,
|
||||
);
|
||||
|
||||
if (!yes) {
|
||||
@@ -65,12 +65,12 @@ async function rm(client: Client, opts: Options, args: string[]) {
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
certs.map(cert => deleteCertById(output, client, cert.uid))
|
||||
certs.map((cert) => deleteCertById(output, client, cert.uid)),
|
||||
);
|
||||
output.success(
|
||||
`${chalk.bold(
|
||||
plural('Certificate', certs.length, true)
|
||||
)} removed ${rmStamp()}`
|
||||
plural('Certificate', certs.length, true),
|
||||
)} removed ${rmStamp()}`,
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
@@ -79,7 +79,7 @@ async function getCertsToDelete(
|
||||
output: Output,
|
||||
client: Client,
|
||||
contextName: string,
|
||||
id: string
|
||||
id: string,
|
||||
) {
|
||||
const cert = await getCertById(client, id);
|
||||
if (cert instanceof ERRORS.CertNotFound) {
|
||||
@@ -93,19 +93,19 @@ async function getCertsToDelete(
|
||||
}
|
||||
|
||||
function readConfirmation(output: Output, msg: string, certs: Cert[]) {
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve) => {
|
||||
output.log(msg);
|
||||
output.print(
|
||||
`${table(certs.map(formatCertRow), {
|
||||
align: ['l', 'r', 'l'],
|
||||
hsep: ' '.repeat(6),
|
||||
}).replace(/^(.*)/gm, ' $1')}\n`
|
||||
}).replace(/^(.*)/gm, ' $1')}\n`,
|
||||
);
|
||||
output.print(
|
||||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`
|
||||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`,
|
||||
);
|
||||
process.stdin
|
||||
.on('data', d => {
|
||||
.on('data', (d) => {
|
||||
process.stdin.pause();
|
||||
resolve(d.toString().trim().toLowerCase() === 'y');
|
||||
})
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { join, resolve, basename } from 'path';
|
||||
import ms from 'ms';
|
||||
import fs from 'fs-extra';
|
||||
import bytes from 'bytes';
|
||||
import chalk from 'chalk';
|
||||
import { join, resolve, basename } from 'path';
|
||||
import {
|
||||
fileNameSymbol,
|
||||
VALID_ARCHIVE_FORMATS,
|
||||
VercelConfig,
|
||||
} from '@vercel/client';
|
||||
import { getPrettyError } from '@vercel/build-utils';
|
||||
import code from '../../util/output/code';
|
||||
import highlight from '../../util/output/highlight';
|
||||
import { readLocalConfig } from '../../util/config/files';
|
||||
import getArgs from '../../util/get-args';
|
||||
import { handleError } from '../../util/error';
|
||||
import Client from '../../util/client';
|
||||
import { getPrettyError } from '@vercel/build-utils';
|
||||
import toHumanPath from '../../util/humanize-path';
|
||||
import Now from '../../util';
|
||||
import stamp from '../../util/output/stamp';
|
||||
@@ -47,9 +47,7 @@ import {
|
||||
import { SchemaValidationFailed } from '../../util/errors';
|
||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import editProjectSettings, {
|
||||
PartialProjectSettings,
|
||||
} from '../../util/input/edit-project-settings';
|
||||
import editProjectSettings from '../../util/input/edit-project-settings';
|
||||
import {
|
||||
getLinkedProject,
|
||||
linkFolderToProject,
|
||||
@@ -64,15 +62,16 @@ import validatePaths, {
|
||||
} from '../../util/validate-paths';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { getPreferredPreviewURL } from '../../util/deploy/get-preferred-preview-url';
|
||||
import { Output } from '../../util/output';
|
||||
import { help } from './args';
|
||||
import type { Output } from '../../util/output';
|
||||
import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
|
||||
import { help } from './args';
|
||||
import parseTarget from '../../util/deploy/parse-target';
|
||||
import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
|
||||
import { createGitMeta } from '../../util/create-git-meta';
|
||||
import { isValidArchive } from '../../util/deploy/validate-archive-format';
|
||||
import { parseEnv } from '../../util/parse-env';
|
||||
import { errorToString, isErrnoException, isError } from '../../util/is-error';
|
||||
import { pickOverrides } from '../../util/projects/project-settings';
|
||||
|
||||
export default async (client: Client): Promise<number> => {
|
||||
const { output } = client;
|
||||
@@ -378,14 +377,14 @@ export default async (client: Client): Promise<number> => {
|
||||
|
||||
if (
|
||||
rootDirectory &&
|
||||
(await validateRootDirectory(
|
||||
!(await validateRootDirectory(
|
||||
output,
|
||||
path,
|
||||
sourcePath,
|
||||
project
|
||||
? `To change your Project Settings, go to https://vercel.com/${org?.slug}/${project.name}/settings`
|
||||
: ''
|
||||
)) === false
|
||||
))
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
@@ -463,27 +462,27 @@ export default async (client: Client): Promise<number> => {
|
||||
}
|
||||
|
||||
// build `meta`
|
||||
const meta = Object.assign(
|
||||
{},
|
||||
parseMeta(localConfig.meta),
|
||||
parseMeta(argv['--meta'])
|
||||
);
|
||||
const meta = {
|
||||
|
||||
...parseMeta(localConfig.meta),
|
||||
...parseMeta(argv['--meta'])
|
||||
};
|
||||
|
||||
const gitMetadata = await createGitMeta(path, output, project);
|
||||
|
||||
// Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments
|
||||
const deploymentEnv = Object.assign(
|
||||
{},
|
||||
parseEnv(localConfig.env),
|
||||
parseEnv(argv['--env'])
|
||||
);
|
||||
const deploymentEnv = {
|
||||
|
||||
...parseEnv(localConfig.env),
|
||||
...parseEnv(argv['--env'])
|
||||
};
|
||||
|
||||
// Merge build env out of `build.env` from vercel.json, and `--build-env` args
|
||||
const deploymentBuildEnv = Object.assign(
|
||||
{},
|
||||
parseEnv(localConfig.build && localConfig.build.env),
|
||||
parseEnv(argv['--build-env'])
|
||||
);
|
||||
const deploymentBuildEnv = {
|
||||
|
||||
...parseEnv(localConfig.build && localConfig.build.env),
|
||||
...parseEnv(argv['--build-env'])
|
||||
};
|
||||
|
||||
// If there's any undefined values, then inherit them from this process
|
||||
try {
|
||||
@@ -501,7 +500,7 @@ export default async (client: Client): Promise<number> => {
|
||||
.filter(Boolean);
|
||||
const regions = regionFlag.length > 0 ? regionFlag : localConfig.regions;
|
||||
|
||||
const currentTeam = org?.type === 'team' ? org.id : undefined;
|
||||
const currentTeam = org.type === 'team' ? org.id : undefined;
|
||||
const now = new Now({
|
||||
client,
|
||||
currentTeam,
|
||||
@@ -509,14 +508,7 @@ export default async (client: Client): Promise<number> => {
|
||||
let deployStamp = stamp();
|
||||
let deployment = null;
|
||||
|
||||
const localConfigurationOverrides: PartialProjectSettings = {
|
||||
buildCommand: localConfig?.buildCommand,
|
||||
devCommand: localConfig?.devCommand,
|
||||
framework: localConfig?.framework,
|
||||
commandForIgnoringBuildStep: localConfig?.ignoreCommand,
|
||||
installCommand: localConfig?.installCommand,
|
||||
outputDirectory: localConfig?.outputDirectory,
|
||||
};
|
||||
const localConfigurationOverrides = pickOverrides(localConfig);
|
||||
|
||||
try {
|
||||
const createArgs: any = {
|
||||
@@ -530,7 +522,12 @@ export default async (client: Client): Promise<number> => {
|
||||
quiet,
|
||||
wantsPublic: argv['--public'] || localConfig.public,
|
||||
type: null,
|
||||
nowConfig: localConfig,
|
||||
nowConfig: {
|
||||
...localConfig,
|
||||
// `images` is allowed in "vercel.json" and processed
|
||||
// by `vc build`, but don't send it to the API endpoint
|
||||
images: undefined,
|
||||
},
|
||||
regions,
|
||||
meta,
|
||||
gitMetadata,
|
||||
@@ -562,7 +559,7 @@ export default async (client: Client): Promise<number> => {
|
||||
);
|
||||
|
||||
if (deployment.code === 'missing_project_settings') {
|
||||
let { projectSettings, framework } = deployment;
|
||||
const { projectSettings, framework } = deployment;
|
||||
if (rootDirectory) {
|
||||
projectSettings.rootDirectory = rootDirectory;
|
||||
}
|
||||
@@ -900,12 +897,12 @@ const printDeploymentStatus = async (
|
||||
}
|
||||
|
||||
output.print(
|
||||
prependEmoji(
|
||||
`${prependEmoji(
|
||||
`${isProdDeployment ? 'Production' : 'Preview'}: ${chalk.bold(
|
||||
previewUrl
|
||||
)} ${deployStamp()}`,
|
||||
emoji('success')
|
||||
) + `\n`
|
||||
) }\n`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -919,7 +916,7 @@ const printDeploymentStatus = async (
|
||||
}
|
||||
|
||||
const newline = '\n';
|
||||
for (let indication of indications) {
|
||||
for (const indication of indications) {
|
||||
const message =
|
||||
prependEmoji(chalk.dim(indication.payload), emoji(indication.type)) +
|
||||
newline;
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
import { resolve, join } from 'path';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import DevServer from '../../util/dev/server';
|
||||
import parseListen from '../../util/dev/parse-listen';
|
||||
import { ProjectEnvVariable } from '../../types';
|
||||
import Client from '../../util/client';
|
||||
import { parseListen } from '../../util/dev/parse-listen';
|
||||
import { getLinkedProject } from '../../util/projects/link';
|
||||
import { getFrameworks } from '../../util/get-frameworks';
|
||||
import { ProjectSettings } from '../../types';
|
||||
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
||||
import setupAndLink from '../../util/link/setup-and-link';
|
||||
import getSystemEnvValues from '../../util/env/get-system-env-values';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import param from '../../util/output/param';
|
||||
import { OUTPUT_DIR } from '../../util/build/write-build-result';
|
||||
import type { ProjectSettings } from '../../types';
|
||||
import type Client from '../../util/client';
|
||||
import type { ProjectEnvVariable } from '../../types';
|
||||
|
||||
type Options = {
|
||||
interface Options {
|
||||
'--listen': string;
|
||||
'--yes': boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export default async function dev(
|
||||
client: Client,
|
||||
opts: Partial<Options>,
|
||||
args: string[]
|
||||
args: string[],
|
||||
) {
|
||||
const { output } = client;
|
||||
const [dir = '.'] = args;
|
||||
@@ -31,10 +29,7 @@ export default async function dev(
|
||||
const listen = parseListen(opts['--listen'] || '3000');
|
||||
|
||||
// retrieve dev command
|
||||
let [link, frameworks] = await Promise.all([
|
||||
getLinkedProject(client, cwd),
|
||||
getFrameworks(client),
|
||||
]);
|
||||
let link = await getLinkedProject(client, cwd);
|
||||
|
||||
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
|
||||
link = await setupAndLink(client, cwd, {
|
||||
@@ -53,14 +48,13 @@ export default async function dev(
|
||||
if (link.reason === 'HEADLESS') {
|
||||
client.output.error(
|
||||
`Command ${getCommandName(
|
||||
'dev'
|
||||
)} requires confirmation. Use option ${param('--yes')} to confirm.`
|
||||
'dev',
|
||||
)} requires confirmation. Use option ${param('--yes')} to confirm.`,
|
||||
);
|
||||
}
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
let devCommand: string | undefined;
|
||||
let projectSettings: ProjectSettings | undefined;
|
||||
let projectEnvs: ProjectEnvVariable[] = [];
|
||||
let systemEnvValues: string[] = [];
|
||||
@@ -70,19 +64,6 @@ export default async function dev(
|
||||
|
||||
projectSettings = project;
|
||||
|
||||
if (project.devCommand) {
|
||||
devCommand = project.devCommand;
|
||||
} else if (project.framework) {
|
||||
const framework = frameworks.find(f => f.slug === project.framework);
|
||||
|
||||
if (framework) {
|
||||
const defaults = framework.settings.devCommand.value;
|
||||
if (defaults) {
|
||||
devCommand = defaults;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (project.rootDirectory) {
|
||||
cwd = join(cwd, project.rootDirectory);
|
||||
}
|
||||
@@ -95,16 +76,17 @@ export default async function dev(
|
||||
]);
|
||||
}
|
||||
|
||||
// This is just for tests - can be removed once project settings
|
||||
// are respected locally in `.vercel/project.json`
|
||||
if (process.env.VERCEL_DEV_COMMAND) {
|
||||
devCommand = process.env.VERCEL_DEV_COMMAND;
|
||||
}
|
||||
const devServer = new DevServer(cwd, {
|
||||
output,
|
||||
projectSettings,
|
||||
projectEnvs,
|
||||
systemEnvValues,
|
||||
});
|
||||
|
||||
// If there is no Development Command, we must delete the
|
||||
// v3 Build Output because it will incorrectly be detected by
|
||||
// @vercel/static-build in BuildOutputV3.getBuildOutputDirectory()
|
||||
if (!devCommand) {
|
||||
if (!devServer.devCommand) {
|
||||
const outputDir = join(cwd, OUTPUT_DIR);
|
||||
if (await fs.pathExists(outputDir)) {
|
||||
output.log(`Removing ${OUTPUT_DIR}`);
|
||||
@@ -112,13 +94,5 @@ export default async function dev(
|
||||
}
|
||||
}
|
||||
|
||||
const devServer = new DevServer(cwd, {
|
||||
output,
|
||||
devCommand,
|
||||
projectSettings,
|
||||
projectEnvs,
|
||||
systemEnvValues,
|
||||
});
|
||||
|
||||
await devServer.start(...listen);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import { PackageJson } from '@vercel/build-utils';
|
||||
|
||||
import getArgs from '../../util/get-args';
|
||||
import getSubcommand from '../../util/get-subcommand';
|
||||
import Client from '../../util/client';
|
||||
import { NowError } from '../../util/now-error';
|
||||
import handleError from '../../util/handle-error';
|
||||
import logo from '../../util/output/logo';
|
||||
import cmd from '../../util/output/cmd';
|
||||
import highlight from '../../util/output/highlight';
|
||||
import dev from './dev';
|
||||
import readConfig from '../../util/config/read-config';
|
||||
import readJSONFile from '../../util/read-json-file';
|
||||
import { getPkgName, getCommandName } from '../../util/pkg-name';
|
||||
import { CantParseJSONFile } from '../../util/errors-ts';
|
||||
import { isErrnoException } from '../../util/is-error';
|
||||
import dev from './dev';
|
||||
import type Client from '../../util/client';
|
||||
import type { PackageJson } from '@vercel/build-utils';
|
||||
|
||||
const COMMAND_CONFIG = {
|
||||
dev: ['dev'],
|
||||
@@ -42,7 +41,7 @@ const help = () => {
|
||||
${chalk.cyan(`$ ${getPkgName()} dev --listen 8080`)}
|
||||
|
||||
${chalk.gray(
|
||||
'–'
|
||||
'–',
|
||||
)} Make the \`vercel dev\` server bind to localhost on port 5000
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} dev --listen 127.0.0.1:5000`)}
|
||||
@@ -53,18 +52,17 @@ export default async function main(client: Client) {
|
||||
if (process.env.__VERCEL_DEV_RUNNING) {
|
||||
client.output.error(
|
||||
`${cmd(
|
||||
`${getPkgName()} dev`
|
||||
`${getPkgName()} dev`,
|
||||
)} must not recursively invoke itself. Check the Development Command in the Project Settings or the ${cmd(
|
||||
'dev'
|
||||
)} script in ${cmd('package.json')}`
|
||||
'dev',
|
||||
)} script in ${cmd('package.json')}`,
|
||||
);
|
||||
client.output.error(
|
||||
`Learn More: https://vercel.link/recursive-invocation-of-commands`
|
||||
`Learn More: https://vercel.link/recursive-invocation-of-commands`,
|
||||
);
|
||||
return 1;
|
||||
} else {
|
||||
process.env.__VERCEL_DEV_RUNNING = '1';
|
||||
}
|
||||
process.env.__VERCEL_DEV_RUNNING = '1';
|
||||
|
||||
let argv;
|
||||
let args;
|
||||
@@ -125,13 +123,13 @@ export default async function main(client: Client) {
|
||||
if (/\b(now|vercel)\b\W+\bdev\b/.test(pkg?.scripts?.dev || '')) {
|
||||
client.output.error(
|
||||
`${cmd(
|
||||
`${getPkgName()} dev`
|
||||
`${getPkgName()} dev`,
|
||||
)} must not recursively invoke itself. Check the Development Command in the Project Settings or the ${cmd(
|
||||
'dev'
|
||||
)} script in ${cmd('package.json')}`
|
||||
'dev',
|
||||
)} script in ${cmd('package.json')}`,
|
||||
);
|
||||
client.output.error(
|
||||
`Learn More: https://vercel.link/recursive-invocation-of-commands`
|
||||
`Learn More: https://vercel.link/recursive-invocation-of-commands`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -153,8 +151,8 @@ export default async function main(client: Client) {
|
||||
const hostname = matches[1];
|
||||
output.error(
|
||||
`The hostname ${highlight(
|
||||
hostname
|
||||
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
|
||||
hostname,
|
||||
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`,
|
||||
);
|
||||
}
|
||||
if (typeof err.stack === 'string') {
|
||||
|
||||
@@ -6,19 +6,19 @@ import {
|
||||
DNSInvalidType,
|
||||
} from '../../util/errors-ts';
|
||||
import addDNSRecord from '../../util/dns/add-dns-record';
|
||||
import Client from '../../util/client';
|
||||
import getScope from '../../util/get-scope';
|
||||
import parseAddDNSRecordArgs from '../../util/dns/parse-add-dns-record-args';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import getDNSData from '../../util/dns/get-dns-data';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import type Client from '../../util/client';
|
||||
|
||||
type Options = {};
|
||||
interface Options {}
|
||||
|
||||
export default async function add(
|
||||
client: Client,
|
||||
opts: Options,
|
||||
args: string[]
|
||||
args: string[],
|
||||
) {
|
||||
const { output } = client;
|
||||
const { contextName } = await getScope(client);
|
||||
@@ -27,8 +27,8 @@ export default async function add(
|
||||
if (!parsedParams) {
|
||||
output.error(
|
||||
`Invalid number of arguments. See: ${chalk.cyan(
|
||||
`${getCommandName('dns --help')}`
|
||||
)} for usage.`
|
||||
`${getCommandName('dns --help')}`,
|
||||
)} for usage.`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -45,8 +45,8 @@ export default async function add(
|
||||
if (record instanceof DomainNotFound) {
|
||||
output.error(
|
||||
`The domain ${domain} can't be found under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(addStamp())}`
|
||||
contextName,
|
||||
)} ${chalk.gray(addStamp())}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -54,8 +54,8 @@ export default async function add(
|
||||
if (record instanceof DNSPermissionDenied) {
|
||||
output.error(
|
||||
`You don't have permissions to add records to domain ${domain} under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(addStamp())}`
|
||||
contextName,
|
||||
)} ${chalk.gray(addStamp())}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -63,8 +63,8 @@ export default async function add(
|
||||
if (record instanceof DNSInvalidPort) {
|
||||
output.error(
|
||||
`Invalid <port> parameter. A number was expected ${chalk.gray(
|
||||
addStamp()
|
||||
)}`
|
||||
addStamp(),
|
||||
)}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -74,8 +74,8 @@ export default async function add(
|
||||
`Invalid <type> parameter "${
|
||||
record.meta.type
|
||||
}". Expected one of A, AAAA, ALIAS, CAA, CNAME, MX, SRV, TXT ${chalk.gray(
|
||||
addStamp()
|
||||
)}`
|
||||
addStamp(),
|
||||
)}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -87,10 +87,10 @@ export default async function add(
|
||||
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} DNS record for domain ${chalk.bold(
|
||||
domain
|
||||
domain,
|
||||
)} ${chalk.gray(`(${record.uid})`)} created under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(addStamp())}`
|
||||
contextName,
|
||||
)} ${chalk.gray(addStamp())}`,
|
||||
);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import chalk from 'chalk';
|
||||
import Client from '../../util/client';
|
||||
import getScope from '../../util/get-scope';
|
||||
import { DomainNotFound, InvalidDomain } from '../../util/errors-ts';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import importZonefile from '../../util/dns/import-zonefile';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import type Client from '../../util/client';
|
||||
|
||||
type Options = {};
|
||||
interface Options {}
|
||||
|
||||
export default async function add(
|
||||
client: Client,
|
||||
opts: Options,
|
||||
args: string[]
|
||||
args: string[],
|
||||
) {
|
||||
const { output } = client;
|
||||
const { contextName } = await getScope(client);
|
||||
@@ -19,8 +19,8 @@ export default async function add(
|
||||
if (args.length !== 2) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
`${getCommandName('dns import <domain> <zonefile>')}`
|
||||
)}`
|
||||
`${getCommandName('dns import <domain> <zonefile>')}`,
|
||||
)}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -32,13 +32,13 @@ export default async function add(
|
||||
client,
|
||||
contextName,
|
||||
domain,
|
||||
zonefilePath
|
||||
zonefilePath,
|
||||
);
|
||||
if (recordIds instanceof DomainNotFound) {
|
||||
output.error(
|
||||
`The domain ${domain} can't be found under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(addStamp())}`
|
||||
contextName,
|
||||
)} ${chalk.gray(addStamp())}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -46,8 +46,8 @@ export default async function add(
|
||||
if (recordIds instanceof InvalidDomain) {
|
||||
output.error(
|
||||
`The domain ${domain} doesn't match with the one found in the Zone file ${chalk.gray(
|
||||
addStamp()
|
||||
)}`
|
||||
addStamp(),
|
||||
)}`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -56,8 +56,8 @@ export default async function add(
|
||||
`${chalk.cyan('> Success!')} ${
|
||||
recordIds.length
|
||||
} DNS records for domain ${chalk.bold(domain)} created under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(addStamp())}`
|
||||
contextName,
|
||||
)} ${chalk.gray(addStamp())}`,
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user