mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 12:57:46 +00:00
Compare commits
64 Commits
@vercel/py
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
28
.github/CODEOWNERS
vendored
28
.github/CODEOWNERS
vendored
@@ -1,23 +1,17 @@
|
|||||||
# Documentation
|
# Documentation
|
||||||
# https://help.github.com/en/articles/about-code-owners
|
# https://help.github.com/en/articles/about-code-owners
|
||||||
|
|
||||||
* @TooTallNate @EndangeredMassa @styfle
|
* @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood
|
||||||
/.github/workflows @TooTallNate @EndangeredMassa @styfle @ijjk
|
/.github/workflows @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||||
/packages/frameworks @TooTallNate @EndangeredMassa @styfle @AndyBitz
|
/packages/cli/src/commands/domains @mglagola @anatrajkovska
|
||||||
/packages/cli/src/commands/domains @javivelasco @mglagola @anatrajkovska
|
/packages/cli/src/commands/certs @mglagola @anatrajkovska
|
||||||
/packages/cli/src/commands/certs @javivelasco @mglagola @anatrajkovska
|
/packages/cli/src/commands/env @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood
|
||||||
/packages/cli/src/commands/env @styfle @lucleray
|
/packages/fs-detectors @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @agadzik @chloetedder
|
||||||
/packages/client @TooTallNate @EndangeredMassa @styfle
|
/packages/middleware @gdborton @vercel/edge-function
|
||||||
/packages/build-utils @TooTallNate @EndangeredMassa @styfle @AndyBitz
|
/packages/node-bridge @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||||
/packages/middleware @gdborton @javivelasco
|
/packages/next @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||||
/packages/node @TooTallNate @EndangeredMassa @styfle
|
/packages/routing-utils @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||||
/packages/node-bridge @TooTallNate @EndangeredMassa @styfle @ijjk
|
/packages/edge @vercel/edge-function
|
||||||
/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
|
|
||||||
/examples @leerob
|
/examples @leerob
|
||||||
/examples/create-react-app @Timer
|
/examples/create-react-app @Timer
|
||||||
/examples/nextjs @timneutkens @ijjk @styfle
|
/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
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
- name: Install
|
- name: Install
|
||||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||||
run: yarn install --check-files --frozen-lockfile --network-timeout 1000000
|
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!');
|
||||||
11
.github/workflows/test-integration-cli.yml
vendored
11
.github/workflows/test-integration-cli.yml
vendored
@@ -25,19 +25,16 @@ jobs:
|
|||||||
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
||||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
||||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.13.15'
|
go-version: '1.13.15'
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
- uses: actions/checkout@v3
|
cache: 'yarn'
|
||||||
with:
|
|
||||||
fetch-depth: 100
|
|
||||||
- run: git --version
|
|
||||||
- run: git fetch origin main --depth=100
|
|
||||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
|
||||||
- run: git diff origin/main...HEAD --name-only
|
|
||||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||||
- run: yarn run build
|
- run: yarn run build
|
||||||
- run: yarn test-integration-cli
|
- run: yarn test-integration-cli
|
||||||
|
|||||||
13
.github/workflows/test-unit.yml
vendored
13
.github/workflows/test-unit.yml
vendored
@@ -28,16 +28,13 @@ jobs:
|
|||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.13.15'
|
go-version: '1.13.15'
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node }}
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 100
|
fetch-depth: 2
|
||||||
- run: git --version
|
- uses: actions/setup-node@v3
|
||||||
- run: git fetch origin main --depth=100
|
with:
|
||||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
node-version: ${{ matrix.node }}
|
||||||
- run: git diff origin/main...HEAD --name-only
|
cache: 'yarn'
|
||||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||||
- run: yarn run build
|
- run: yarn run build
|
||||||
- run: yarn run lint
|
- run: yarn run lint
|
||||||
|
|||||||
7
.github/workflows/test.yml
vendored
7
.github/workflows/test.yml
vendored
@@ -20,8 +20,11 @@ jobs:
|
|||||||
dplUrl: ${{ steps.waitForTarball.outputs.url }}
|
dplUrl: ${{ steps.waitForTarball.outputs.url }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- run: git --version
|
with:
|
||||||
- run: git fetch origin main
|
fetch-depth: 2
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '1.13.15'
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
version = 1
|
version = 1
|
||||||
|
|
||||||
[merge]
|
[merge]
|
||||||
automerge_label = ["semver-major","semver-minor","semver-patch"]
|
automerge_label = ["pr: automerge"]
|
||||||
blacklist_title_regex = "^WIP.*"
|
blacklist_title_regex = "^WIP.*"
|
||||||
blacklist_labels = ["work in progress"]
|
blacklist_labels = ["work in progress"]
|
||||||
method = "squash"
|
method = "squash"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import { join, dirname } from 'path';
|
import { join, dirname } from 'path';
|
||||||
import execa from 'execa';
|
|
||||||
import { getExampleList } from '../examples/example-list';
|
import { getExampleList } from '../examples/example-list';
|
||||||
import { mapOldToNew } from '../examples/map-old-to-new';
|
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.rm(pubDir, { recursive: true, force: true });
|
||||||
await fs.mkdir(pubDir);
|
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 examples = await getExampleList();
|
||||||
const pathListAll = join(pubDir, 'list-all.json');
|
const pathListAll = join(pubDir, 'list-all.json');
|
||||||
await fs.writeFile(pathListAll, JSON.stringify(examples));
|
await fs.writeFile(pathListAll, JSON.stringify(examples));
|
||||||
@@ -41,10 +46,6 @@ async function main() {
|
|||||||
JSON.stringify([...existingExamples, ...oldExamples])
|
JSON.stringify([...existingExamples, ...oldExamples])
|
||||||
);
|
);
|
||||||
|
|
||||||
const { stdout: sha } = await execa('git', ['rev-parse', '--short', 'HEAD'], {
|
|
||||||
cwd: repoRoot,
|
|
||||||
});
|
|
||||||
|
|
||||||
const tarballsDir = join(pubDir, 'tarballs');
|
const tarballsDir = join(pubDir, 'tarballs');
|
||||||
const packagesDir = join(repoRoot, 'packages');
|
const packagesDir = join(repoRoot, 'packages');
|
||||||
const packages = await fs.readdir(packagesDir);
|
const packages = await fs.readdir(packagesDir);
|
||||||
@@ -55,12 +56,21 @@ async function main() {
|
|||||||
'utf-8'
|
'utf-8'
|
||||||
);
|
);
|
||||||
const packageJson = JSON.parse(packageJsonRaw);
|
const packageJson = JSON.parse(packageJsonRaw);
|
||||||
const tarballName = `${packageJson.name
|
const files = await fs.readdir(fullDir);
|
||||||
.replace('@', '')
|
const tarballName = files.find(f => /^vercel-.+\.tgz$/.test(f));
|
||||||
.replace('/', '-')}-v${packageJson.version}-${sha.trim()}.tgz`;
|
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`);
|
const destTarballPath = join(tarballsDir, `${packageJson.name}.tgz`);
|
||||||
await fs.mkdir(dirname(destTarballPath), { recursive: true });
|
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.');
|
console.log('Completed building static frontend.');
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ const frameworks = (_frameworks as Framework[])
|
|||||||
defaultRoutes: undefined,
|
defaultRoutes: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (framework.logo) {
|
|
||||||
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return framework;
|
return framework;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
9
examples/nextjs/.gitignore
vendored
9
examples/nextjs/.gitignore
vendored
@@ -26,10 +26,11 @@ yarn-error.log*
|
|||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env.local
|
.env*.local
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
|
swcMinify: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = nextConfig
|
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,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
@@ -7,12 +9,12 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "12.1.4",
|
"next": "12.3.0",
|
||||||
"react": "18.0.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.0.0"
|
"react-dom": "18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "8.12.0",
|
"eslint": "8.23.0",
|
||||||
"eslint-config-next": "12.1.4"
|
"eslint-config-next": "12.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,3 +114,16 @@
|
|||||||
flex-direction: column;
|
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;
|
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_
|
_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
|
## 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
|
```bash
|
||||||
pnpm run dev
|
pnpm run dev
|
||||||
|
|||||||
@@ -10,4 +10,8 @@
|
|||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": 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,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "svelte-kit dev",
|
"dev": "vite dev",
|
||||||
"build": "svelte-kit build",
|
"build": "vite build",
|
||||||
"package": "svelte-kit package",
|
"package": "svelte-kit package",
|
||||||
"preview": "svelte-kit preview",
|
"preview": "vite preview",
|
||||||
"prepare": "svelte-kit sync",
|
|
||||||
"check": "svelte-check --tsconfig ./jsconfig.json",
|
"check": "svelte-check --tsconfig ./jsconfig.json",
|
||||||
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
|
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
|
||||||
"lint": "prettier --check --plugin-search-dir=. .",
|
"lint": "prettier --check .",
|
||||||
"format": "prettier --write --plugin-search-dir=. ."
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "1.0.0-next.50",
|
"@sveltejs/adapter-auto": "next",
|
||||||
"@sveltejs/kit": "1.0.0-next.347",
|
"@sveltejs/kit": "next",
|
||||||
"@types/cookie": "^0.4.1",
|
"@types/cookie": "^0.5.1",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.6.2",
|
||||||
"prettier-plugin-svelte": "^2.5.0",
|
"prettier-plugin-svelte": "^2.7.0",
|
||||||
"svelte": "^3.46.0",
|
"svelte": "^3.46.0",
|
||||||
"svelte-check": "^2.2.6",
|
"svelte-check": "^2.7.1",
|
||||||
"typescript": "~4.6.2"
|
"typescript": "^4.7.4",
|
||||||
|
"vite": "^3.0.8"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"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
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
|
// and what to do when importing types
|
||||||
declare namespace App {
|
declare namespace App {
|
||||||
interface Locals {
|
interface Locals {
|
||||||
userid: string;
|
userid: string;
|
||||||
@@ -9,7 +8,7 @@ declare namespace App {
|
|||||||
|
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
|
|
||||||
// interface Session {}
|
// interface PrivateEnv {}
|
||||||
|
|
||||||
// interface Stuff {}
|
// interface PublicEnv {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<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%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,107 +1,107 @@
|
|||||||
<script>
|
<script>
|
||||||
import { spring } from 'svelte/motion';
|
import { spring } from 'svelte/motion';
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
const displayed_count = spring();
|
const displayed_count = spring();
|
||||||
$: displayed_count.set(count);
|
$: displayed_count.set(count);
|
||||||
$: offset = modulo($displayed_count, 1);
|
$: offset = modulo($displayed_count, 1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} n
|
* @param {number} n
|
||||||
* @param {number} m
|
* @param {number} m
|
||||||
*/
|
*/
|
||||||
function modulo(n, m) {
|
function modulo(n, m) {
|
||||||
// handle negative numbers
|
// handle negative numbers
|
||||||
return ((n % m) + m) % m;
|
return ((n % m) + m) % m;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="counter">
|
<div class="counter">
|
||||||
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
||||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||||
<path d="M0,0.5 L1,0.5" />
|
<path d="M0,0.5 L1,0.5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="counter-viewport">
|
<div class="counter-viewport">
|
||||||
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
||||||
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
||||||
<strong>{Math.floor($displayed_count)}</strong>
|
<strong>{Math.floor($displayed_count)}</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
||||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||||
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.counter {
|
.counter {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter button {
|
.counter button {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 0;
|
border: 0;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter button:hover {
|
.counter button:hover {
|
||||||
background-color: var(--secondary-color);
|
background-color: var(--secondary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
height: 25%;
|
height: 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
path {
|
path {
|
||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
stroke-width: 2px;
|
stroke-width: 2px;
|
||||||
stroke: var(--text-color);
|
stroke: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter-viewport {
|
.counter-viewport {
|
||||||
width: 8em;
|
width: 8em;
|
||||||
height: 4em;
|
height: 4em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter-viewport strong {
|
.counter-viewport strong {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
font-size: 4rem;
|
font-size: 4rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter-digits {
|
.counter-digits {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
top: -100%;
|
top: -100%;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ import { invalidate } from '$app/navigation';
|
|||||||
export function enhance(form, { pending, error, result } = {}) {
|
export function enhance(form, { pending, error, result } = {}) {
|
||||||
let current_token;
|
let current_token;
|
||||||
|
|
||||||
/** @param {SubmitEvent} e */
|
/** @param {SubmitEvent} event */
|
||||||
async function handle_submit(e) {
|
async function handle_submit(event) {
|
||||||
const token = (current_token = {});
|
const token = (current_token = {});
|
||||||
|
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const data = new FormData(form);
|
const data = new FormData(form);
|
||||||
|
|
||||||
@@ -63,11 +63,11 @@ export function enhance(form, { pending, error, result } = {}) {
|
|||||||
} else {
|
} else {
|
||||||
console.error(await response.text());
|
console.error(await response.text());
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
if (error && e instanceof Error) {
|
if (error && err instanceof Error) {
|
||||||
error({ data, form, error: e, response: null });
|
error({ data, form, error: err, response: null });
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,124 +1,124 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import logo from './svelte-logo.svg';
|
import logo from './svelte-logo.svg';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div class="corner">
|
<div class="corner">
|
||||||
<a href="https://kit.svelte.dev">
|
<a href="https://kit.svelte.dev">
|
||||||
<img src={logo} alt="SvelteKit" />
|
<img src={logo} alt="SvelteKit" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
<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" />
|
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
||||||
</svg>
|
</svg>
|
||||||
<ul>
|
<ul>
|
||||||
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
||||||
<li class:active={$page.url.pathname === '/about'}>
|
<li class:active={$page.url.pathname === '/about'}>
|
||||||
<a sveltekit:prefetch href="/about">About</a>
|
<a sveltekit:prefetch href="/about">About</a>
|
||||||
</li>
|
</li>
|
||||||
<li class:active={$page.url.pathname === '/todos'}>
|
<li class:active={$page.url.pathname === '/todos'}>
|
||||||
<a sveltekit:prefetch href="/todos">Todos</a>
|
<a sveltekit:prefetch href="/todos">Todos</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
<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" />
|
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
|
||||||
</svg>
|
</svg>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="corner">
|
<div class="corner">
|
||||||
<!-- TODO put something else here? github link? -->
|
<!-- TODO put something else here? github link? -->
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.corner {
|
.corner {
|
||||||
width: 3em;
|
width: 3em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.corner a {
|
.corner a {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.corner img {
|
.corner img {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
height: 2em;
|
height: 2em;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
--background: rgba(255, 255, 255, 0.7);
|
--background: rgba(255, 255, 255, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
path {
|
path {
|
||||||
fill: var(--background);
|
fill: var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
li.active::before {
|
li.active::before {
|
||||||
--size: 6px;
|
--size: 6px;
|
||||||
content: '';
|
content: '';
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: calc(50% - var(--size));
|
left: calc(50% - var(--size));
|
||||||
border: var(--size) solid transparent;
|
border: var(--size) solid transparent;
|
||||||
border-top: var(--size) solid var(--accent-color);
|
border-top: var(--size) solid var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
nav a {
|
nav a {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 1em;
|
padding: 0 1em;
|
||||||
color: var(--heading-color);
|
color: var(--heading-color);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: color 0.2s linear;
|
transition: color 0.2s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
</style>
|
</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
|
This module is used by the /todos endpoint to
|
||||||
make calls to api.svelte.dev, which stores todos
|
make calls to api.svelte.dev, which stores todos
|
||||||
for each user. The leading underscore indicates that this is
|
for each user.
|
||||||
a private module, _not_ an endpoint — visiting /todos/_api
|
|
||||||
will net you a 404 response.
|
|
||||||
|
|
||||||
(The data on the todo app will expire periodically; no
|
(The data on the todo app will expire periodically; no
|
||||||
guarantees are made. Don't use it to organise your life.)
|
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
|
// Override http methods in the Todo forms
|
||||||
methodOverride: {
|
methodOverride: {
|
||||||
allowed: ['PATCH', 'DELETE']
|
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
@@ -8,7 +8,10 @@
|
|||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"nohoist": [
|
"nohoist": [
|
||||||
"**/@types/**"
|
"**/@types/**",
|
||||||
|
"**/typedoc",
|
||||||
|
"**/typedoc-plugin-markdown",
|
||||||
|
"**/typedoc-plugin-mdn-links"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/build-utils",
|
"name": "@vercel/build-utils",
|
||||||
"version": "5.3.2",
|
"version": "5.4.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.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 === undefined || env === null) {
|
||||||
|
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[]) {
|
export default function debug(message: string, ...additional: any[]) {
|
||||||
if (getPlatformEnv('BUILDER_DEBUG')) {
|
if (getPlatformEnv('BUILDER_DEBUG')) {
|
||||||
console.log(message, ...additional);
|
console.log(message, ...additional);
|
||||||
|
} else if (process.env.VERCEL_DEBUG_PREFIX) {
|
||||||
|
console.log(`${process.env.VERCEL_DEBUG_PREFIX}${message}`, ...additional);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { NowBuildError } from '../errors';
|
|||||||
import { Meta, PackageJson, NodeVersion, Config } from '../types';
|
import { Meta, PackageJson, NodeVersion, Config } from '../types';
|
||||||
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
|
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
|
||||||
import { readConfigFile } from './read-config-file';
|
import { readConfigFile } from './read-config-file';
|
||||||
|
import { cloneEnv } from '../clone-env';
|
||||||
|
|
||||||
// Only allow one `runNpmInstall()` invocation to run concurrently
|
// Only allow one `runNpmInstall()` invocation to run concurrently
|
||||||
const runNpmInstallSema = new Sema(1);
|
const runNpmInstallSema = new Sema(1);
|
||||||
@@ -23,8 +24,7 @@ export interface ScanParentDirsResult {
|
|||||||
*/
|
*/
|
||||||
cliType: CliType;
|
cliType: CliType;
|
||||||
/**
|
/**
|
||||||
* The file path of found `package.json` file, or `undefined` if none was
|
* The file path of found `package.json` file, or `undefined` if not found.
|
||||||
* found.
|
|
||||||
*/
|
*/
|
||||||
packageJsonPath?: string;
|
packageJsonPath?: string;
|
||||||
/**
|
/**
|
||||||
@@ -33,8 +33,13 @@ export interface ScanParentDirsResult {
|
|||||||
*/
|
*/
|
||||||
packageJson?: PackageJson;
|
packageJson?: PackageJson;
|
||||||
/**
|
/**
|
||||||
* The `lockfileVersion` number from the `package-lock.json` file,
|
* The file path of the lockfile (`yarn.lock`, `package-lock.json`, or `pnpm-lock.yaml`)
|
||||||
* when present.
|
* 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;
|
lockfileVersion?: number;
|
||||||
}
|
}
|
||||||
@@ -178,25 +183,9 @@ export async function getNodeBinPath({
|
|||||||
}: {
|
}: {
|
||||||
cwd: string;
|
cwd: string;
|
||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
const { code, stdout, stderr } = await execAsync('npm', ['bin'], {
|
const { lockfilePath } = await scanParentDirs(cwd);
|
||||||
cwd,
|
const dir = path.dirname(lockfilePath || cwd);
|
||||||
prettyCommand: 'npm bin',
|
return path.join(dir, 'node_modules', '.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})`,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function chmodPlusX(fsPath: string) {
|
async function chmodPlusX(fsPath: string) {
|
||||||
@@ -229,7 +218,7 @@ export function getSpawnOptions(
|
|||||||
nodeVersion: NodeVersion
|
nodeVersion: NodeVersion
|
||||||
): SpawnOptions {
|
): SpawnOptions {
|
||||||
const opts = {
|
const opts = {
|
||||||
env: { ...process.env },
|
env: cloneEnv(process.env),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!meta.isDev) {
|
if (!meta.isDev) {
|
||||||
@@ -319,6 +308,7 @@ export async function scanParentDirs(
|
|||||||
start: destPath,
|
start: destPath,
|
||||||
filenames: ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml'],
|
filenames: ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml'],
|
||||||
});
|
});
|
||||||
|
let lockfilePath: string | undefined;
|
||||||
let lockfileVersion: number | undefined;
|
let lockfileVersion: number | undefined;
|
||||||
let cliType: CliType = 'yarn';
|
let cliType: CliType = 'yarn';
|
||||||
|
|
||||||
@@ -335,17 +325,25 @@ export async function scanParentDirs(
|
|||||||
// Priority order is Yarn > pnpm > npm
|
// Priority order is Yarn > pnpm > npm
|
||||||
if (hasYarnLock) {
|
if (hasYarnLock) {
|
||||||
cliType = 'yarn';
|
cliType = 'yarn';
|
||||||
|
lockfilePath = yarnLockPath;
|
||||||
} else if (pnpmLockYaml) {
|
} else if (pnpmLockYaml) {
|
||||||
cliType = 'pnpm';
|
cliType = 'pnpm';
|
||||||
// just ensure that it is read as a number and not a string
|
lockfilePath = pnpmLockPath;
|
||||||
lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
|
lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
|
||||||
} else if (packageLockJson) {
|
} else if (packageLockJson) {
|
||||||
cliType = 'npm';
|
cliType = 'npm';
|
||||||
|
lockfilePath = npmLockPath;
|
||||||
lockfileVersion = packageLockJson.lockfileVersion;
|
lockfileVersion = packageLockJson.lockfileVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
const packageJsonPath = pkgJsonPath || undefined;
|
const packageJsonPath = pkgJsonPath || undefined;
|
||||||
return { cliType, packageJson, lockfileVersion, packageJsonPath };
|
return {
|
||||||
|
cliType,
|
||||||
|
packageJson,
|
||||||
|
lockfilePath,
|
||||||
|
lockfileVersion,
|
||||||
|
packageJsonPath,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function walkParentDirs({
|
export async function walkParentDirs({
|
||||||
@@ -452,7 +450,7 @@ export async function runNpmInstall(
|
|||||||
debug(`Installing to ${destPath}`);
|
debug(`Installing to ${destPath}`);
|
||||||
|
|
||||||
const opts: SpawnOptionsExtended = { cwd: destPath, ...spawnOpts };
|
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;
|
delete env.NODE_ENV;
|
||||||
opts.env = getEnvForPackageManager({
|
opts.env = getEnvForPackageManager({
|
||||||
cliType,
|
cliType,
|
||||||
@@ -467,6 +465,19 @@ export async function runNpmInstall(
|
|||||||
commandArgs = args
|
commandArgs = args
|
||||||
.filter(a => a !== '--prefer-offline')
|
.filter(a => a !== '--prefer-offline')
|
||||||
.concat(['install', '--no-audit', '--unsafe-perm']);
|
.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') {
|
} else if (cliType === 'pnpm') {
|
||||||
// PNPM's install command is similar to NPM's but without the audit nonsense
|
// PNPM's install command is similar to NPM's but without the audit nonsense
|
||||||
// @see options https://pnpm.io/cli/install
|
// @see options https://pnpm.io/cli/install
|
||||||
@@ -594,10 +605,7 @@ export async function runPackageJsonScript(
|
|||||||
cliType,
|
cliType,
|
||||||
lockfileVersion,
|
lockfileVersion,
|
||||||
nodeVersion: undefined,
|
nodeVersion: undefined,
|
||||||
env: {
|
env: cloneEnv(process.env, spawnOpts?.env),
|
||||||
...process.env,
|
|
||||||
...spawnOpts?.env,
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import debug from './debug';
|
|||||||
import getIgnoreFilter from './get-ignore-filter';
|
import getIgnoreFilter from './get-ignore-filter';
|
||||||
import { getPlatformEnv } from './get-platform-env';
|
import { getPlatformEnv } from './get-platform-env';
|
||||||
import { getPrefixedEnvVars } from './get-prefixed-env-vars';
|
import { getPrefixedEnvVars } from './get-prefixed-env-vars';
|
||||||
|
import { cloneEnv } from './clone-env';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
FileBlob,
|
FileBlob,
|
||||||
@@ -84,6 +85,7 @@ export {
|
|||||||
getLambdaOptionsFromFunction,
|
getLambdaOptionsFromFunction,
|
||||||
scanParentDirs,
|
scanParentDirs,
|
||||||
getIgnoreFilter,
|
getIgnoreFilter,
|
||||||
|
cloneEnv,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { EdgeFunction } from './edge-function';
|
export { EdgeFunction } from './edge-function';
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export interface Config {
|
|||||||
devCommand?: string;
|
devCommand?: string;
|
||||||
framework?: string | null;
|
framework?: string | null;
|
||||||
nodeVersion?: string;
|
nodeVersion?: string;
|
||||||
|
middleware?: boolean;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,6 +342,7 @@ export interface BuilderV2 {
|
|||||||
version: 2;
|
version: 2;
|
||||||
build: BuildV2;
|
build: BuildV2;
|
||||||
prepareCache?: PrepareCache;
|
prepareCache?: PrepareCache;
|
||||||
|
shouldServe?: ShouldServe;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BuilderV3 {
|
export interface BuilderV3 {
|
||||||
|
|||||||
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,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
"name": "20-npm-7",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const skipFixtures: string[] = [
|
|||||||
'08-zero-config-middleman',
|
'08-zero-config-middleman',
|
||||||
'21-npm-workspaces',
|
'21-npm-workspaces',
|
||||||
'23-pnpm-workspaces',
|
'23-pnpm-workspaces',
|
||||||
|
'41-nx-monorepo',
|
||||||
|
'42-npm-workspace-with-nx',
|
||||||
];
|
];
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
|||||||
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',
|
||||||
|
});
|
||||||
|
});
|
||||||
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'));
|
||||||
|
});
|
||||||
|
});
|
||||||
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 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
|
import { runNpmInstall, cloneEnv } from '../src';
|
||||||
|
import type { Meta } from '../src/types';
|
||||||
|
|
||||||
|
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),
|
||||||
|
});
|
||||||
|
});
|
||||||
65
packages/build-utils/test/unit.test.ts
vendored
65
packages/build-utils/test/unit.test.ts
vendored
@@ -1,7 +1,6 @@
|
|||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs, { readlink } from 'fs-extra';
|
import fs, { readlink } from 'fs-extra';
|
||||||
import retry from 'async-retry';
|
|
||||||
import { strict as assert, strictEqual } from 'assert';
|
import { strict as assert, strictEqual } from 'assert';
|
||||||
import { createZip } from '../src/lambda';
|
import { createZip } from '../src/lambda';
|
||||||
import { getSupportedNodeVersion } from '../src/fs/node-version';
|
import { getSupportedNodeVersion } from '../src/fs/node-version';
|
||||||
@@ -16,7 +15,6 @@ import {
|
|||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
scanParentDirs,
|
scanParentDirs,
|
||||||
FileBlob,
|
FileBlob,
|
||||||
Meta,
|
|
||||||
} from '../src';
|
} from '../src';
|
||||||
|
|
||||||
jest.setTimeout(10 * 1000);
|
jest.setTimeout(10 * 1000);
|
||||||
@@ -501,6 +499,7 @@ it('should return lockfileVersion 2 with npm7', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('npm');
|
expect(result.cliType).toEqual('npm');
|
||||||
expect(result.lockfileVersion).toEqual(2);
|
expect(result.lockfileVersion).toEqual(2);
|
||||||
|
expect(result.lockfilePath).toEqual(path.join(fixture, 'package-lock.json'));
|
||||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.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);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('yarn');
|
expect(result.cliType).toEqual('yarn');
|
||||||
expect(result.lockfileVersion).toEqual(undefined);
|
expect(result.lockfileVersion).toEqual(undefined);
|
||||||
|
expect(result.lockfilePath).toEqual(path.join(fixture, 'yarn.lock'));
|
||||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
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);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('npm');
|
expect(result.cliType).toEqual('npm');
|
||||||
expect(result.lockfileVersion).toEqual(1);
|
expect(result.lockfileVersion).toEqual(1);
|
||||||
|
expect(result.lockfilePath).toEqual(path.join(fixture, 'package-lock.json'));
|
||||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.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);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('npm');
|
expect(result.cliType).toEqual('npm');
|
||||||
expect(result.lockfileVersion).toEqual(2);
|
expect(result.lockfileVersion).toEqual(2);
|
||||||
|
expect(result.lockfilePath).toEqual(
|
||||||
|
path.join(fixture, '..', 'package-lock.json')
|
||||||
|
);
|
||||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.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);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('pnpm');
|
expect(result.cliType).toEqual('pnpm');
|
||||||
expect(result.lockfileVersion).toEqual(5.3);
|
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'));
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -541,6 +546,9 @@ it('should detect pnpm with workspaces', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('pnpm');
|
expect(result.cliType).toEqual('pnpm');
|
||||||
expect(result.lockfileVersion).toEqual(5.3);
|
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'));
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -552,6 +560,7 @@ it('should detect package.json in nested backend', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('yarn');
|
expect(result.cliType).toEqual('yarn');
|
||||||
expect(result.lockfileVersion).toEqual(undefined);
|
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'));
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -563,56 +572,6 @@ it('should detect package.json in nested frontend', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('yarn');
|
expect(result.cliType).toEqual('yarn');
|
||||||
expect(result.lockfileVersion).toEqual(undefined);
|
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'));
|
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);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vercel",
|
"name": "vercel",
|
||||||
"version": "28.1.4",
|
"version": "28.3.0",
|
||||||
"preferGlobal": true,
|
"preferGlobal": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "The command-line interface for Vercel",
|
"description": "The command-line interface for Vercel",
|
||||||
@@ -41,16 +41,16 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/build-utils": "5.3.2",
|
"@vercel/build-utils": "5.4.3",
|
||||||
"@vercel/go": "2.2.2",
|
"@vercel/go": "2.2.6",
|
||||||
"@vercel/hydrogen": "0.0.15",
|
"@vercel/hydrogen": "0.0.19",
|
||||||
"@vercel/next": "3.1.21",
|
"@vercel/next": "3.1.26",
|
||||||
"@vercel/node": "2.5.10",
|
"@vercel/node": "2.5.15",
|
||||||
"@vercel/python": "3.1.11",
|
"@vercel/python": "3.1.15",
|
||||||
"@vercel/redwood": "1.0.19",
|
"@vercel/redwood": "1.0.24",
|
||||||
"@vercel/remix": "1.0.20",
|
"@vercel/remix": "1.0.25",
|
||||||
"@vercel/ruby": "1.3.28",
|
"@vercel/ruby": "1.3.32",
|
||||||
"@vercel/static-build": "1.0.19",
|
"@vercel/static-build": "1.0.24",
|
||||||
"update-notifier": "5.1.0"
|
"update-notifier": "5.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -85,7 +85,6 @@
|
|||||||
"@types/node-fetch": "2.5.10",
|
"@types/node-fetch": "2.5.10",
|
||||||
"@types/npm-package-arg": "6.1.0",
|
"@types/npm-package-arg": "6.1.0",
|
||||||
"@types/pluralize": "0.0.29",
|
"@types/pluralize": "0.0.29",
|
||||||
"@types/progress": "2.0.3",
|
|
||||||
"@types/psl": "1.1.0",
|
"@types/psl": "1.1.0",
|
||||||
"@types/semver": "6.0.1",
|
"@types/semver": "6.0.1",
|
||||||
"@types/tar-fs": "1.16.1",
|
"@types/tar-fs": "1.16.1",
|
||||||
@@ -96,9 +95,9 @@
|
|||||||
"@types/which": "1.3.2",
|
"@types/which": "1.3.2",
|
||||||
"@types/write-json-file": "2.2.1",
|
"@types/write-json-file": "2.2.1",
|
||||||
"@types/yauzl-promise": "2.1.0",
|
"@types/yauzl-promise": "2.1.0",
|
||||||
"@vercel/client": "12.2.1",
|
"@vercel/client": "12.2.5",
|
||||||
"@vercel/frameworks": "1.1.3",
|
"@vercel/frameworks": "1.1.6",
|
||||||
"@vercel/fs-detectors": "2.0.5",
|
"@vercel/fs-detectors": "3.3.0",
|
||||||
"@vercel/fun": "1.0.4",
|
"@vercel/fun": "1.0.4",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"@zeit/source-map-support": "0.6.2",
|
"@zeit/source-map-support": "0.6.2",
|
||||||
@@ -153,7 +152,6 @@
|
|||||||
"ora": "3.4.0",
|
"ora": "3.4.0",
|
||||||
"pcre-to-regexp": "1.0.0",
|
"pcre-to-regexp": "1.0.0",
|
||||||
"pluralize": "7.0.0",
|
"pluralize": "7.0.0",
|
||||||
"progress": "2.0.3",
|
|
||||||
"promisepipe": "3.0.0",
|
"promisepipe": "3.0.0",
|
||||||
"psl": "1.1.31",
|
"psl": "1.1.31",
|
||||||
"qr-image": "3.2.0",
|
"qr-image": "3.2.0",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const { statSync } = require('fs');
|
|||||||
const pkg = require('../package');
|
const pkg = require('../package');
|
||||||
|
|
||||||
function error(command) {
|
function error(command) {
|
||||||
console.error('> Error!', command);
|
console.error('> Error:', command);
|
||||||
}
|
}
|
||||||
|
|
||||||
function debug(str) {
|
function debug(str) {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import cliPkg from '../util/pkg';
|
|||||||
import readJSONFile from '../util/read-json-file';
|
import readJSONFile from '../util/read-json-file';
|
||||||
import { CantParseJSONFile } from '../util/errors-ts';
|
import { CantParseJSONFile } from '../util/errors-ts';
|
||||||
import {
|
import {
|
||||||
|
pickOverrides,
|
||||||
ProjectLinkAndSettings,
|
ProjectLinkAndSettings,
|
||||||
readProjectSettings,
|
readProjectSettings,
|
||||||
} from '../util/projects/project-settings';
|
} from '../util/projects/project-settings';
|
||||||
@@ -278,6 +279,11 @@ async function doBuild(
|
|||||||
if (pkg instanceof CantParseJSONFile) throw pkg;
|
if (pkg instanceof CantParseJSONFile) throw pkg;
|
||||||
if (vercelConfig instanceof CantParseJSONFile) throw vercelConfig;
|
if (vercelConfig instanceof CantParseJSONFile) throw vercelConfig;
|
||||||
|
|
||||||
|
const projectSettings = {
|
||||||
|
...project.settings,
|
||||||
|
...pickOverrides(vercelConfig || {}),
|
||||||
|
};
|
||||||
|
|
||||||
// Get a list of source files
|
// Get a list of source files
|
||||||
const files = (await getFiles(workPath, client)).map(f =>
|
const files = (await getFiles(workPath, client)).map(f =>
|
||||||
normalizePath(relative(workPath, f))
|
normalizePath(relative(workPath, f))
|
||||||
@@ -313,7 +319,7 @@ async function doBuild(
|
|||||||
// Detect the Vercel Builders that will need to be invoked
|
// Detect the Vercel Builders that will need to be invoked
|
||||||
const detectedBuilders = await detectBuilders(files, pkg, {
|
const detectedBuilders = await detectBuilders(files, pkg, {
|
||||||
...vercelConfig,
|
...vercelConfig,
|
||||||
projectSettings: project.settings,
|
projectSettings,
|
||||||
ignoreBuildScript: true,
|
ignoreBuildScript: true,
|
||||||
featHandleMiss: true,
|
featHandleMiss: true,
|
||||||
});
|
});
|
||||||
@@ -425,14 +431,14 @@ async function doBuild(
|
|||||||
|
|
||||||
const buildConfig: Config = isZeroConfig
|
const buildConfig: Config = isZeroConfig
|
||||||
? {
|
? {
|
||||||
outputDirectory: project.settings.outputDirectory ?? undefined,
|
outputDirectory: projectSettings.outputDirectory ?? undefined,
|
||||||
...build.config,
|
...build.config,
|
||||||
projectSettings: project.settings,
|
projectSettings,
|
||||||
installCommand: project.settings.installCommand ?? undefined,
|
installCommand: projectSettings.installCommand ?? undefined,
|
||||||
devCommand: project.settings.devCommand ?? undefined,
|
devCommand: projectSettings.devCommand ?? undefined,
|
||||||
buildCommand: project.settings.buildCommand ?? undefined,
|
buildCommand: projectSettings.buildCommand ?? undefined,
|
||||||
framework: project.settings.framework,
|
framework: projectSettings.framework,
|
||||||
nodeVersion: project.settings.nodeVersion,
|
nodeVersion: projectSettings.nodeVersion,
|
||||||
}
|
}
|
||||||
: build.config || {};
|
: build.config || {};
|
||||||
const buildOptions: BuildOptions = {
|
const buildOptions: BuildOptions = {
|
||||||
|
|||||||
@@ -47,9 +47,7 @@ import {
|
|||||||
import { SchemaValidationFailed } from '../../util/errors';
|
import { SchemaValidationFailed } from '../../util/errors';
|
||||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||||
import confirm from '../../util/input/confirm';
|
import confirm from '../../util/input/confirm';
|
||||||
import editProjectSettings, {
|
import editProjectSettings from '../../util/input/edit-project-settings';
|
||||||
PartialProjectSettings,
|
|
||||||
} from '../../util/input/edit-project-settings';
|
|
||||||
import {
|
import {
|
||||||
getLinkedProject,
|
getLinkedProject,
|
||||||
linkFolderToProject,
|
linkFolderToProject,
|
||||||
@@ -73,6 +71,7 @@ import { createGitMeta } from '../../util/create-git-meta';
|
|||||||
import { isValidArchive } from '../../util/deploy/validate-archive-format';
|
import { isValidArchive } from '../../util/deploy/validate-archive-format';
|
||||||
import { parseEnv } from '../../util/parse-env';
|
import { parseEnv } from '../../util/parse-env';
|
||||||
import { errorToString, isErrnoException, isError } from '../../util/is-error';
|
import { errorToString, isErrnoException, isError } from '../../util/is-error';
|
||||||
|
import { pickOverrides } from '../../util/projects/project-settings';
|
||||||
|
|
||||||
export default async (client: Client): Promise<number> => {
|
export default async (client: Client): Promise<number> => {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
@@ -509,14 +508,7 @@ export default async (client: Client): Promise<number> => {
|
|||||||
let deployStamp = stamp();
|
let deployStamp = stamp();
|
||||||
let deployment = null;
|
let deployment = null;
|
||||||
|
|
||||||
const localConfigurationOverrides: PartialProjectSettings = {
|
const localConfigurationOverrides = pickOverrides(localConfig);
|
||||||
buildCommand: localConfig?.buildCommand,
|
|
||||||
devCommand: localConfig?.devCommand,
|
|
||||||
framework: localConfig?.framework,
|
|
||||||
commandForIgnoringBuildStep: localConfig?.ignoreCommand,
|
|
||||||
installCommand: localConfig?.installCommand,
|
|
||||||
outputDirectory: localConfig?.outputDirectory,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const createArgs: any = {
|
const createArgs: any = {
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ import { resolve, join } from 'path';
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
|
||||||
import DevServer from '../../util/dev/server';
|
import DevServer from '../../util/dev/server';
|
||||||
import parseListen from '../../util/dev/parse-listen';
|
import { parseListen } from '../../util/dev/parse-listen';
|
||||||
import { ProjectEnvVariable } from '../../types';
|
import { ProjectEnvVariable } from '../../types';
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import { getLinkedProject } from '../../util/projects/link';
|
import { getLinkedProject } from '../../util/projects/link';
|
||||||
import { getFrameworks } from '../../util/get-frameworks';
|
|
||||||
import { ProjectSettings } from '../../types';
|
import { ProjectSettings } from '../../types';
|
||||||
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
||||||
import setupAndLink from '../../util/link/setup-and-link';
|
import setupAndLink from '../../util/link/setup-and-link';
|
||||||
@@ -31,10 +30,7 @@ export default async function dev(
|
|||||||
const listen = parseListen(opts['--listen'] || '3000');
|
const listen = parseListen(opts['--listen'] || '3000');
|
||||||
|
|
||||||
// retrieve dev command
|
// retrieve dev command
|
||||||
let [link, frameworks] = await Promise.all([
|
let link = await getLinkedProject(client, cwd);
|
||||||
getLinkedProject(client, cwd),
|
|
||||||
getFrameworks(client),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
|
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
|
||||||
link = await setupAndLink(client, cwd, {
|
link = await setupAndLink(client, cwd, {
|
||||||
@@ -60,7 +56,6 @@ export default async function dev(
|
|||||||
return link.exitCode;
|
return link.exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
let devCommand: string | undefined;
|
|
||||||
let projectSettings: ProjectSettings | undefined;
|
let projectSettings: ProjectSettings | undefined;
|
||||||
let projectEnvs: ProjectEnvVariable[] = [];
|
let projectEnvs: ProjectEnvVariable[] = [];
|
||||||
let systemEnvValues: string[] = [];
|
let systemEnvValues: string[] = [];
|
||||||
@@ -70,19 +65,6 @@ export default async function dev(
|
|||||||
|
|
||||||
projectSettings = project;
|
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) {
|
if (project.rootDirectory) {
|
||||||
cwd = join(cwd, project.rootDirectory);
|
cwd = join(cwd, project.rootDirectory);
|
||||||
}
|
}
|
||||||
@@ -95,16 +77,17 @@ export default async function dev(
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is just for tests - can be removed once project settings
|
const devServer = new DevServer(cwd, {
|
||||||
// are respected locally in `.vercel/project.json`
|
output,
|
||||||
if (process.env.VERCEL_DEV_COMMAND) {
|
projectSettings,
|
||||||
devCommand = process.env.VERCEL_DEV_COMMAND;
|
projectEnvs,
|
||||||
}
|
systemEnvValues,
|
||||||
|
});
|
||||||
|
|
||||||
// If there is no Development Command, we must delete the
|
// If there is no Development Command, we must delete the
|
||||||
// v3 Build Output because it will incorrectly be detected by
|
// v3 Build Output because it will incorrectly be detected by
|
||||||
// @vercel/static-build in BuildOutputV3.getBuildOutputDirectory()
|
// @vercel/static-build in BuildOutputV3.getBuildOutputDirectory()
|
||||||
if (!devCommand) {
|
if (!devServer.devCommand) {
|
||||||
const outputDir = join(cwd, OUTPUT_DIR);
|
const outputDir = join(cwd, OUTPUT_DIR);
|
||||||
if (await fs.pathExists(outputDir)) {
|
if (await fs.pathExists(outputDir)) {
|
||||||
output.log(`Removing ${OUTPUT_DIR}`);
|
output.log(`Removing ${OUTPUT_DIR}`);
|
||||||
@@ -112,13 +95,5 @@ export default async function dev(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const devServer = new DevServer(cwd, {
|
|
||||||
output,
|
|
||||||
devCommand,
|
|
||||||
projectSettings,
|
|
||||||
projectEnvs,
|
|
||||||
systemEnvValues,
|
|
||||||
});
|
|
||||||
|
|
||||||
await devServer.start(...listen);
|
await devServer.start(...listen);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const help = () => {
|
|||||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||||
'TOKEN'
|
'TOKEN'
|
||||||
)} Login token
|
)} Login token
|
||||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
-y, --yes Skip confirmation when connecting a Git provider
|
||||||
|
|
||||||
${chalk.dim('Examples:')}
|
${chalk.dim('Examples:')}
|
||||||
|
|
||||||
@@ -35,7 +35,9 @@ const help = () => {
|
|||||||
|
|
||||||
${chalk.cyan(`$ ${getPkgName()} git connect`)}
|
${chalk.cyan(`$ ${getPkgName()} git connect`)}
|
||||||
|
|
||||||
${chalk.gray('–')} Connect your Vercel Project to a Git repository using the remote URL
|
${chalk.gray(
|
||||||
|
'–'
|
||||||
|
)} Connect your Vercel Project to a Git repository using the remote URL
|
||||||
|
|
||||||
${chalk.cyan(
|
${chalk.cyan(
|
||||||
`$ ${getPkgName()} git connect https://github.com/user/repo.git`
|
`$ ${getPkgName()} git connect https://github.com/user/repo.git`
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ try {
|
|||||||
process.cwd();
|
process.cwd();
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
if (isError(err) && err.message.includes('uv_cwd')) {
|
if (isError(err) && err.message.includes('uv_cwd')) {
|
||||||
console.error('Error! The current working directory does not exist.');
|
console.error('Error: The current working directory does not exist.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
import { URL } from 'url';
|
||||||
|
import plural from 'pluralize';
|
||||||
import npa from 'npm-package-arg';
|
import npa from 'npm-package-arg';
|
||||||
import { satisfies } from 'semver';
|
import { satisfies } from 'semver';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
import { mkdirp, outputJSON, readJSON, symlink } from 'fs-extra';
|
import { mkdirp, outputJSON, readJSON, symlink } from 'fs-extra';
|
||||||
|
import { isStaticRuntime } from '@vercel/fs-detectors';
|
||||||
import {
|
import {
|
||||||
BuilderV2,
|
BuilderV2,
|
||||||
BuilderV3,
|
BuilderV3,
|
||||||
@@ -13,6 +16,9 @@ import { VERCEL_DIR } from '../projects/link';
|
|||||||
import { Output } from '../output';
|
import { Output } from '../output';
|
||||||
import readJSONFile from '../read-json-file';
|
import readJSONFile from '../read-json-file';
|
||||||
import { CantParseJSONFile } from '../errors-ts';
|
import { CantParseJSONFile } from '../errors-ts';
|
||||||
|
import { errorToString, isErrnoException, isError } from '../is-error';
|
||||||
|
import cmd from '../output/cmd';
|
||||||
|
import code from '../output/code';
|
||||||
|
|
||||||
export interface BuilderWithPkg {
|
export interface BuilderWithPkg {
|
||||||
path: string;
|
path: string;
|
||||||
@@ -81,7 +87,7 @@ export async function resolveBuilders(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === '@vercel/static' || name === '@now/static') {
|
if (isStaticRuntime(name)) {
|
||||||
// `@vercel/static` is a special-case built-in builder
|
// `@vercel/static` is a special-case built-in builder
|
||||||
builders.set(name, {
|
builders.set(name, {
|
||||||
builder: staticBuilder,
|
builder: staticBuilder,
|
||||||
@@ -201,15 +207,54 @@ async function installBuilders(
|
|||||||
if (err.code !== 'EEXIST') throw err;
|
if (err.code !== 'EEXIST') throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.debug(`Installing Builders: ${Array.from(buildersToAdd).join(', ')}`);
|
output.log(
|
||||||
await spawnAsync('yarn', ['add', '@vercel/build-utils', ...buildersToAdd], {
|
`Installing ${plural('Builder', buildersToAdd.size)}: ${Array.from(
|
||||||
cwd: buildersDir,
|
buildersToAdd
|
||||||
});
|
).join(', ')}`
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await spawnAsync(
|
||||||
|
'npm',
|
||||||
|
['install', '@vercel/build-utils', ...buildersToAdd],
|
||||||
|
{
|
||||||
|
cwd: buildersDir,
|
||||||
|
stdio: 'pipe',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
if (isError(err)) {
|
||||||
|
(err as any).link =
|
||||||
|
'https://vercel.link/builder-dependencies-install-failed';
|
||||||
|
if (isErrnoException(err) && err.code === 'ENOENT') {
|
||||||
|
// `npm` is not installed
|
||||||
|
err.message = `Please install ${cmd('npm')} before continuing`;
|
||||||
|
} else {
|
||||||
|
const message = errorToString(err);
|
||||||
|
const notFound = /GET (.*) - Not found/.exec(message);
|
||||||
|
if (notFound) {
|
||||||
|
const url = new URL(notFound[1]);
|
||||||
|
const packageName = decodeURIComponent(url.pathname.slice(1));
|
||||||
|
err.message = `The package ${code(
|
||||||
|
packageName
|
||||||
|
)} is not published on the npm registry`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
// Symlink `@now/build-utils` -> `@vercel/build-utils` to support legacy Builders
|
// Symlink `@now/build-utils` -> `@vercel/build-utils` to support legacy Builders
|
||||||
const nowScopePath = join(buildersDir, 'node_modules/@now');
|
const nowScopePath = join(buildersDir, 'node_modules/@now');
|
||||||
await mkdirp(nowScopePath);
|
await mkdirp(nowScopePath);
|
||||||
await symlink('../@vercel/build-utils', join(nowScopePath, 'build-utils'));
|
|
||||||
|
try {
|
||||||
|
await symlink('../@vercel/build-utils', join(nowScopePath, 'build-utils'));
|
||||||
|
} catch (err: unknown) {
|
||||||
|
if (!isErrnoException(err) || err.code !== 'EEXIST') {
|
||||||
|
// Throw unless the error is due to the symlink already existing
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cross-reference any builderSpecs from the saved `package.json` file,
|
// Cross-reference any builderSpecs from the saved `package.json` file,
|
||||||
// in case they were installed from a URL
|
// in case they were installed from a URL
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import minimatch from 'minimatch';
|
import minimatch from 'minimatch';
|
||||||
import { BuildV2, Files } from '@vercel/build-utils';
|
import { shouldServe as defaultShouldServe } from '@vercel/build-utils';
|
||||||
|
import type { BuildV2, Files, ShouldServe } from '@vercel/build-utils';
|
||||||
|
|
||||||
export const version = 2;
|
export const version = 2;
|
||||||
|
|
||||||
@@ -39,3 +40,18 @@ export const build: BuildV2 = async ({ entrypoint, files, config }) => {
|
|||||||
|
|
||||||
return { output };
|
return { output };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const shouldServe: ShouldServe = _opts => {
|
||||||
|
const opts = { ..._opts };
|
||||||
|
const {
|
||||||
|
config: { zeroConfig, outputDirectory },
|
||||||
|
} = opts;
|
||||||
|
|
||||||
|
// Add the output directory prefix
|
||||||
|
if (zeroConfig && outputDirectory) {
|
||||||
|
opts.entrypoint = `${outputDirectory}/${opts.entrypoint}`;
|
||||||
|
opts.requestPath = `${outputDirectory}/${opts.requestPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultShouldServe(opts);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import bytes from 'bytes';
|
import bytes from 'bytes';
|
||||||
import Progress from 'progress';
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import {
|
import {
|
||||||
ArchiveFormat,
|
ArchiveFormat,
|
||||||
@@ -8,6 +7,7 @@ import {
|
|||||||
VercelClientOptions,
|
VercelClientOptions,
|
||||||
} from '@vercel/client';
|
} from '@vercel/client';
|
||||||
import { Output } from '../output';
|
import { Output } from '../output';
|
||||||
|
import { progress } from '../output/progress';
|
||||||
import Now from '../../util';
|
import Now from '../../util';
|
||||||
import { Org } from '../../types';
|
import { Org } from '../../types';
|
||||||
import ua from '../ua';
|
import ua from '../ua';
|
||||||
@@ -68,7 +68,6 @@ export default async function processDeployment({
|
|||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
const { debug } = output;
|
const { debug } = output;
|
||||||
let bar: Progress | null = null;
|
|
||||||
|
|
||||||
const { env = {} } = requestBody;
|
const { env = {} } = requestBody;
|
||||||
|
|
||||||
@@ -114,32 +113,28 @@ export default async function processDeployment({
|
|||||||
const missingSize = missing
|
const missingSize = missing
|
||||||
.map((sha: string) => total.get(sha).data.length)
|
.map((sha: string) => total.get(sha).data.length)
|
||||||
.reduce((a: number, b: number) => a + b, 0);
|
.reduce((a: number, b: number) => a + b, 0);
|
||||||
|
const totalSizeHuman = bytes.format(missingSize, { decimalPlaces: 1 });
|
||||||
output.stopSpinner();
|
|
||||||
bar = new Progress(`${chalk.gray('>')} Upload [:bar] :percent :etas`, {
|
|
||||||
width: 20,
|
|
||||||
complete: '=',
|
|
||||||
incomplete: '',
|
|
||||||
total: missingSize,
|
|
||||||
clear: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
bar.tick(0);
|
|
||||||
|
|
||||||
uploads.forEach((e: any) =>
|
uploads.forEach((e: any) =>
|
||||||
e.on('progress', () => {
|
e.on('progress', () => {
|
||||||
if (!bar) return;
|
const uploadedBytes = uploads.reduce((acc: number, e: any) => {
|
||||||
|
|
||||||
const totalBytesUploaded = uploads.reduce((acc: number, e: any) => {
|
|
||||||
return acc + e.bytesUploaded;
|
return acc + e.bytesUploaded;
|
||||||
}, 0);
|
}, 0);
|
||||||
// set the current progress bar value
|
|
||||||
bar.curr = totalBytesUploaded;
|
|
||||||
// trigger rendering
|
|
||||||
bar.tick(0);
|
|
||||||
|
|
||||||
if (bar.complete) {
|
const bar = progress(uploadedBytes, missingSize);
|
||||||
|
if (!bar || uploadedBytes === missingSize) {
|
||||||
output.spinner(deployingSpinnerVal, 0);
|
output.spinner(deployingSpinnerVal, 0);
|
||||||
|
} else {
|
||||||
|
const uploadedHuman = bytes.format(uploadedBytes, {
|
||||||
|
decimalPlaces: 1,
|
||||||
|
fixedDecimals: true,
|
||||||
|
});
|
||||||
|
output.spinner(
|
||||||
|
`Uploading ${chalk.reset(
|
||||||
|
`[${bar}] (${uploadedHuman}/${totalSizeHuman})`
|
||||||
|
)}`,
|
||||||
|
0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -154,10 +149,6 @@ export default async function processDeployment({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.type === 'created') {
|
if (event.type === 'created') {
|
||||||
if (bar && !bar.complete) {
|
|
||||||
bar.tick(bar.total + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
await linkFolderToProject(
|
await linkFolderToProject(
|
||||||
output,
|
output,
|
||||||
cwd || paths[0],
|
cwd || paths[0],
|
||||||
|
|||||||
@@ -1,412 +0,0 @@
|
|||||||
import chalk from 'chalk';
|
|
||||||
import execa from 'execa';
|
|
||||||
import semver from 'semver';
|
|
||||||
import npa from 'npm-package-arg';
|
|
||||||
import pluralize from 'pluralize';
|
|
||||||
import { basename, join } from 'path';
|
|
||||||
import XDGAppPaths from 'xdg-app-paths';
|
|
||||||
import { mkdirp, readJSON, writeJSON } from 'fs-extra';
|
|
||||||
import { NowBuildError, PackageJson } from '@vercel/build-utils';
|
|
||||||
import cliPkg from '../pkg';
|
|
||||||
|
|
||||||
import cmd from '../output/cmd';
|
|
||||||
import { Output } from '../output';
|
|
||||||
import { NoBuilderCacheError } from '../errors-ts';
|
|
||||||
|
|
||||||
import * as staticBuilder from './static-builder';
|
|
||||||
import { BuilderWithPackage } from './types';
|
|
||||||
import { isErrnoException } from '../is-error';
|
|
||||||
|
|
||||||
const require_: typeof require = eval('require');
|
|
||||||
|
|
||||||
const registryTypes = new Set(['version', 'tag', 'range']);
|
|
||||||
|
|
||||||
const createStaticBuilder = (scope: string): BuilderWithPackage => {
|
|
||||||
return {
|
|
||||||
runInProcess: true,
|
|
||||||
requirePath: `${scope}/static`,
|
|
||||||
builder: Object.freeze(staticBuilder),
|
|
||||||
package: Object.freeze({ name: `@${scope}/static`, version: '' }),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const localBuilders: { [key: string]: BuilderWithPackage } = {
|
|
||||||
'@now/static': createStaticBuilder('now'),
|
|
||||||
'@vercel/static': createStaticBuilder('vercel'),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const cacheDirPromise = prepareCacheDir();
|
|
||||||
export const builderDirPromise = prepareBuilderDir();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare cache directory for installing Vercel runtimes.
|
|
||||||
*/
|
|
||||||
export async function prepareCacheDir() {
|
|
||||||
const designated = XDGAppPaths('com.vercel.cli').cache();
|
|
||||||
|
|
||||||
if (!designated) {
|
|
||||||
throw new NoBuilderCacheError();
|
|
||||||
}
|
|
||||||
|
|
||||||
const cacheDir = join(designated, 'dev');
|
|
||||||
await mkdirp(cacheDir);
|
|
||||||
return cacheDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function prepareBuilderDir() {
|
|
||||||
const builderDir = join(await cacheDirPromise, 'builders');
|
|
||||||
await mkdirp(builderDir);
|
|
||||||
|
|
||||||
// Create an empty `package.json` file, only if one does not already exist
|
|
||||||
try {
|
|
||||||
const buildersPkg = join(builderDir, 'package.json');
|
|
||||||
await writeJSON(buildersPkg, { private: true }, { flag: 'wx' });
|
|
||||||
} catch (err: unknown) {
|
|
||||||
if (!isErrnoException(err) || err.code !== 'EEXIST') {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builderDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNpmVersion(use = ''): string {
|
|
||||||
const parsed = npa(use);
|
|
||||||
if (registryTypes.has(parsed.type)) {
|
|
||||||
return parsed.fetchSpec || '';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBuildUtils(packages: string[]): string {
|
|
||||||
const version = packages
|
|
||||||
.map(getNpmVersion)
|
|
||||||
.some(ver => ver.includes('canary'))
|
|
||||||
? 'canary'
|
|
||||||
: 'latest';
|
|
||||||
|
|
||||||
return `@vercel/build-utils@${version}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseVersionSafe(rawSpec: string) {
|
|
||||||
try {
|
|
||||||
return semver.parse(rawSpec);
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function filterPackage(
|
|
||||||
builderSpec: string,
|
|
||||||
buildersPkg: PackageJson,
|
|
||||||
cliPkg: Partial<PackageJson>
|
|
||||||
) {
|
|
||||||
if (builderSpec in localBuilders) return false;
|
|
||||||
const parsed = npa(builderSpec);
|
|
||||||
const parsedVersion = parseVersionSafe(parsed.rawSpec);
|
|
||||||
|
|
||||||
// Skip install of Runtimes that are part of Vercel CLI's `dependencies`
|
|
||||||
if (isBundledBuilder(parsed, cliPkg)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip install of already installed Runtime with exact version match
|
|
||||||
if (
|
|
||||||
parsed.name &&
|
|
||||||
parsed.type === 'version' &&
|
|
||||||
parsedVersion &&
|
|
||||||
buildersPkg.dependencies &&
|
|
||||||
parsedVersion.version == buildersPkg.dependencies[parsed.name]
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Install a list of builders to the cache directory.
|
|
||||||
*/
|
|
||||||
export async function installBuilders(
|
|
||||||
packagesSet: Set<string>,
|
|
||||||
output: Output,
|
|
||||||
builderDir?: string
|
|
||||||
): Promise<void> {
|
|
||||||
const packages = Array.from(packagesSet);
|
|
||||||
if (
|
|
||||||
packages.length === 0 ||
|
|
||||||
(packages.length === 1 &&
|
|
||||||
Object.hasOwnProperty.call(localBuilders, packages[0]))
|
|
||||||
) {
|
|
||||||
// Static deployment, no builders to install
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!builderDir) {
|
|
||||||
builderDir = await builderDirPromise;
|
|
||||||
}
|
|
||||||
const buildersPkgPath = join(builderDir, 'package.json');
|
|
||||||
const buildersPkgBefore = await readJSON(buildersPkgPath);
|
|
||||||
const depsBefore = {
|
|
||||||
...buildersPkgBefore.devDependencies,
|
|
||||||
...buildersPkgBefore.dependencies,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filter out any packages that come packaged with Vercel CLI
|
|
||||||
const packagesToInstall = packages.filter(p =>
|
|
||||||
filterPackage(p, buildersPkgBefore, cliPkg)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (packagesToInstall.length === 0) {
|
|
||||||
output.debug('No Runtimes need to be installed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
packagesToInstall.push(getBuildUtils(packages));
|
|
||||||
|
|
||||||
await npmInstall(builderDir, output, packagesToInstall, false);
|
|
||||||
|
|
||||||
const updatedPackages: string[] = [];
|
|
||||||
const buildersPkgAfter = await readJSON(buildersPkgPath);
|
|
||||||
const depsAfter = {
|
|
||||||
...buildersPkgAfter.devDependencies,
|
|
||||||
...buildersPkgAfter.dependencies,
|
|
||||||
};
|
|
||||||
for (const [name, version] of Object.entries(depsAfter)) {
|
|
||||||
if (version !== depsBefore[name]) {
|
|
||||||
output.debug(`Runtime "${name}" updated to version \`${version}\``);
|
|
||||||
updatedPackages.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
purgeRequireCache(updatedPackages, builderDir, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function npmInstall(
|
|
||||||
cwd: string,
|
|
||||||
output: Output,
|
|
||||||
packagesToInstall: string[],
|
|
||||||
silent: boolean
|
|
||||||
) {
|
|
||||||
const sortedPackages = packagesToInstall.sort();
|
|
||||||
|
|
||||||
if (!silent) {
|
|
||||||
output.spinner(
|
|
||||||
`Installing ${pluralize(
|
|
||||||
'Runtime',
|
|
||||||
sortedPackages.length
|
|
||||||
)}: ${sortedPackages.join(', ')}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.debug(`Running npm install in ${cwd}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const args = [
|
|
||||||
'install',
|
|
||||||
'--save-exact',
|
|
||||||
'--no-package-lock',
|
|
||||||
'--no-audit',
|
|
||||||
'--no-progress',
|
|
||||||
];
|
|
||||||
if (process.stderr.isTTY) {
|
|
||||||
// Force colors in the npm child process
|
|
||||||
// https://docs.npmjs.com/misc/config#color
|
|
||||||
args.push('--color=always');
|
|
||||||
}
|
|
||||||
args.push(...sortedPackages);
|
|
||||||
const result = await execa('npm', args, {
|
|
||||||
cwd,
|
|
||||||
reject: false,
|
|
||||||
stdio: output.isDebugEnabled() ? 'inherit' : 'pipe',
|
|
||||||
});
|
|
||||||
if (result.failed) {
|
|
||||||
output.stopSpinner();
|
|
||||||
if (result.stdout) {
|
|
||||||
console.log(result.stdout);
|
|
||||||
}
|
|
||||||
if (result.stderr) {
|
|
||||||
console.error(result.stderr);
|
|
||||||
}
|
|
||||||
throw new NowBuildError({
|
|
||||||
message:
|
|
||||||
(result as any).code === 'ENOENT'
|
|
||||||
? `Command not found: ${chalk.cyan(
|
|
||||||
'npm'
|
|
||||||
)}\nPlease ensure that ${cmd('npm')} is properly installed`
|
|
||||||
: 'Failed to install `vercel dev` dependencies',
|
|
||||||
code: 'NPM_INSTALL_ERROR',
|
|
||||||
link: 'https://vercel.link/npm-install-failed-dev',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
output.stopSpinner();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateBuilders(
|
|
||||||
packagesSet: Set<string>,
|
|
||||||
output: Output,
|
|
||||||
builderDir?: string
|
|
||||||
): Promise<string[]> {
|
|
||||||
if (!builderDir) {
|
|
||||||
builderDir = await builderDirPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedPackages: string[] = [];
|
|
||||||
const packages = Array.from(packagesSet);
|
|
||||||
const buildersPkgPath = join(builderDir, 'package.json');
|
|
||||||
const buildersPkgBefore = await readJSON(buildersPkgPath);
|
|
||||||
const depsBefore = {
|
|
||||||
...buildersPkgBefore.devDependencies,
|
|
||||||
...buildersPkgBefore.dependencies,
|
|
||||||
};
|
|
||||||
|
|
||||||
const packagesToUpdate = packages.filter(p => {
|
|
||||||
if (p in localBuilders) return false;
|
|
||||||
|
|
||||||
// If it's a builder that is part of Vercel CLI's
|
|
||||||
// `dependencies` then don't update it
|
|
||||||
if (isBundledBuilder(npa(p), cliPkg)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (packagesToUpdate.length > 0) {
|
|
||||||
packagesToUpdate.push(getBuildUtils(packages));
|
|
||||||
|
|
||||||
await npmInstall(builderDir, output, packagesToUpdate, true);
|
|
||||||
|
|
||||||
const buildersPkgAfter = await readJSON(buildersPkgPath);
|
|
||||||
const depsAfter = {
|
|
||||||
...buildersPkgAfter.devDependencies,
|
|
||||||
...buildersPkgAfter.dependencies,
|
|
||||||
};
|
|
||||||
for (const [name, version] of Object.entries(depsAfter)) {
|
|
||||||
if (version !== depsBefore[name]) {
|
|
||||||
output.debug(`Runtime "${name}" updated to version \`${version}\``);
|
|
||||||
updatedPackages.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
purgeRequireCache(updatedPackages, builderDir, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatedPackages;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a builder from the cache directory.
|
|
||||||
*/
|
|
||||||
export async function getBuilder(
|
|
||||||
builderPkg: string,
|
|
||||||
output: Output,
|
|
||||||
builderDir?: string,
|
|
||||||
isRetry = false
|
|
||||||
): Promise<BuilderWithPackage> {
|
|
||||||
let builderWithPkg: BuilderWithPackage = localBuilders[builderPkg];
|
|
||||||
if (!builderWithPkg) {
|
|
||||||
if (!builderDir) {
|
|
||||||
builderDir = await builderDirPromise;
|
|
||||||
}
|
|
||||||
let requirePath: string;
|
|
||||||
const parsed = npa(builderPkg);
|
|
||||||
|
|
||||||
// First check if it's a bundled Runtime in Vercel CLI's `node_modules`
|
|
||||||
const bundledBuilder = isBundledBuilder(parsed, cliPkg);
|
|
||||||
if (bundledBuilder && parsed.name) {
|
|
||||||
requirePath = parsed.name;
|
|
||||||
} else {
|
|
||||||
const buildersPkg = await readJSON(join(builderDir, 'package.json'));
|
|
||||||
const pkgName = getPackageName(parsed, buildersPkg) || builderPkg;
|
|
||||||
requirePath = join(builderDir, 'node_modules', pkgName);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
output.debug(`Requiring runtime: "${requirePath}"`);
|
|
||||||
const mod = require_(requirePath);
|
|
||||||
const pkg = require_(join(requirePath, 'package.json'));
|
|
||||||
builderWithPkg = {
|
|
||||||
requirePath,
|
|
||||||
builder: Object.freeze(mod),
|
|
||||||
package: Object.freeze(pkg),
|
|
||||||
};
|
|
||||||
} catch (err: unknown) {
|
|
||||||
if (
|
|
||||||
isErrnoException(err) &&
|
|
||||||
err.code === 'MODULE_NOT_FOUND' &&
|
|
||||||
!isRetry
|
|
||||||
) {
|
|
||||||
output.debug(
|
|
||||||
`Attempted to require ${requirePath}, but it is not installed`
|
|
||||||
);
|
|
||||||
const pkgSet = new Set([builderPkg]);
|
|
||||||
await installBuilders(pkgSet, output, builderDir);
|
|
||||||
|
|
||||||
// Run `getBuilder()` again now that the builder has been installed
|
|
||||||
return getBuilder(builderPkg, output, builderDir, true);
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's a bundled builder, then cache the require call
|
|
||||||
if (bundledBuilder) {
|
|
||||||
localBuilders[builderPkg] = builderWithPkg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builderWithPkg;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isBundledBuilder(
|
|
||||||
parsed: npa.Result,
|
|
||||||
{ dependencies = {} }: PackageJson
|
|
||||||
): boolean {
|
|
||||||
if (!parsed.name) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const inCliDependencyList = !!dependencies[parsed.name];
|
|
||||||
const inScope = parsed.scope === '@vercel';
|
|
||||||
const isVersionedReference = ['tag', 'version', 'range'].includes(
|
|
||||||
parsed.type
|
|
||||||
);
|
|
||||||
|
|
||||||
return inCliDependencyList && inScope && isVersionedReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPackageName(
|
|
||||||
parsed: npa.Result,
|
|
||||||
buildersPkg: PackageJson
|
|
||||||
): string | null {
|
|
||||||
if (registryTypes.has(parsed.type)) {
|
|
||||||
return parsed.name;
|
|
||||||
}
|
|
||||||
const deps: PackageJson.DependencyMap = {
|
|
||||||
...buildersPkg.devDependencies,
|
|
||||||
...buildersPkg.dependencies,
|
|
||||||
};
|
|
||||||
for (const [name, dep] of Object.entries(deps)) {
|
|
||||||
if (dep === parsed.raw || basename(dep) === basename(parsed.raw)) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function purgeRequireCache(
|
|
||||||
packages: string[],
|
|
||||||
builderDir: string,
|
|
||||||
output: Output
|
|
||||||
) {
|
|
||||||
// The `require()` cache for the builder's assets must be purged
|
|
||||||
const packagesPaths = packages.map(b => join(builderDir, 'node_modules', b));
|
|
||||||
for (const id of Object.keys(require_.cache)) {
|
|
||||||
for (const path of packagesPaths) {
|
|
||||||
if (id.startsWith(path)) {
|
|
||||||
output.debug(`Purging require cache for "${id}"`);
|
|
||||||
delete require_.cache[id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,8 +13,9 @@ import {
|
|||||||
Lambda,
|
Lambda,
|
||||||
FileBlob,
|
FileBlob,
|
||||||
FileFsRef,
|
FileFsRef,
|
||||||
|
normalizePath,
|
||||||
} from '@vercel/build-utils';
|
} from '@vercel/build-utils';
|
||||||
import { isOfficialRuntime } from '@vercel/fs-detectors';
|
import { isStaticRuntime } from '@vercel/fs-detectors';
|
||||||
import plural from 'pluralize';
|
import plural from 'pluralize';
|
||||||
import minimatch from 'minimatch';
|
import minimatch from 'minimatch';
|
||||||
|
|
||||||
@@ -25,7 +26,6 @@ import { relative } from '../path-helpers';
|
|||||||
import { LambdaSizeExceededError } from '../errors-ts';
|
import { LambdaSizeExceededError } from '../errors-ts';
|
||||||
|
|
||||||
import DevServer from './server';
|
import DevServer from './server';
|
||||||
import { getBuilder } from './builder-cache';
|
|
||||||
import {
|
import {
|
||||||
VercelConfig,
|
VercelConfig,
|
||||||
BuildMatch,
|
BuildMatch,
|
||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
import { normalizeRoutes } from '@vercel/routing-utils';
|
import { normalizeRoutes } from '@vercel/routing-utils';
|
||||||
import getUpdateCommand from '../get-update-command';
|
import getUpdateCommand from '../get-update-command';
|
||||||
import { getTitleName } from '../pkg-name';
|
import { getTitleName } from '../pkg-name';
|
||||||
|
import { importBuilders } from '../build/import-builders';
|
||||||
|
|
||||||
interface BuildMessage {
|
interface BuildMessage {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -107,18 +108,18 @@ export async function executeBuild(
|
|||||||
filesRemoved?: string[]
|
filesRemoved?: string[]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const {
|
const {
|
||||||
builderWithPkg: { runInProcess, requirePath, builder, package: pkg },
|
builderWithPkg: { path: requirePath, builder, pkg },
|
||||||
} = match;
|
} = match;
|
||||||
const { entrypoint } = match;
|
const { entrypoint, use } = match;
|
||||||
|
const isStatic = isStaticRuntime(use);
|
||||||
const { envConfigs, cwd: workPath, devCacheDir } = devServer;
|
const { envConfigs, cwd: workPath, devCacheDir } = devServer;
|
||||||
const debug = devServer.output.isDebugEnabled();
|
const debug = devServer.output.isDebugEnabled();
|
||||||
|
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const showBuildTimestamp =
|
const showBuildTimestamp = !isStatic && (!isInitialBuild || debug);
|
||||||
!isOfficialRuntime('static', match.use) && (!isInitialBuild || debug);
|
|
||||||
|
|
||||||
if (showBuildTimestamp) {
|
if (showBuildTimestamp) {
|
||||||
devServer.output.log(`Building ${match.use}:${entrypoint}`);
|
devServer.output.log(`Building ${use}:${entrypoint}`);
|
||||||
devServer.output.debug(
|
devServer.output.debug(
|
||||||
`Using \`${pkg.name}${pkg.version ? `@${pkg.version}` : ''}\``
|
`Using \`${pkg.name}${pkg.version ? `@${pkg.version}` : ''}\``
|
||||||
);
|
);
|
||||||
@@ -129,7 +130,7 @@ export async function executeBuild(
|
|||||||
let result: BuildResult;
|
let result: BuildResult;
|
||||||
|
|
||||||
let { buildProcess } = match;
|
let { buildProcess } = match;
|
||||||
if (!runInProcess && !buildProcess) {
|
if (!isStatic && !buildProcess) {
|
||||||
buildProcess = await createBuildProcess(
|
buildProcess = await createBuildProcess(
|
||||||
match,
|
match,
|
||||||
envConfigs,
|
envConfigs,
|
||||||
@@ -157,7 +158,7 @@ export async function executeBuild(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let buildResultOrOutputs: BuilderOutputs | BuildResult | BuildResultV3;
|
let buildResultOrOutputs;
|
||||||
if (buildProcess) {
|
if (buildProcess) {
|
||||||
buildProcess.send({
|
buildProcess.send({
|
||||||
type: 'build',
|
type: 'build',
|
||||||
@@ -197,16 +198,12 @@ export async function executeBuild(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort out build result to builder v2 shape
|
// Sort out build result to builder v2 shape
|
||||||
if (!builder.version || builder.version === 1) {
|
if (!builder.version || (builder as any).version === 1) {
|
||||||
// `BuilderOutputs` map was returned (Now Builder v1 behavior)
|
// `BuilderOutputs` map was returned (Now Builder v1 behavior)
|
||||||
result = {
|
result = {
|
||||||
output: buildResultOrOutputs as BuilderOutputs,
|
output: buildResultOrOutputs as BuilderOutputs,
|
||||||
routes: [],
|
routes: [],
|
||||||
watch: [],
|
watch: [],
|
||||||
distPath:
|
|
||||||
typeof buildResultOrOutputs.distPath === 'string'
|
|
||||||
? buildResultOrOutputs.distPath
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
} else if (builder.version === 2) {
|
} else if (builder.version === 2) {
|
||||||
result = buildResultOrOutputs as BuildResult;
|
result = buildResultOrOutputs as BuildResult;
|
||||||
@@ -252,7 +249,7 @@ export async function executeBuild(
|
|||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${getTitleName()} CLI does not support builder version ${
|
`${getTitleName()} CLI does not support builder version ${
|
||||||
builder.version
|
(builder as any).version
|
||||||
}.\nPlease run \`${await getUpdateCommand()}\` to update to the latest CLI.`
|
}.\nPlease run \`${await getUpdateCommand()}\` to update to the latest CLI.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -269,7 +266,9 @@ export async function executeBuild(
|
|||||||
const { cleanUrls } = vercelConfig;
|
const { cleanUrls } = vercelConfig;
|
||||||
|
|
||||||
// Mimic fmeta-util and perform file renaming
|
// Mimic fmeta-util and perform file renaming
|
||||||
Object.entries(output).forEach(([path, value]) => {
|
for (const [originalPath, value] of Object.entries(output)) {
|
||||||
|
let path = normalizePath(originalPath);
|
||||||
|
|
||||||
if (cleanUrls && path.endsWith('.html')) {
|
if (cleanUrls && path.endsWith('.html')) {
|
||||||
path = path.slice(0, -5);
|
path = path.slice(0, -5);
|
||||||
|
|
||||||
@@ -284,7 +283,7 @@ export async function executeBuild(
|
|||||||
}
|
}
|
||||||
|
|
||||||
output[path] = value;
|
output[path] = value;
|
||||||
});
|
}
|
||||||
|
|
||||||
// Convert the JSON-ified output map back into their corresponding `File`
|
// Convert the JSON-ified output map back into their corresponding `File`
|
||||||
// subclass type instances.
|
// subclass type instances.
|
||||||
@@ -380,7 +379,7 @@ export async function executeBuild(
|
|||||||
if (showBuildTimestamp) {
|
if (showBuildTimestamp) {
|
||||||
const endTime = Date.now();
|
const endTime = Date.now();
|
||||||
devServer.output.log(
|
devServer.output.log(
|
||||||
`Built ${match.use}:${entrypoint} [${ms(endTime - startTime)}]`
|
`Built ${use}:${entrypoint} [${ms(endTime - startTime)}]`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -402,6 +401,8 @@ export async function getBuildMatches(
|
|||||||
|
|
||||||
const noMatches: Builder[] = [];
|
const noMatches: Builder[] = [];
|
||||||
const builds = vercelConfig.builds || [{ src: '**', use: '@vercel/static' }];
|
const builds = vercelConfig.builds || [{ src: '**', use: '@vercel/static' }];
|
||||||
|
const builderSpecs = new Set(builds.map(b => b.use).filter(Boolean));
|
||||||
|
const buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
|
||||||
|
|
||||||
for (const buildConfig of builds) {
|
for (const buildConfig of builds) {
|
||||||
let { src = '**', use, config = {} } = buildConfig;
|
let { src = '**', use, config = {} } = buildConfig;
|
||||||
@@ -436,6 +437,8 @@ export async function getBuildMatches(
|
|||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
src = relative(cwd, file);
|
src = relative(cwd, file);
|
||||||
|
|
||||||
|
const entrypoint = mapToEntrypoint.get(src) || src;
|
||||||
|
|
||||||
// Remove the output directory prefix
|
// Remove the output directory prefix
|
||||||
if (config.zeroConfig && config.outputDirectory) {
|
if (config.zeroConfig && config.outputDirectory) {
|
||||||
const outputMatch = config.outputDirectory + '/';
|
const outputMatch = config.outputDirectory + '/';
|
||||||
@@ -444,11 +447,15 @@ export async function getBuildMatches(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const builderWithPkg = await getBuilder(use, output);
|
const builderWithPkg = buildersWithPkgs.get(use);
|
||||||
|
if (!builderWithPkg) {
|
||||||
|
throw new Error(`Failed to load Builder "${use}"`);
|
||||||
|
}
|
||||||
|
|
||||||
matches.push({
|
matches.push({
|
||||||
...buildConfig,
|
...buildConfig,
|
||||||
src,
|
src,
|
||||||
entrypoint: mapToEntrypoint.get(src) || src,
|
entrypoint,
|
||||||
builderWithPkg,
|
builderWithPkg,
|
||||||
buildOutput: {},
|
buildOutput: {},
|
||||||
buildResults: new Map(),
|
buildResults: new Map(),
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { parse } from 'url';
|
import { parse } from 'url';
|
||||||
import { ListenSpec } from './types';
|
import { ListenSpec } from './types';
|
||||||
|
|
||||||
export default function parseListen(
|
export function parseListen(str: string, defaultPort = 3000): ListenSpec {
|
||||||
str: string,
|
|
||||||
defaultPort = 3000
|
|
||||||
): ListenSpec {
|
|
||||||
let port = Number(str);
|
let port = Number(str);
|
||||||
|
|
||||||
if (!isNaN(port)) {
|
if (!isNaN(port)) {
|
||||||
@@ -50,3 +47,7 @@ export default function parseListen(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function replaceLocalhost(address: string): string {
|
||||||
|
return address.replace('[::]', 'localhost').replace('0.0.0.0', 'localhost');
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import ms from 'ms';
|
|
||||||
import url, { URL } from 'url';
|
import url, { URL } from 'url';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
@@ -32,6 +31,7 @@ import {
|
|||||||
} from '@vercel/routing-utils';
|
} from '@vercel/routing-utils';
|
||||||
import {
|
import {
|
||||||
Builder,
|
Builder,
|
||||||
|
cloneEnv,
|
||||||
Env,
|
Env,
|
||||||
StartDevServerResult,
|
StartDevServerResult,
|
||||||
FileFsRef,
|
FileFsRef,
|
||||||
@@ -62,11 +62,6 @@ import { devRouter, getRoutesTypes } from './router';
|
|||||||
import getMimeType from './mime-type';
|
import getMimeType from './mime-type';
|
||||||
import { executeBuild, getBuildMatches, shutdownBuilder } from './builder';
|
import { executeBuild, getBuildMatches, shutdownBuilder } from './builder';
|
||||||
import { generateErrorMessage, generateHttpStatusDescription } from './errors';
|
import { generateErrorMessage, generateHttpStatusDescription } from './errors';
|
||||||
import {
|
|
||||||
installBuilders,
|
|
||||||
updateBuilders,
|
|
||||||
builderDirPromise,
|
|
||||||
} from './builder-cache';
|
|
||||||
|
|
||||||
// HTML templates
|
// HTML templates
|
||||||
import errorTemplate from './templates/error';
|
import errorTemplate from './templates/error';
|
||||||
@@ -101,6 +96,9 @@ import {
|
|||||||
isError,
|
isError,
|
||||||
isSpawnError,
|
isSpawnError,
|
||||||
} from '../is-error';
|
} from '../is-error';
|
||||||
|
import isURL from './is-url';
|
||||||
|
import { pickOverrides } from '../projects/project-settings';
|
||||||
|
import { replaceLocalhost } from './parse-listen';
|
||||||
|
|
||||||
const frontendRuntimeSet = new Set(
|
const frontendRuntimeSet = new Set(
|
||||||
frameworkList.map(f => f.useRuntime?.use || '@vercel/static-build')
|
frameworkList.map(f => f.useRuntime?.use || '@vercel/static-build')
|
||||||
@@ -133,9 +131,19 @@ export default class DevServer {
|
|||||||
public proxy: httpProxy;
|
public proxy: httpProxy;
|
||||||
public envConfigs: EnvConfigs;
|
public envConfigs: EnvConfigs;
|
||||||
public files: BuilderInputs;
|
public files: BuilderInputs;
|
||||||
public address: string;
|
|
||||||
public devCacheDir: string;
|
|
||||||
|
|
||||||
|
private _address: URL | undefined;
|
||||||
|
public get address(): URL {
|
||||||
|
if (!this._address) {
|
||||||
|
throw new Error(
|
||||||
|
'Invalid access to `address` because `start` has not yet populated `this.address`.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this._address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public devCacheDir: string;
|
||||||
|
private currentDevCommand?: string;
|
||||||
private caseSensitive: boolean;
|
private caseSensitive: boolean;
|
||||||
private apiDir: string | null;
|
private apiDir: string | null;
|
||||||
private apiExtensions: Set<string>;
|
private apiExtensions: Set<string>;
|
||||||
@@ -149,17 +157,15 @@ export default class DevServer {
|
|||||||
private watchAggregationTimeout: number;
|
private watchAggregationTimeout: number;
|
||||||
private filter: (path: string) => boolean;
|
private filter: (path: string) => boolean;
|
||||||
private podId: string;
|
private podId: string;
|
||||||
private devCommand?: string;
|
|
||||||
private devProcess?: ChildProcess;
|
private devProcess?: ChildProcess;
|
||||||
private devProcessPort?: number;
|
private devProcessOrigin?: string;
|
||||||
private devServerPids: Set<number>;
|
private devServerPids: Set<number>;
|
||||||
|
private originalProjectSettings?: ProjectSettings;
|
||||||
private projectSettings?: ProjectSettings;
|
private projectSettings?: ProjectSettings;
|
||||||
|
|
||||||
private vercelConfigWarning: boolean;
|
private vercelConfigWarning: boolean;
|
||||||
private getVercelConfigPromise: Promise<VercelConfig> | null;
|
private getVercelConfigPromise: Promise<VercelConfig> | null;
|
||||||
private blockingBuildsPromise: Promise<void> | null;
|
private blockingBuildsPromise: Promise<void> | null;
|
||||||
private updateBuildersPromise: Promise<void> | null;
|
|
||||||
private updateBuildersTimeout: NodeJS.Timeout | undefined;
|
|
||||||
private startPromise: Promise<void> | null;
|
private startPromise: Promise<void> | null;
|
||||||
|
|
||||||
private systemEnvValues: string[];
|
private systemEnvValues: string[];
|
||||||
@@ -172,17 +178,22 @@ export default class DevServer {
|
|||||||
this.systemEnvValues = options.systemEnvValues || [];
|
this.systemEnvValues = options.systemEnvValues || [];
|
||||||
this.projectEnvs = options.projectEnvs || [];
|
this.projectEnvs = options.projectEnvs || [];
|
||||||
this.files = {};
|
this.files = {};
|
||||||
this.address = '';
|
this.originalProjectSettings = options.projectSettings;
|
||||||
this.devCommand = options.devCommand;
|
|
||||||
this.projectSettings = options.projectSettings;
|
this.projectSettings = options.projectSettings;
|
||||||
this.caseSensitive = false;
|
this.caseSensitive = false;
|
||||||
this.apiDir = null;
|
this.apiDir = null;
|
||||||
this.apiExtensions = new Set();
|
this.apiExtensions = new Set();
|
||||||
|
|
||||||
this.proxy = httpProxy.createProxyServer({
|
this.proxy = httpProxy.createProxyServer({
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
ws: true,
|
ws: true,
|
||||||
xfwd: true,
|
xfwd: true,
|
||||||
});
|
});
|
||||||
|
this.proxy.on('proxyRes', proxyRes => {
|
||||||
|
// override "server" header, like production
|
||||||
|
proxyRes.headers['server'] = 'Vercel';
|
||||||
|
});
|
||||||
|
|
||||||
this.server = http.createServer(this.devServerHandler);
|
this.server = http.createServer(this.devServerHandler);
|
||||||
this.server.timeout = 0; // Disable timeout
|
this.server.timeout = 0; // Disable timeout
|
||||||
this.stopping = false;
|
this.stopping = false;
|
||||||
@@ -193,7 +204,6 @@ export default class DevServer {
|
|||||||
this.vercelConfigWarning = false;
|
this.vercelConfigWarning = false;
|
||||||
this.getVercelConfigPromise = null;
|
this.getVercelConfigPromise = null;
|
||||||
this.blockingBuildsPromise = null;
|
this.blockingBuildsPromise = null;
|
||||||
this.updateBuildersPromise = null;
|
|
||||||
this.startPromise = null;
|
this.startPromise = null;
|
||||||
|
|
||||||
this.watchAggregationId = null;
|
this.watchAggregationId = null;
|
||||||
@@ -475,33 +485,6 @@ export default class DevServer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async invalidateBuildMatches(
|
|
||||||
vercelConfig: VercelConfig,
|
|
||||||
updatedBuilders: string[]
|
|
||||||
): Promise<void> {
|
|
||||||
if (updatedBuilders.length === 0) {
|
|
||||||
this.output.debug('No builders were updated');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete any build matches that have the old builder required already
|
|
||||||
for (const buildMatch of this.buildMatches.values()) {
|
|
||||||
const {
|
|
||||||
src,
|
|
||||||
builderWithPkg: { package: pkg },
|
|
||||||
} = buildMatch;
|
|
||||||
if (isOfficialRuntime('static', pkg.name)) continue;
|
|
||||||
if (pkg.name && updatedBuilders.includes(pkg.name)) {
|
|
||||||
shutdownBuilder(buildMatch, this.output);
|
|
||||||
this.buildMatches.delete(src);
|
|
||||||
this.output.debug(`Invalidated build match for "${src}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-add the build matches that were just removed, but with the new builder
|
|
||||||
await this.updateBuildMatches(vercelConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLocalEnv(fileName: string, base?: Env): Promise<Env> {
|
async getLocalEnv(fileName: string, base?: Env): Promise<Env> {
|
||||||
// TODO: use the file watcher to only invalidate the env `dotfile`
|
// TODO: use the file watcher to only invalidate the env `dotfile`
|
||||||
// once a change to the `fileName` occurs
|
// once a change to the `fileName` occurs
|
||||||
@@ -549,6 +532,23 @@ export default class DevServer {
|
|||||||
return this.getVercelConfigPromise;
|
return this.getVercelConfigPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get devCommand() {
|
||||||
|
if (this.projectSettings?.devCommand) {
|
||||||
|
return this.projectSettings.devCommand;
|
||||||
|
} else if (this.projectSettings?.framework) {
|
||||||
|
const frameworkSlug = this.projectSettings.framework;
|
||||||
|
const framework = frameworkList.find(f => f.slug === frameworkSlug);
|
||||||
|
|
||||||
|
if (framework) {
|
||||||
|
const defaults = framework.settings.devCommand.value;
|
||||||
|
if (defaults) {
|
||||||
|
return defaults;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
async _getVercelConfig(): Promise<VercelConfig> {
|
async _getVercelConfig(): Promise<VercelConfig> {
|
||||||
const configPath = getVercelConfigPath(this.cwd);
|
const configPath = getVercelConfigPath(this.cwd);
|
||||||
|
|
||||||
@@ -563,6 +563,12 @@ export default class DevServer {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
await this.validateVercelConfig(vercelConfig);
|
await this.validateVercelConfig(vercelConfig);
|
||||||
|
|
||||||
|
this.projectSettings = {
|
||||||
|
...this.originalProjectSettings,
|
||||||
|
...pickOverrides(vercelConfig),
|
||||||
|
};
|
||||||
|
|
||||||
const { error: routeError, routes: maybeRoutes } =
|
const { error: routeError, routes: maybeRoutes } =
|
||||||
getTransformedRoutes(vercelConfig);
|
getTransformedRoutes(vercelConfig);
|
||||||
if (routeError) {
|
if (routeError) {
|
||||||
@@ -682,7 +688,7 @@ export default class DevServer {
|
|||||||
this.projectEnvs || [],
|
this.projectEnvs || [],
|
||||||
this.systemEnvValues || [],
|
this.systemEnvValues || [],
|
||||||
this.projectSettings?.autoExposeSystemEnvs,
|
this.projectSettings?.autoExposeSystemEnvs,
|
||||||
new URL(this.address).host
|
this.address.host
|
||||||
);
|
);
|
||||||
|
|
||||||
allEnv = { ...cloudEnv };
|
allEnv = { ...cloudEnv };
|
||||||
@@ -703,6 +709,11 @@ export default class DevServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.envConfigs = { buildEnv, runEnv, allEnv };
|
this.envConfigs = { buildEnv, runEnv, allEnv };
|
||||||
|
|
||||||
|
// If the `devCommand` was modified via project settings
|
||||||
|
// overrides then the dev process needs to be restarted
|
||||||
|
await this.runDevCommand();
|
||||||
|
|
||||||
return vercelConfig;
|
return vercelConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -812,7 +823,7 @@ export default class DevServer {
|
|||||||
injectSystemValuesInDotenv(env: Env): Env {
|
injectSystemValuesInDotenv(env: Env): Env {
|
||||||
for (const name of Object.keys(env)) {
|
for (const name of Object.keys(env)) {
|
||||||
if (name === 'VERCEL_URL') {
|
if (name === 'VERCEL_URL') {
|
||||||
env['VERCEL_URL'] = new URL(this.address).host;
|
env['VERCEL_URL'] = this.address.host;
|
||||||
} else if (name === 'VERCEL_REGION') {
|
} else if (name === 'VERCEL_REGION') {
|
||||||
env['VERCEL_REGION'] = 'dev1';
|
env['VERCEL_REGION'] = 'dev1';
|
||||||
}
|
}
|
||||||
@@ -885,9 +896,7 @@ export default class DevServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.address = address
|
this._address = new URL(replaceLocalhost(address));
|
||||||
.replace('[::]', 'localhost')
|
|
||||||
.replace('127.0.0.1', 'localhost');
|
|
||||||
|
|
||||||
const vercelConfig = await this.getVercelConfig();
|
const vercelConfig = await this.getVercelConfig();
|
||||||
const devCommandPromise = this.runDevCommand();
|
const devCommandPromise = this.runDevCommand();
|
||||||
@@ -904,30 +913,8 @@ export default class DevServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const builders = new Set<string>(
|
|
||||||
(vercelConfig.builds || [])
|
|
||||||
.filter((b: Builder) => b.use)
|
|
||||||
.map((b: Builder) => b.use)
|
|
||||||
);
|
|
||||||
|
|
||||||
await installBuilders(builders, this.output);
|
|
||||||
await this.updateBuildMatches(vercelConfig, true);
|
await this.updateBuildMatches(vercelConfig, true);
|
||||||
|
|
||||||
// Updating builders happens lazily, and any builders that were updated
|
|
||||||
// get their "build matches" invalidated so that the new version is used.
|
|
||||||
this.updateBuildersTimeout = setTimeout(() => {
|
|
||||||
this.updateBuildersPromise = updateBuilders(builders, this.output)
|
|
||||||
.then(updatedBuilders => {
|
|
||||||
this.updateBuildersPromise = null;
|
|
||||||
this.invalidateBuildMatches(vercelConfig, updatedBuilders);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
this.updateBuildersPromise = null;
|
|
||||||
this.output.prettyError(err);
|
|
||||||
this.output.debug(err.stack);
|
|
||||||
});
|
|
||||||
}, ms('30s'));
|
|
||||||
|
|
||||||
// Builders that do not define a `shouldServe()` function need to be
|
// Builders that do not define a `shouldServe()` function need to be
|
||||||
// executed at boot-up time in order to get the initial assets and/or routes
|
// executed at boot-up time in order to get the initial assets and/or routes
|
||||||
// that can be served by the builder.
|
// that can be served by the builder.
|
||||||
@@ -977,37 +964,37 @@ export default class DevServer {
|
|||||||
// Configure the server to forward WebSocket "upgrade" events to the proxy.
|
// Configure the server to forward WebSocket "upgrade" events to the proxy.
|
||||||
this.server.on('upgrade', async (req, socket, head) => {
|
this.server.on('upgrade', async (req, socket, head) => {
|
||||||
await this.startPromise;
|
await this.startPromise;
|
||||||
if (!this.devProcessPort) {
|
if (!this.devProcessOrigin) {
|
||||||
this.output.debug(
|
this.output.debug(
|
||||||
`Detected "upgrade" event, but closing socket because no frontend dev server is running`
|
`Detected "upgrade" event, but closing socket because no frontend dev server is running`
|
||||||
);
|
);
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const target = `http://127.0.0.1:${this.devProcessPort}`;
|
const target = this.devProcessOrigin;
|
||||||
this.output.debug(`Detected "upgrade" event, proxying to ${target}`);
|
this.output.debug(`Detected "upgrade" event, proxying to ${target}`);
|
||||||
this.proxy.ws(req, socket, head, { target });
|
this.proxy.ws(req, socket, head, { target });
|
||||||
});
|
});
|
||||||
|
|
||||||
await devCommandPromise;
|
await devCommandPromise;
|
||||||
|
|
||||||
this.output.ready(`Available at ${link(this.address)}`);
|
let addressFormatted = this.address.toString();
|
||||||
|
if (this.address.pathname === '/' && this.address.protocol === 'http:') {
|
||||||
|
// log address without trailing slash to maintain backwards compatibility
|
||||||
|
addressFormatted = addressFormatted.replace(/\/$/, '');
|
||||||
|
}
|
||||||
|
this.output.ready(`Available at ${link(addressFormatted)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuts down the `vercel dev` server, and cleans up any temporary resources.
|
* Shuts down the `vercel dev` server, and cleans up any temporary resources.
|
||||||
*/
|
*/
|
||||||
async stop(exitCode?: number): Promise<void> {
|
async stop(exitCode?: number): Promise<void> {
|
||||||
const { devProcess } = this;
|
|
||||||
const { debug } = this.output;
|
|
||||||
if (this.stopping) return;
|
if (this.stopping) return;
|
||||||
|
|
||||||
this.stopping = true;
|
this.stopping = true;
|
||||||
|
|
||||||
if (typeof this.updateBuildersTimeout !== 'undefined') {
|
const { devProcess } = this;
|
||||||
clearTimeout(this.updateBuildersTimeout);
|
const { debug } = this.output;
|
||||||
}
|
|
||||||
|
|
||||||
const ops: Promise<any>[] = [];
|
const ops: Promise<any>[] = [];
|
||||||
|
|
||||||
for (const match of this.buildMatches.values()) {
|
for (const match of this.buildMatches.values()) {
|
||||||
@@ -1025,18 +1012,10 @@ export default class DevServer {
|
|||||||
ops.push(this.watcher.close());
|
ops.push(this.watcher.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.updateBuildersPromise) {
|
|
||||||
debug(`Waiting for builders update to complete`);
|
|
||||||
ops.push(this.updateBuildersPromise);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const pid of this.devServerPids) {
|
for (const pid of this.devServerPids) {
|
||||||
ops.push(this.killBuilderDevServer(pid));
|
ops.push(this.killBuilderDevServer(pid));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the builders module cache is created
|
|
||||||
ops.push(builderDirPromise);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(ops);
|
await Promise.all(ops);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
@@ -1435,8 +1414,9 @@ export default class DevServer {
|
|||||||
// the middleware server for every HTTP request?
|
// the middleware server for every HTTP request?
|
||||||
const { envConfigs, files, devCacheDir, cwd: workPath } = this;
|
const { envConfigs, files, devCacheDir, cwd: workPath } = this;
|
||||||
try {
|
try {
|
||||||
startMiddlewareResult =
|
const { builder } = middleware.builderWithPkg;
|
||||||
await middleware.builderWithPkg.builder.startDevServer?.({
|
if (builder.version === 3) {
|
||||||
|
startMiddlewareResult = await builder.startDevServer?.({
|
||||||
files,
|
files,
|
||||||
entrypoint: middleware.entrypoint,
|
entrypoint: middleware.entrypoint,
|
||||||
workPath,
|
workPath,
|
||||||
@@ -1450,6 +1430,7 @@ export default class DevServer {
|
|||||||
buildEnv: { ...envConfigs.buildEnv },
|
buildEnv: { ...envConfigs.buildEnv },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (startMiddlewareResult) {
|
if (startMiddlewareResult) {
|
||||||
const { port, pid } = startMiddlewareResult;
|
const { port, pid } = startMiddlewareResult;
|
||||||
@@ -1530,15 +1511,34 @@ export default class DevServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rewritePath) {
|
if (rewritePath) {
|
||||||
// TODO: add validation?
|
|
||||||
debug(`Detected rewrite path from middleware: "${rewritePath}"`);
|
debug(`Detected rewrite path from middleware: "${rewritePath}"`);
|
||||||
prevUrl = rewritePath;
|
prevUrl = rewritePath;
|
||||||
|
|
||||||
// Retain orginal pathname, but override query parameters from the rewrite
|
|
||||||
const beforeRewriteUrl = req.url || '/';
|
const beforeRewriteUrl = req.url || '/';
|
||||||
const rewriteUrlParsed = url.parse(beforeRewriteUrl);
|
|
||||||
rewriteUrlParsed.search = url.parse(rewritePath).search;
|
if (isURL(rewritePath)) {
|
||||||
req.url = url.format(rewriteUrlParsed);
|
const rewriteUrlParsed = new URL(rewritePath);
|
||||||
|
|
||||||
|
// `this.address` already has localhost normalized from ip4 and ip6 values
|
||||||
|
if (this.address.origin === rewriteUrlParsed.origin) {
|
||||||
|
// remove origin, leaving the path
|
||||||
|
req.url =
|
||||||
|
rewritePath.slice(rewriteUrlParsed.origin.length) || '/';
|
||||||
|
prevUrl = req.url;
|
||||||
|
} else {
|
||||||
|
// Proxy to absolute URL with different origin
|
||||||
|
debug(`ProxyPass: ${rewritePath}`);
|
||||||
|
this.setResponseHeaders(res, requestId);
|
||||||
|
proxyPass(req, res, rewritePath, this, requestId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Retain orginal pathname, but override query parameters from the rewrite
|
||||||
|
const rewriteUrlParsed = url.parse(beforeRewriteUrl);
|
||||||
|
rewriteUrlParsed.search = url.parse(rewritePath).search;
|
||||||
|
req.url = url.format(rewriteUrlParsed);
|
||||||
|
}
|
||||||
|
|
||||||
debug(
|
debug(
|
||||||
`Rewrote incoming HTTP URL from "${beforeRewriteUrl}" to "${req.url}"`
|
`Rewrote incoming HTTP URL from "${beforeRewriteUrl}" to "${req.url}"`
|
||||||
);
|
);
|
||||||
@@ -1587,12 +1587,16 @@ export default class DevServer {
|
|||||||
missRoutes,
|
missRoutes,
|
||||||
phase
|
phase
|
||||||
);
|
);
|
||||||
prevUrl =
|
|
||||||
routeResult.continue && routeResult.dest
|
if (routeResult.continue) {
|
||||||
? getReqUrl(routeResult)
|
if (routeResult.dest) {
|
||||||
: req.url;
|
prevUrl = getReqUrl(routeResult);
|
||||||
prevHeaders =
|
}
|
||||||
routeResult.continue && routeResult.headers ? routeResult.headers : {};
|
|
||||||
|
if (routeResult.headers) {
|
||||||
|
prevHeaders = routeResult.headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (routeResult.isDestUrl) {
|
if (routeResult.isDestUrl) {
|
||||||
// Mix the `routes` result dest query params into the req path
|
// Mix the `routes` result dest query params into the req path
|
||||||
@@ -1765,8 +1769,8 @@ export default class DevServer {
|
|||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
// If the dev command is started, then proxy to it
|
// If the dev command is started, then proxy to it
|
||||||
if (this.devProcessPort) {
|
if (this.devProcessOrigin) {
|
||||||
const upstream = `http://127.0.0.1:${this.devProcessPort}`;
|
const upstream = this.devProcessOrigin;
|
||||||
debug(`Proxying to frontend dev server: ${upstream}`);
|
debug(`Proxying to frontend dev server: ${upstream}`);
|
||||||
|
|
||||||
// Add the Vercel platform proxy request headers
|
// Add the Vercel platform proxy request headers
|
||||||
@@ -1839,8 +1843,8 @@ export default class DevServer {
|
|||||||
// up a single-serve dev HTTP server that vercel dev will proxy this HTTP request
|
// up a single-serve dev HTTP server that vercel dev will proxy this HTTP request
|
||||||
// to. Once the proxied request is finished, vercel dev shuts down the dev
|
// to. Once the proxied request is finished, vercel dev shuts down the dev
|
||||||
// server child process.
|
// server child process.
|
||||||
const { builder, package: builderPkg } = match.builderWithPkg;
|
const { builder, pkg: builderPkg } = match.builderWithPkg;
|
||||||
if (typeof builder.startDevServer === 'function') {
|
if (builder.version === 3 && typeof builder.startDevServer === 'function') {
|
||||||
let devServerResult: StartDevServerResult = null;
|
let devServerResult: StartDevServerResult = null;
|
||||||
try {
|
try {
|
||||||
const { envConfigs, files, devCacheDir, cwd: workPath } = this;
|
const { envConfigs, files, devCacheDir, cwd: workPath } = this;
|
||||||
@@ -1856,7 +1860,9 @@ export default class DevServer {
|
|||||||
devCacheDir,
|
devCacheDir,
|
||||||
env: {
|
env: {
|
||||||
...envConfigs.runEnv,
|
...envConfigs.runEnv,
|
||||||
VERCEL_BUILDER_DEBUG: this.output.debugEnabled ? '1' : undefined,
|
VERCEL_DEBUG_PREFIX: this.output.debugEnabled
|
||||||
|
? '[builder]'
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
buildEnv: { ...envConfigs.buildEnv },
|
buildEnv: { ...envConfigs.buildEnv },
|
||||||
},
|
},
|
||||||
@@ -1943,7 +1949,7 @@ export default class DevServer {
|
|||||||
// - when there is no asset
|
// - when there is no asset
|
||||||
// - when the asset is not a Lambda (the dev server must take care of all static files)
|
// - when the asset is not a Lambda (the dev server must take care of all static files)
|
||||||
if (
|
if (
|
||||||
this.devProcessPort &&
|
this.devProcessOrigin &&
|
||||||
(!foundAsset || (foundAsset && foundAsset.asset.type !== 'Lambda'))
|
(!foundAsset || (foundAsset && foundAsset.asset.type !== 'Lambda'))
|
||||||
) {
|
) {
|
||||||
debug('Proxying to frontend dev server');
|
debug('Proxying to frontend dev server');
|
||||||
@@ -1955,14 +1961,7 @@ export default class DevServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setResponseHeaders(res, requestId);
|
this.setResponseHeaders(res, requestId);
|
||||||
return proxyPass(
|
return proxyPass(req, res, this.devProcessOrigin, this, requestId, false);
|
||||||
req,
|
|
||||||
res,
|
|
||||||
`http://127.0.0.1:${this.devProcessPort}`,
|
|
||||||
this,
|
|
||||||
requestId,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!foundAsset) {
|
if (!foundAsset) {
|
||||||
@@ -2203,28 +2202,43 @@ export default class DevServer {
|
|||||||
async runDevCommand() {
|
async runDevCommand() {
|
||||||
const { devCommand, cwd } = this;
|
const { devCommand, cwd } = this;
|
||||||
|
|
||||||
|
if (devCommand === this.currentDevCommand) {
|
||||||
|
// `devCommand` has not changed, so don't restart frontend dev process
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentDevCommand = devCommand;
|
||||||
|
|
||||||
if (!devCommand) {
|
if (!devCommand) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.devProcess) {
|
||||||
|
await treeKill(this.devProcess.pid);
|
||||||
|
}
|
||||||
|
|
||||||
this.output.log(
|
this.output.log(
|
||||||
`Running Dev Command ${chalk.cyan.bold(`“${devCommand}”`)}`
|
`Running Dev Command ${chalk.cyan.bold(`“${devCommand}”`)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const port = await getPort();
|
const port = await getPort();
|
||||||
|
|
||||||
const env: Env = {
|
const env: Env = cloneEnv(
|
||||||
// Because of child process 'pipe' below, isTTY will be false.
|
{
|
||||||
// Most frameworks use `chalk`/`supports-color` so we enable it anyway.
|
// Because of child process 'pipe' below, isTTY will be false.
|
||||||
FORCE_COLOR: process.stdout.isTTY ? '1' : '0',
|
// Most frameworks use `chalk`/`supports-color` so we enable it anyway.
|
||||||
// Prevent framework dev servers from automatically opening a web
|
FORCE_COLOR: process.stdout.isTTY ? '1' : '0',
|
||||||
// browser window, since it will not be the port that `vc dev`
|
// Prevent framework dev servers from automatically opening a web
|
||||||
// is listening on and thus will be missing Vercel features.
|
// browser window, since it will not be the port that `vc dev`
|
||||||
BROWSER: 'none',
|
// is listening on and thus will be missing Vercel features.
|
||||||
...process.env,
|
BROWSER: 'none',
|
||||||
...this.envConfigs.allEnv,
|
},
|
||||||
PORT: `${port}`,
|
process.env,
|
||||||
};
|
this.envConfigs.allEnv,
|
||||||
|
{
|
||||||
|
PORT: `${port}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// This is necesary so that the dev command in the Project
|
// This is necesary so that the dev command in the Project
|
||||||
// will work cross-platform (especially Windows).
|
// will work cross-platform (especially Windows).
|
||||||
@@ -2258,7 +2272,6 @@ export default class DevServer {
|
|||||||
|
|
||||||
this.output.debug(`Spawning dev command: ${command}`);
|
this.output.debug(`Spawning dev command: ${command}`);
|
||||||
|
|
||||||
const devPort = new URL(this.address).port;
|
|
||||||
const proxyPort = new RegExp(port.toString(), 'g');
|
const proxyPort = new RegExp(port.toString(), 'g');
|
||||||
const p = spawnCommand(command, {
|
const p = spawnCommand(command, {
|
||||||
stdio: ['inherit', 'pipe', 'pipe'],
|
stdio: ['inherit', 'pipe', 'pipe'],
|
||||||
@@ -2275,17 +2288,16 @@ export default class DevServer {
|
|||||||
p.stdout.setEncoding('utf8');
|
p.stdout.setEncoding('utf8');
|
||||||
|
|
||||||
p.stdout.on('data', (data: string) => {
|
p.stdout.on('data', (data: string) => {
|
||||||
process.stdout.write(data.replace(proxyPort, devPort));
|
process.stdout.write(data.replace(proxyPort, this.address.port));
|
||||||
});
|
});
|
||||||
|
|
||||||
p.on('exit', (code, signal) => {
|
p.on('exit', (code, signal) => {
|
||||||
this.output.debug(`Dev command exited with "${signal || code}"`);
|
this.output.debug(`Dev command exited with "${signal || code}"`);
|
||||||
this.devProcessPort = undefined;
|
this.devProcessOrigin = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
await checkForPort(port, 1000 * 60 * 5);
|
const devProcessHost = await checkForPort(port, 1000 * 60 * 5);
|
||||||
|
this.devProcessOrigin = `http://${devProcessHost}:${port}`;
|
||||||
this.devProcessPort = port;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2573,15 +2585,29 @@ function needsBlockingBuild(buildMatch: BuildMatch): boolean {
|
|||||||
return typeof builder.shouldServe !== 'function';
|
return typeof builder.shouldServe !== 'function';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkForPort(port: number, timeout: number): Promise<void> {
|
async function checkForPort(port: number, timeout: number): Promise<string> {
|
||||||
const opts = { host: '127.0.0.1' };
|
let host;
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
while (!(await isPortReachable(port, opts))) {
|
while (!(host = await getReachableHostOnPort(port))) {
|
||||||
if (Date.now() - start > timeout) {
|
if (Date.now() - start > timeout) {
|
||||||
throw new Error(`Detecting port ${port} timed out after ${timeout}ms`);
|
break;
|
||||||
}
|
}
|
||||||
await sleep(100);
|
await sleep(100);
|
||||||
}
|
}
|
||||||
|
if (!host) {
|
||||||
|
throw new Error(`Detecting port ${port} timed out after ${timeout}ms`);
|
||||||
|
}
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getReachableHostOnPort(port: number): Promise<string | false> {
|
||||||
|
const optsIpv4 = { host: '127.0.0.1' };
|
||||||
|
const optsIpv6 = { host: '::1' };
|
||||||
|
const results = await Promise.all([
|
||||||
|
isPortReachable(port, optsIpv6).then(r => r && `[${optsIpv6.host}]`),
|
||||||
|
isPortReachable(port, optsIpv4).then(r => r && optsIpv4.host),
|
||||||
|
]);
|
||||||
|
return results.find(Boolean) || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterFrontendBuilds(build: Builder) {
|
function filterFrontendBuilds(build: Builder) {
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import {
|
|
||||||
FileFsRef,
|
|
||||||
BuildOptions,
|
|
||||||
shouldServe as defaultShouldServe,
|
|
||||||
ShouldServeOptions,
|
|
||||||
} from '@vercel/build-utils';
|
|
||||||
import { BuildResult } from './types';
|
|
||||||
|
|
||||||
export const version = 2;
|
|
||||||
|
|
||||||
export function build({
|
|
||||||
files,
|
|
||||||
entrypoint,
|
|
||||||
config: { zeroConfig, outputDirectory },
|
|
||||||
}: BuildOptions): BuildResult {
|
|
||||||
const path =
|
|
||||||
zeroConfig && outputDirectory
|
|
||||||
? `${outputDirectory}/${entrypoint}`
|
|
||||||
: entrypoint;
|
|
||||||
return {
|
|
||||||
output: {
|
|
||||||
[entrypoint]: files[path] as FileFsRef,
|
|
||||||
},
|
|
||||||
routes: [],
|
|
||||||
watch: [path],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldServe(_opts: ShouldServeOptions) {
|
|
||||||
const opts = { ..._opts };
|
|
||||||
let {
|
|
||||||
config: { zeroConfig, outputDirectory },
|
|
||||||
} = opts;
|
|
||||||
|
|
||||||
// Add the output directory prefix
|
|
||||||
if (zeroConfig && outputDirectory) {
|
|
||||||
opts.entrypoint = `${outputDirectory}/${opts.entrypoint}`;
|
|
||||||
opts.requestPath = `${outputDirectory}/${opts.requestPath}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultShouldServe(opts);
|
|
||||||
}
|
|
||||||
@@ -12,18 +12,17 @@ import {
|
|||||||
FileBlob,
|
FileBlob,
|
||||||
FileFsRef,
|
FileFsRef,
|
||||||
Lambda,
|
Lambda,
|
||||||
PackageJson,
|
|
||||||
} from '@vercel/build-utils';
|
} from '@vercel/build-utils';
|
||||||
import { VercelConfig } from '@vercel/client';
|
import { VercelConfig } from '@vercel/client';
|
||||||
import { HandleValue, Route } from '@vercel/routing-utils';
|
import { HandleValue, Route } from '@vercel/routing-utils';
|
||||||
import { Output } from '../output';
|
import { Output } from '../output';
|
||||||
import { ProjectEnvVariable, ProjectSettings } from '../../types';
|
import { ProjectEnvVariable, ProjectSettings } from '../../types';
|
||||||
|
import { BuilderWithPkg } from '../build/import-builders';
|
||||||
|
|
||||||
export { VercelConfig };
|
export { VercelConfig };
|
||||||
|
|
||||||
export interface DevServerOptions {
|
export interface DevServerOptions {
|
||||||
output: Output;
|
output: Output;
|
||||||
devCommand?: string;
|
|
||||||
projectSettings?: ProjectSettings;
|
projectSettings?: ProjectSettings;
|
||||||
systemEnvValues?: string[];
|
systemEnvValues?: string[];
|
||||||
projectEnvs?: ProjectEnvVariable[];
|
projectEnvs?: ProjectEnvVariable[];
|
||||||
@@ -49,7 +48,7 @@ export interface EnvConfigs {
|
|||||||
export interface BuildMatch extends BuildConfig {
|
export interface BuildMatch extends BuildConfig {
|
||||||
entrypoint: string;
|
entrypoint: string;
|
||||||
src: string;
|
src: string;
|
||||||
builderWithPkg: BuilderWithPackage;
|
builderWithPkg: BuilderWithPkg;
|
||||||
buildOutput: BuilderOutputs;
|
buildOutput: BuilderOutputs;
|
||||||
buildResults: Map<string | null, BuildResult>;
|
buildResults: Map<string | null, BuildResult>;
|
||||||
buildTimestamp: number;
|
buildTimestamp: number;
|
||||||
@@ -123,13 +122,6 @@ export interface BuildResultV4 {
|
|||||||
distPath?: string;
|
distPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BuilderWithPackage {
|
|
||||||
runInProcess?: boolean;
|
|
||||||
requirePath: string;
|
|
||||||
builder: Readonly<Builder>;
|
|
||||||
package: Readonly<PackageJson>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HttpHeadersConfig {
|
export interface HttpHeadersConfig {
|
||||||
[name: string]: string;
|
[name: string]: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export class Output {
|
|||||||
link?: string,
|
link?: string,
|
||||||
action = 'Learn More'
|
action = 'Learn More'
|
||||||
) => {
|
) => {
|
||||||
this.print(`${chalk.red(`Error!`)} ${str}\n`);
|
this.print(`${chalk.red(`Error:`)} ${str}\n`);
|
||||||
const details = slug ? `https://err.sh/vercel/${slug}` : link;
|
const details = slug ? `https://err.sh/vercel/${slug}` : link;
|
||||||
if (details) {
|
if (details) {
|
||||||
this.print(`${chalk.bold(action)}: ${renderLink(details)}\n`);
|
this.print(`${chalk.bold(action)}: ${renderLink(details)}\n`);
|
||||||
|
|||||||
@@ -23,5 +23,5 @@ export default function error(
|
|||||||
metric.exception(messages.join('\n')).send();
|
metric.exception(messages.join('\n')).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${chalk.red('Error!')} ${messages.join('\n')}`;
|
return `${chalk.red('Error:')} ${messages.join('\n')}`;
|
||||||
}
|
}
|
||||||
|
|||||||
23
packages/cli/src/util/output/progress.ts
Normal file
23
packages/cli/src/util/output/progress.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
export interface ProgressOptions {
|
||||||
|
width?: number;
|
||||||
|
complete?: string;
|
||||||
|
incomplete?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a raw progress bar string.
|
||||||
|
*/
|
||||||
|
export function progress(
|
||||||
|
current: number,
|
||||||
|
total: number,
|
||||||
|
opts: ProgressOptions = {}
|
||||||
|
): string | null {
|
||||||
|
const { width = 20, complete = '=', incomplete = '-' } = opts;
|
||||||
|
if (total <= 0 || current < 0 || current > total) {
|
||||||
|
// Let the caller decide how to handle out-of-range values
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const unit = total / width;
|
||||||
|
const pos = Math.floor(current / unit);
|
||||||
|
return `${complete.repeat(pos)}${incomplete.repeat(width - pos)}`;
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ import { outputJSON } from 'fs-extra';
|
|||||||
import { Org, Project, ProjectLink } from '../../types';
|
import { Org, Project, ProjectLink } from '../../types';
|
||||||
import { getLinkFromDir, VERCEL_DIR, VERCEL_DIR_PROJECT } from './link';
|
import { getLinkFromDir, VERCEL_DIR, VERCEL_DIR_PROJECT } from './link';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
import { VercelConfig } from '@vercel/client';
|
||||||
|
import { PartialProjectSettings } from '../input/edit-project-settings';
|
||||||
|
|
||||||
export type ProjectLinkAndSettings = ProjectLink & {
|
export type ProjectLinkAndSettings = ProjectLink & {
|
||||||
settings: {
|
settings: {
|
||||||
@@ -51,3 +53,26 @@ export async function writeProjectSettings(
|
|||||||
export async function readProjectSettings(cwd: string) {
|
export async function readProjectSettings(cwd: string) {
|
||||||
return await getLinkFromDir<ProjectLinkAndSettings>(cwd);
|
return await getLinkFromDir<ProjectLinkAndSettings>(cwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function pickOverrides(
|
||||||
|
vercelConfig: VercelConfig
|
||||||
|
): PartialProjectSettings {
|
||||||
|
const overrides: PartialProjectSettings = {};
|
||||||
|
for (const prop of [
|
||||||
|
'buildCommand',
|
||||||
|
'devCommand',
|
||||||
|
'framework',
|
||||||
|
'ignoreCommand',
|
||||||
|
'installCommand',
|
||||||
|
'outputDirectory',
|
||||||
|
] as const) {
|
||||||
|
if (typeof vercelConfig[prop] !== 'undefined') {
|
||||||
|
if (prop === 'ignoreCommand') {
|
||||||
|
overrides.commandForIgnoringBuildStep = vercelConfig[prop];
|
||||||
|
} else {
|
||||||
|
overrides[prop] = vercelConfig[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return overrides;
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,28 +22,28 @@ export async function validateRootDirectory(
|
|||||||
const suffix = errorSuffix ? ` ${errorSuffix}` : '';
|
const suffix = errorSuffix ? ` ${errorSuffix}` : '';
|
||||||
|
|
||||||
if (!pathStat) {
|
if (!pathStat) {
|
||||||
output.print(
|
output.error(
|
||||||
`${chalk.red('Error!')} The provided path ${chalk.cyan(
|
`The provided path ${chalk.cyan(
|
||||||
`“${toHumanPath(path)}”`
|
`“${toHumanPath(path)}”`
|
||||||
)} does not exist.${suffix}\n`
|
)} does not exist.${suffix}`
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pathStat.isDirectory()) {
|
if (!pathStat.isDirectory()) {
|
||||||
output.print(
|
output.error(
|
||||||
`${chalk.red('Error!')} The provided path ${chalk.cyan(
|
`The provided path ${chalk.cyan(
|
||||||
`“${toHumanPath(path)}”`
|
`“${toHumanPath(path)}”`
|
||||||
)} is a file, but expected a directory.${suffix}\n`
|
)} is a file, but expected a directory.${suffix}`
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!path.startsWith(cwd)) {
|
if (!path.startsWith(cwd)) {
|
||||||
output.print(
|
output.error(
|
||||||
`${chalk.red('Error!')} The provided path ${chalk.cyan(
|
`The provided path ${chalk.cyan(
|
||||||
`“${toHumanPath(path)}”`
|
`“${toHumanPath(path)}”`
|
||||||
)} is outside of the project.${suffix}\n`
|
)} is outside of the project.${suffix}`
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ export default async function validatePaths(
|
|||||||
|
|
||||||
// can't deploy more than 1 path
|
// can't deploy more than 1 path
|
||||||
if (paths.length > 1) {
|
if (paths.length > 1) {
|
||||||
output.print(`${chalk.red('Error!')} Can't deploy more than one path.\n`);
|
output.error(`Can't deploy more than one path.`);
|
||||||
return { valid: false, exitCode: 1 };
|
return { valid: false, exitCode: 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,11 +69,7 @@ export default async function validatePaths(
|
|||||||
const pathStat = await stat(path).catch(() => null);
|
const pathStat = await stat(path).catch(() => null);
|
||||||
|
|
||||||
if (!pathStat) {
|
if (!pathStat) {
|
||||||
output.print(
|
output.error(`Could not find ${chalk.cyan(`“${toHumanPath(path)}”`)}`);
|
||||||
`${chalk.red('Error!')} Could not find ${chalk.cyan(
|
|
||||||
`“${toHumanPath(path)}”`
|
|
||||||
)}\n`
|
|
||||||
);
|
|
||||||
return { valid: false, exitCode: 1 };
|
return { valid: false, exitCode: 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "nextjs",
|
"name": "nextjs-node",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next",
|
"dev": "next",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "nextjs",
|
"name": "nextjs-src-dir",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next",
|
"dev": "next",
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"devCommand": "next dev --port $PORT"
|
||||||
|
}
|
||||||
@@ -5,5 +5,6 @@
|
|||||||
"src": "/test",
|
"src": "/test",
|
||||||
"dest": "/?route-param=b"
|
"dest": "/?route-param=b"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"devCommand": "next dev --port $PORT"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export const config = {
|
||||||
|
runtime: 'experimental-edge',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function edge(request: Request, event: Event) {
|
||||||
|
return new Response('heyo');
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<h1>Hello from Index</h1>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
export const config = {
|
||||||
|
runtime: 'experimental-edge',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function edge(request: Request, event: Event) {
|
||||||
|
if (request.url.indexOf('/index.html') > -1) {
|
||||||
|
return new Response(null, {
|
||||||
|
headers: {
|
||||||
|
'x-middleware-rewrite': '/does-not-exist.html',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.url.indexOf('/api/edge') > -1) {
|
||||||
|
return new Response(null, {
|
||||||
|
headers: {
|
||||||
|
'x-middleware-rewrite': '/api/does-not-exist',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,15 @@
|
|||||||
export default req => {
|
export default req => {
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
|
|
||||||
|
const rewriteTo = url.searchParams.get('to');
|
||||||
|
if (rewriteTo) {
|
||||||
|
return new Response(null, {
|
||||||
|
headers: {
|
||||||
|
'x-middleware-rewrite': rewriteTo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (url.pathname === '/') {
|
if (url.pathname === '/') {
|
||||||
// Pass-through "index.html" page
|
// Pass-through "index.html" page
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
This is the original
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This is the overridden!
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "not used - required for zero-config"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"serve": "14.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"devCommand":"serve -p $PORT original"}
|
||||||
@@ -0,0 +1,588 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@zeit/schemas@2.21.0":
|
||||||
|
version "2.21.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@zeit/schemas/-/schemas-2.21.0.tgz#cd242c6551ffb51830049d68d9743ab65b45b820"
|
||||||
|
integrity sha512-/J4WBTpWtQ4itN1rb3ao8LfClmVcmz2pO6oYb7Qd4h7VSqUhIbJIvrykz9Ew1WMg6eFWsKdsMHc5uPbFxqlCpg==
|
||||||
|
|
||||||
|
accepts@~1.3.5:
|
||||||
|
version "1.3.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
|
||||||
|
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
|
||||||
|
dependencies:
|
||||||
|
mime-types "~2.1.34"
|
||||||
|
negotiator "0.6.3"
|
||||||
|
|
||||||
|
ajv@8.11.0:
|
||||||
|
version "8.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
|
||||||
|
integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
|
||||||
|
dependencies:
|
||||||
|
fast-deep-equal "^3.1.1"
|
||||||
|
json-schema-traverse "^1.0.0"
|
||||||
|
require-from-string "^2.0.2"
|
||||||
|
uri-js "^4.2.2"
|
||||||
|
|
||||||
|
ansi-align@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59"
|
||||||
|
integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==
|
||||||
|
dependencies:
|
||||||
|
string-width "^4.1.0"
|
||||||
|
|
||||||
|
ansi-regex@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||||
|
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||||
|
|
||||||
|
ansi-regex@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
|
||||||
|
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
|
||||||
|
|
||||||
|
ansi-styles@^4.1.0:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
|
||||||
|
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
|
||||||
|
dependencies:
|
||||||
|
color-convert "^2.0.1"
|
||||||
|
|
||||||
|
ansi-styles@^6.1.0:
|
||||||
|
version "6.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3"
|
||||||
|
integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==
|
||||||
|
|
||||||
|
arch@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
|
||||||
|
integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
|
||||||
|
|
||||||
|
arg@5.0.2:
|
||||||
|
version "5.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
|
||||||
|
integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
|
||||||
|
|
||||||
|
balanced-match@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
|
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||||
|
|
||||||
|
boxen@7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/boxen/-/boxen-7.0.0.tgz#9e5f8c26e716793fc96edcf7cf754cdf5e3fbf32"
|
||||||
|
integrity sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==
|
||||||
|
dependencies:
|
||||||
|
ansi-align "^3.0.1"
|
||||||
|
camelcase "^7.0.0"
|
||||||
|
chalk "^5.0.1"
|
||||||
|
cli-boxes "^3.0.0"
|
||||||
|
string-width "^5.1.2"
|
||||||
|
type-fest "^2.13.0"
|
||||||
|
widest-line "^4.0.1"
|
||||||
|
wrap-ansi "^8.0.1"
|
||||||
|
|
||||||
|
brace-expansion@^1.1.7:
|
||||||
|
version "1.1.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||||
|
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
||||||
|
dependencies:
|
||||||
|
balanced-match "^1.0.0"
|
||||||
|
concat-map "0.0.1"
|
||||||
|
|
||||||
|
bytes@3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||||
|
integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==
|
||||||
|
|
||||||
|
camelcase@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.0.tgz#fd112621b212126741f998d614cbc2a8623fd174"
|
||||||
|
integrity sha512-JToIvOmz6nhGsUhAYScbo2d6Py5wojjNfoxoc2mEVLUdJ70gJK2gnd+ABY1Tc3sVMyK7QDPtN0T/XdlCQWITyQ==
|
||||||
|
|
||||||
|
chalk-template@0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-0.4.0.tgz#692c034d0ed62436b9062c1707fadcd0f753204b"
|
||||||
|
integrity sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==
|
||||||
|
dependencies:
|
||||||
|
chalk "^4.1.2"
|
||||||
|
|
||||||
|
chalk@5.0.1, chalk@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.0.1.tgz#ca57d71e82bb534a296df63bbacc4a1c22b2a4b6"
|
||||||
|
integrity sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==
|
||||||
|
|
||||||
|
chalk@^4.1.2:
|
||||||
|
version "4.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||||
|
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^4.1.0"
|
||||||
|
supports-color "^7.1.0"
|
||||||
|
|
||||||
|
cli-boxes@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145"
|
||||||
|
integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==
|
||||||
|
|
||||||
|
clipboardy@3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-3.0.0.tgz#f3876247404d334c9ed01b6f269c11d09a5e3092"
|
||||||
|
integrity sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==
|
||||||
|
dependencies:
|
||||||
|
arch "^2.2.0"
|
||||||
|
execa "^5.1.1"
|
||||||
|
is-wsl "^2.2.0"
|
||||||
|
|
||||||
|
color-convert@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
|
||||||
|
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
|
||||||
|
dependencies:
|
||||||
|
color-name "~1.1.4"
|
||||||
|
|
||||||
|
color-name@~1.1.4:
|
||||||
|
version "1.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||||
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
|
||||||
|
compressible@~2.0.16:
|
||||||
|
version "2.0.18"
|
||||||
|
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
|
||||||
|
integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
|
||||||
|
dependencies:
|
||||||
|
mime-db ">= 1.43.0 < 2"
|
||||||
|
|
||||||
|
compression@1.7.4:
|
||||||
|
version "1.7.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
|
||||||
|
integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
|
||||||
|
dependencies:
|
||||||
|
accepts "~1.3.5"
|
||||||
|
bytes "3.0.0"
|
||||||
|
compressible "~2.0.16"
|
||||||
|
debug "2.6.9"
|
||||||
|
on-headers "~1.0.2"
|
||||||
|
safe-buffer "5.1.2"
|
||||||
|
vary "~1.1.2"
|
||||||
|
|
||||||
|
concat-map@0.0.1:
|
||||||
|
version "0.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
|
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||||
|
|
||||||
|
content-disposition@0.5.2:
|
||||||
|
version "0.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
|
||||||
|
integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==
|
||||||
|
|
||||||
|
cross-spawn@^7.0.3:
|
||||||
|
version "7.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||||
|
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||||
|
dependencies:
|
||||||
|
path-key "^3.1.0"
|
||||||
|
shebang-command "^2.0.0"
|
||||||
|
which "^2.0.1"
|
||||||
|
|
||||||
|
debug@2.6.9:
|
||||||
|
version "2.6.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||||
|
dependencies:
|
||||||
|
ms "2.0.0"
|
||||||
|
|
||||||
|
deep-extend@^0.6.0:
|
||||||
|
version "0.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||||
|
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
||||||
|
|
||||||
|
eastasianwidth@^0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
||||||
|
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
||||||
|
|
||||||
|
emoji-regex@^8.0.0:
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||||
|
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||||
|
|
||||||
|
emoji-regex@^9.2.2:
|
||||||
|
version "9.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
||||||
|
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
||||||
|
|
||||||
|
execa@^5.1.1:
|
||||||
|
version "5.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
|
||||||
|
integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
|
||||||
|
dependencies:
|
||||||
|
cross-spawn "^7.0.3"
|
||||||
|
get-stream "^6.0.0"
|
||||||
|
human-signals "^2.1.0"
|
||||||
|
is-stream "^2.0.0"
|
||||||
|
merge-stream "^2.0.0"
|
||||||
|
npm-run-path "^4.0.1"
|
||||||
|
onetime "^5.1.2"
|
||||||
|
signal-exit "^3.0.3"
|
||||||
|
strip-final-newline "^2.0.0"
|
||||||
|
|
||||||
|
fast-deep-equal@^3.1.1:
|
||||||
|
version "3.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
|
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||||
|
|
||||||
|
fast-url-parser@1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d"
|
||||||
|
integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==
|
||||||
|
dependencies:
|
||||||
|
punycode "^1.3.2"
|
||||||
|
|
||||||
|
get-stream@^6.0.0:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
|
||||||
|
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
|
||||||
|
|
||||||
|
has-flag@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||||
|
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||||
|
|
||||||
|
human-signals@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||||
|
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
||||||
|
|
||||||
|
ini@~1.3.0:
|
||||||
|
version "1.3.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
|
||||||
|
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||||
|
|
||||||
|
is-docker@^2.0.0:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
|
||||||
|
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
|
||||||
|
|
||||||
|
is-fullwidth-code-point@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||||
|
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||||
|
|
||||||
|
is-port-reachable@4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-port-reachable/-/is-port-reachable-4.0.0.tgz#dac044091ef15319c8ab2f34604d8794181f8c2d"
|
||||||
|
integrity sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==
|
||||||
|
|
||||||
|
is-stream@^2.0.0:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
|
||||||
|
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
|
||||||
|
|
||||||
|
is-wsl@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
|
||||||
|
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
|
||||||
|
dependencies:
|
||||||
|
is-docker "^2.0.0"
|
||||||
|
|
||||||
|
isexe@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||||
|
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
||||||
|
|
||||||
|
json-schema-traverse@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
|
||||||
|
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
|
||||||
|
|
||||||
|
merge-stream@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||||
|
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
|
||||||
|
|
||||||
|
mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
|
||||||
|
version "1.52.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||||
|
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||||
|
|
||||||
|
mime-db@~1.33.0:
|
||||||
|
version "1.33.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
|
||||||
|
integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==
|
||||||
|
|
||||||
|
mime-types@2.1.18:
|
||||||
|
version "2.1.18"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
|
||||||
|
integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==
|
||||||
|
dependencies:
|
||||||
|
mime-db "~1.33.0"
|
||||||
|
|
||||||
|
mime-types@~2.1.34:
|
||||||
|
version "2.1.35"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||||
|
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||||
|
dependencies:
|
||||||
|
mime-db "1.52.0"
|
||||||
|
|
||||||
|
mimic-fn@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||||
|
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||||
|
|
||||||
|
minimatch@3.0.4:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||||
|
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||||
|
dependencies:
|
||||||
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
|
minimist@^1.2.0:
|
||||||
|
version "1.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||||
|
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||||
|
|
||||||
|
ms@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
|
||||||
|
|
||||||
|
negotiator@0.6.3:
|
||||||
|
version "0.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
||||||
|
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
|
||||||
|
|
||||||
|
npm-run-path@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
|
||||||
|
integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
|
||||||
|
dependencies:
|
||||||
|
path-key "^3.0.0"
|
||||||
|
|
||||||
|
on-headers@~1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
|
||||||
|
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
|
||||||
|
|
||||||
|
onetime@^5.1.2:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
|
||||||
|
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
|
||||||
|
dependencies:
|
||||||
|
mimic-fn "^2.1.0"
|
||||||
|
|
||||||
|
path-is-inside@1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
||||||
|
integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==
|
||||||
|
|
||||||
|
path-key@^3.0.0, path-key@^3.1.0:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
|
||||||
|
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
|
||||||
|
|
||||||
|
path-to-regexp@2.2.1:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45"
|
||||||
|
integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==
|
||||||
|
|
||||||
|
punycode@^1.3.2:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||||
|
integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==
|
||||||
|
|
||||||
|
punycode@^2.1.0:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||||
|
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||||
|
|
||||||
|
range-parser@1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
|
||||||
|
integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==
|
||||||
|
|
||||||
|
rc@^1.0.1, rc@^1.1.6:
|
||||||
|
version "1.2.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||||
|
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
|
||||||
|
dependencies:
|
||||||
|
deep-extend "^0.6.0"
|
||||||
|
ini "~1.3.0"
|
||||||
|
minimist "^1.2.0"
|
||||||
|
strip-json-comments "~2.0.1"
|
||||||
|
|
||||||
|
registry-auth-token@3.3.2:
|
||||||
|
version "3.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20"
|
||||||
|
integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==
|
||||||
|
dependencies:
|
||||||
|
rc "^1.1.6"
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
registry-url@3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
|
||||||
|
integrity sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==
|
||||||
|
dependencies:
|
||||||
|
rc "^1.0.1"
|
||||||
|
|
||||||
|
require-from-string@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
|
||||||
|
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
|
||||||
|
|
||||||
|
safe-buffer@5.1.2:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
|
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||||
|
|
||||||
|
safe-buffer@^5.0.1:
|
||||||
|
version "5.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||||
|
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||||
|
|
||||||
|
serve-handler@6.1.3:
|
||||||
|
version "6.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.3.tgz#1bf8c5ae138712af55c758477533b9117f6435e8"
|
||||||
|
integrity sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.0.0"
|
||||||
|
content-disposition "0.5.2"
|
||||||
|
fast-url-parser "1.1.3"
|
||||||
|
mime-types "2.1.18"
|
||||||
|
minimatch "3.0.4"
|
||||||
|
path-is-inside "1.0.2"
|
||||||
|
path-to-regexp "2.2.1"
|
||||||
|
range-parser "1.2.0"
|
||||||
|
|
||||||
|
serve@14.0.1:
|
||||||
|
version "14.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/serve/-/serve-14.0.1.tgz#5b6ffc17e18e1a8e963cc392958d7df13e1ef9fd"
|
||||||
|
integrity sha512-tNGwxl27FwA8TbmMQqN0jTaSx8/trL532qZsJHX1VdiEIjjtMJHCs7AFS6OvtC7cTHOvmjXqt5yczejU6CV2Xg==
|
||||||
|
dependencies:
|
||||||
|
"@zeit/schemas" "2.21.0"
|
||||||
|
ajv "8.11.0"
|
||||||
|
arg "5.0.2"
|
||||||
|
boxen "7.0.0"
|
||||||
|
chalk "5.0.1"
|
||||||
|
chalk-template "0.4.0"
|
||||||
|
clipboardy "3.0.0"
|
||||||
|
compression "1.7.4"
|
||||||
|
is-port-reachable "4.0.0"
|
||||||
|
serve-handler "6.1.3"
|
||||||
|
update-check "1.5.4"
|
||||||
|
|
||||||
|
shebang-command@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
|
||||||
|
integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
|
||||||
|
dependencies:
|
||||||
|
shebang-regex "^3.0.0"
|
||||||
|
|
||||||
|
shebang-regex@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||||
|
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||||
|
|
||||||
|
signal-exit@^3.0.3:
|
||||||
|
version "3.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||||
|
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||||
|
|
||||||
|
string-width@^4.1.0:
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
|
string-width@^5.0.1, string-width@^5.1.2:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||||
|
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
|
||||||
|
dependencies:
|
||||||
|
eastasianwidth "^0.2.0"
|
||||||
|
emoji-regex "^9.2.2"
|
||||||
|
strip-ansi "^7.0.1"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
|
strip-ansi@^7.0.1:
|
||||||
|
version "7.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
|
||||||
|
integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^6.0.1"
|
||||||
|
|
||||||
|
strip-final-newline@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
|
||||||
|
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
|
||||||
|
|
||||||
|
strip-json-comments@~2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||||
|
integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
|
||||||
|
|
||||||
|
supports-color@^7.1.0:
|
||||||
|
version "7.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||||
|
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
|
||||||
|
dependencies:
|
||||||
|
has-flag "^4.0.0"
|
||||||
|
|
||||||
|
type-fest@^2.13.0:
|
||||||
|
version "2.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
|
||||||
|
integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
|
||||||
|
|
||||||
|
update-check@1.5.4:
|
||||||
|
version "1.5.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/update-check/-/update-check-1.5.4.tgz#5b508e259558f1ad7dbc8b4b0457d4c9d28c8743"
|
||||||
|
integrity sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==
|
||||||
|
dependencies:
|
||||||
|
registry-auth-token "3.3.2"
|
||||||
|
registry-url "3.1.0"
|
||||||
|
|
||||||
|
uri-js@^4.2.2:
|
||||||
|
version "4.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
|
||||||
|
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
|
||||||
|
dependencies:
|
||||||
|
punycode "^2.1.0"
|
||||||
|
|
||||||
|
vary@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||||
|
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
||||||
|
|
||||||
|
which@^2.0.1:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||||
|
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
|
||||||
|
dependencies:
|
||||||
|
isexe "^2.0.0"
|
||||||
|
|
||||||
|
widest-line@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2"
|
||||||
|
integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==
|
||||||
|
dependencies:
|
||||||
|
string-width "^5.0.1"
|
||||||
|
|
||||||
|
wrap-ansi@^8.0.1:
|
||||||
|
version "8.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.0.1.tgz#2101e861777fec527d0ea90c57c6b03aac56a5b3"
|
||||||
|
integrity sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^6.1.0"
|
||||||
|
string-width "^5.0.1"
|
||||||
|
strip-ansi "^7.0.1"
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --port $PORT",
|
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"serve": "vite preview"
|
"serve": "vite preview"
|
||||||
},
|
},
|
||||||
|
|||||||
3
packages/cli/test/dev/fixtures/vite-dev/vercel.json
Normal file
3
packages/cli/test/dev/fixtures/vite-dev/vercel.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"devCommand": "vite --port $PORT --host ::1"
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import url from 'url';
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import listen from 'async-listen';
|
import listen from 'async-listen';
|
||||||
|
import stripAnsi from 'strip-ansi';
|
||||||
import { createServer } from 'http';
|
import { createServer } from 'http';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -449,11 +450,7 @@ test('[vercel dev] should maintain query when proxy passing', async () => {
|
|||||||
|
|
||||||
test('[vercel dev] should maintain query when dev server defines routes', async () => {
|
test('[vercel dev] should maintain query when dev server defines routes', async () => {
|
||||||
const dir = fixture('dev-server-query');
|
const dir = fixture('dev-server-query');
|
||||||
const { dev, port, readyResolver } = await testFixture(dir, {
|
const { dev, port, readyResolver } = await testFixture(dir);
|
||||||
env: {
|
|
||||||
VERCEL_DEV_COMMAND: 'next dev --port $PORT',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await readyResolver;
|
await readyResolver;
|
||||||
@@ -516,11 +513,7 @@ test('[vercel dev] should send `etag` header for static files', async () => {
|
|||||||
|
|
||||||
test('[vercel dev] should frontend dev server and routes', async () => {
|
test('[vercel dev] should frontend dev server and routes', async () => {
|
||||||
const dir = fixture('dev-server-and-routes');
|
const dir = fixture('dev-server-and-routes');
|
||||||
const { dev, port, readyResolver } = await testFixture(dir, {
|
const { dev, port, readyResolver } = await testFixture(dir);
|
||||||
env: {
|
|
||||||
VERCEL_DEV_COMMAND: 'next dev --port $PORT',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await readyResolver;
|
await readyResolver;
|
||||||
@@ -731,12 +724,15 @@ test('[vercel dev] should support custom 404 routes', async () => {
|
|||||||
test('[vercel dev] prints `npm install` errors', async () => {
|
test('[vercel dev] prints `npm install` errors', async () => {
|
||||||
const dir = fixture('runtime-not-installed');
|
const dir = fixture('runtime-not-installed');
|
||||||
const result = await exec(dir);
|
const result = await exec(dir);
|
||||||
expect(result.stderr.includes('npm ERR! 404')).toBeTruthy();
|
|
||||||
expect(
|
expect(
|
||||||
result.stderr.includes('Failed to install `vercel dev` dependencies')
|
stripAnsi(result.stderr).includes(
|
||||||
|
'Error: The package `@vercel/does-not-exist` is not published on the npm registry'
|
||||||
|
)
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(
|
expect(
|
||||||
result.stderr.includes('https://vercel.link/npm-install-failed-dev')
|
result.stderr.includes(
|
||||||
|
'https://vercel.link/builder-dependencies-install-failed'
|
||||||
|
)
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -445,11 +445,7 @@ test(
|
|||||||
test(
|
test(
|
||||||
'[vercel dev] Middleware that has no response',
|
'[vercel dev] Middleware that has no response',
|
||||||
testFixtureStdio('middleware-no-response', async (testPath: any) => {
|
testFixtureStdio('middleware-no-response', async (testPath: any) => {
|
||||||
await testPath(
|
await testPath(200, '/api/hello', 'hello from a serverless function');
|
||||||
500,
|
|
||||||
'/api/hello',
|
|
||||||
'A server error has occurred\n\nEDGE_FUNCTION_INVOCATION_FAILED'
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -461,9 +457,42 @@ test(
|
|||||||
await testPath(200, '/another', '<h1>Another</h1>');
|
await testPath(200, '/another', '<h1>Another</h1>');
|
||||||
await testPath(200, '/another.html', '<h1>Another</h1>');
|
await testPath(200, '/another.html', '<h1>Another</h1>');
|
||||||
await testPath(200, '/foo', '<h1>Another</h1>');
|
await testPath(200, '/foo', '<h1>Another</h1>');
|
||||||
|
// different origin
|
||||||
|
await testPath(200, '?to=http://example.com', /Example Domain/);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test('[vercel dev] Middleware rewrites with same origin', async () => {
|
||||||
|
const directory = fixture('middleware-rewrite');
|
||||||
|
const { dev, port, readyResolver } = await testFixture(directory);
|
||||||
|
|
||||||
|
try {
|
||||||
|
dev.unref();
|
||||||
|
await readyResolver;
|
||||||
|
|
||||||
|
let response = await fetch(
|
||||||
|
`http://localhost:${port}?to=http://localhost:${port}`
|
||||||
|
);
|
||||||
|
validateResponseHeaders(response);
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(await response.text()).toMatch(/<h1>Index<\/h1>/);
|
||||||
|
|
||||||
|
response = await fetch(
|
||||||
|
`http://localhost:${port}?to=http://127.0.0.1:${port}`
|
||||||
|
);
|
||||||
|
validateResponseHeaders(response);
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(await response.text()).toMatch(/<h1>Index<\/h1>/);
|
||||||
|
|
||||||
|
response = await fetch(`http://localhost:${port}?to=http://[::1]:${port}`);
|
||||||
|
validateResponseHeaders(response);
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(await response.text()).toMatch(/<h1>Index<\/h1>/);
|
||||||
|
} finally {
|
||||||
|
await dev.kill('SIGTERM');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'[vercel dev] Middleware that rewrites with custom query params',
|
'[vercel dev] Middleware that rewrites with custom query params',
|
||||||
testFixtureStdio('middleware-rewrite-query', async (testPath: any) => {
|
testFixtureStdio('middleware-rewrite-query', async (testPath: any) => {
|
||||||
@@ -481,6 +510,14 @@ test(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'[vercel dev] Middleware that rewrites to 404s',
|
||||||
|
testFixtureStdio('middleware-rewrite-404', async (testPath: any) => {
|
||||||
|
await testPath(404, '/api/edge', /NOT_FOUND/);
|
||||||
|
await testPath(404, '/index.html', /NOT_FOUND/);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'[vercel dev] Middleware that redirects',
|
'[vercel dev] Middleware that redirects',
|
||||||
testFixtureStdio('middleware-redirect', async (testPath: any) => {
|
testFixtureStdio('middleware-redirect', async (testPath: any) => {
|
||||||
@@ -539,3 +576,40 @@ test(
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'[vercel dev] restarts dev process when `devCommand` setting is modified',
|
||||||
|
testFixtureStdio(
|
||||||
|
'project-settings-override',
|
||||||
|
async (_testPath: any, port: any) => {
|
||||||
|
const directory = fixture('project-settings-override');
|
||||||
|
const vercelJsonPath = join(directory, 'vercel.json');
|
||||||
|
const originalVercelJson = await fs.readJSON(vercelJsonPath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const originalResponse = await fetch(
|
||||||
|
`http://localhost:${port}/index.txt`
|
||||||
|
);
|
||||||
|
validateResponseHeaders(originalResponse);
|
||||||
|
const body = await originalResponse.text();
|
||||||
|
expect(body.trim()).toEqual('This is the original');
|
||||||
|
expect(originalResponse.status).toBe(200);
|
||||||
|
|
||||||
|
await fs.writeJSON(vercelJsonPath, {
|
||||||
|
devCommand: 'serve -p $PORT overridden',
|
||||||
|
});
|
||||||
|
|
||||||
|
const overriddenResponse = await fetch(
|
||||||
|
`http://localhost:${port}/index.txt`
|
||||||
|
);
|
||||||
|
validateResponseHeaders(overriddenResponse);
|
||||||
|
const body2 = await overriddenResponse.text();
|
||||||
|
expect(body2.trim()).toEqual('This is the overridden!');
|
||||||
|
expect(overriddenResponse.status).toBe(200);
|
||||||
|
} finally {
|
||||||
|
await fs.writeJSON(vercelJsonPath, originalVercelJson);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ skipDeploy: true }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const {
|
|||||||
|
|
||||||
jest.setTimeout(6 * 60 * 1000);
|
jest.setTimeout(6 * 60 * 1000);
|
||||||
|
|
||||||
|
const isCI = !!process.env.CI;
|
||||||
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
const isCanary = () => getDistTag(cliVersion) === 'canary';
|
const isCanary = () => getDistTag(cliVersion) === 'canary';
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ function fetchWithRetry(url, opts = {}) {
|
|||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
retries: opts.retries || 3,
|
retries: opts.retries ?? 3,
|
||||||
factor: 1,
|
factor: 1,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -150,9 +151,9 @@ async function testPath(
|
|||||||
fetchOpts = {}
|
fetchOpts = {}
|
||||||
) {
|
) {
|
||||||
const opts = {
|
const opts = {
|
||||||
|
retries: isCI ? 5 : 0,
|
||||||
...fetchOpts,
|
...fetchOpts,
|
||||||
redirect: 'manual-dont-change',
|
redirect: 'manual-dont-change',
|
||||||
retries: 5,
|
|
||||||
status,
|
status,
|
||||||
};
|
};
|
||||||
const url = `${origin}${path}`;
|
const url = `${origin}${path}`;
|
||||||
@@ -330,7 +331,7 @@ function testFixtureStdio(
|
|||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(projectSettings),
|
body: JSON.stringify(projectSettings),
|
||||||
retries: 3,
|
retries: isCI ? 3 : 0,
|
||||||
status: 200,
|
status: 200,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "only-module",
|
"name": "prefer-browser",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "dist-main.js",
|
"main": "dist-main.js",
|
||||||
"module": "dist-module.js",
|
"module": "dist-module.js",
|
||||||
|
|||||||
1
packages/cli/test/fixtures/unit/commands/build/project-settings-override/.gitignore
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/build/project-settings-override/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
output
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"orgId": ".",
|
||||||
|
"projectId": ".",
|
||||||
|
"settings": {
|
||||||
|
"buildCommand": "mkdir -p output && echo 2 > output/index.txt",
|
||||||
|
"outputDirectory": "output",
|
||||||
|
"framework": null
|
||||||
|
}
|
||||||
|
}
|
||||||
5
packages/cli/test/fixtures/unit/commands/build/project-settings-override/package.json
vendored
Normal file
5
packages/cli/test/fixtures/unit/commands/build/project-settings-override/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "mkdir -p output && echo 1 > output/index.txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/cli/test/fixtures/unit/commands/build/project-settings-override/vercel.json
vendored
Normal file
3
packages/cli/test/fixtures/unit/commands/build/project-settings-override/vercel.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"buildCommand": "mkdir -p output && echo 3 > output/index.txt"
|
||||||
|
}
|
||||||
@@ -76,25 +76,6 @@ module.exports = async function prepare(session, binaryPath, tmpFixturesDir) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
'build-env-debug': {
|
|
||||||
'now.json': JSON.stringify({
|
|
||||||
builds: [{ src: 'index.js', use: '@vercel/node' }],
|
|
||||||
}),
|
|
||||||
'package.json': JSON.stringify({
|
|
||||||
scripts: {
|
|
||||||
'now-build': 'node now-build.js',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'now-build.js': `
|
|
||||||
const fs = require('fs');
|
|
||||||
fs.writeFileSync(
|
|
||||||
'index.js',
|
|
||||||
fs.readFileSync('index.js', 'utf8')
|
|
||||||
.replace('BUILD_ENV_DEBUG', process.env.NOW_BUILDER_DEBUG ? 'on' : 'off'),
|
|
||||||
);
|
|
||||||
`,
|
|
||||||
'index.js': `module.exports = (req, res) => { res.status(200).send('BUILD_ENV_DEBUG'); }`,
|
|
||||||
},
|
|
||||||
'now-revert-alias-1': {
|
'now-revert-alias-1': {
|
||||||
'index.json': JSON.stringify({ name: 'now-revert-alias-1' }),
|
'index.json': JSON.stringify({ name: 'now-revert-alias-1' }),
|
||||||
'now.json': getRevertAliasConfigFile(),
|
'now.json': getRevertAliasConfigFile(),
|
||||||
|
|||||||
73
packages/cli/test/integration.js
vendored
73
packages/cli/test/integration.js
vendored
@@ -360,7 +360,7 @@ test('default command should prompt login with empty auth.json', async t => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
t.true(
|
t.true(
|
||||||
err.stderr.includes(
|
err.stderr.includes(
|
||||||
'Error! No existing credentials found. Please run `vercel login` or pass "--token"'
|
'Error: No existing credentials found. Please run `vercel login` or pass "--token"'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -749,7 +749,7 @@ test('deploy fails using --local-config flag with non-existent path', async t =>
|
|||||||
|
|
||||||
t.is(exitCode, 1, formatOutput({ stderr, stdout }));
|
t.is(exitCode, 1, formatOutput({ stderr, stdout }));
|
||||||
|
|
||||||
t.regex(stderr, /Error! Couldn't find a project configuration file at/);
|
t.regex(stderr, /Error: Couldn't find a project configuration file at/);
|
||||||
t.regex(stderr, /does-not-exist\.json/);
|
t.regex(stderr, /does-not-exist\.json/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1451,7 +1451,7 @@ test('login with unregistered user', async t => {
|
|||||||
console.log(stdout);
|
console.log(stdout);
|
||||||
console.log(exitCode);
|
console.log(exitCode);
|
||||||
|
|
||||||
const goal = `Error! Please sign up: https://vercel.com/signup`;
|
const goal = `Error: Please sign up: https://vercel.com/signup`;
|
||||||
const lines = stderr.trim().split('\n');
|
const lines = stderr.trim().split('\n');
|
||||||
const last = lines[lines.length - 1];
|
const last = lines[lines.length - 1];
|
||||||
|
|
||||||
@@ -1629,7 +1629,7 @@ test('try to purchase a domain', async t => {
|
|||||||
t.is(exitCode, 1);
|
t.is(exitCode, 1);
|
||||||
t.regex(
|
t.regex(
|
||||||
stderr,
|
stderr,
|
||||||
/Error! Could not purchase domain\. Please add a payment method using/
|
/Error: Could not purchase domain\. Please add a payment method using/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1655,7 +1655,7 @@ test('try to transfer-in a domain with "--code" option', async t => {
|
|||||||
|
|
||||||
t.true(
|
t.true(
|
||||||
stderr.includes(
|
stderr.includes(
|
||||||
`Error! The domain "${session}-test.com" is not transferable.`
|
`Error: The domain "${session}-test.com" is not transferable.`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
t.is(exitCode, 1);
|
t.is(exitCode, 1);
|
||||||
@@ -1680,7 +1680,7 @@ test('try to move an invalid domain', async t => {
|
|||||||
console.log(stdout);
|
console.log(stdout);
|
||||||
console.log(exitCode);
|
console.log(exitCode);
|
||||||
|
|
||||||
t.true(stderr.includes(`Error! Domain not found under `));
|
t.true(stderr.includes(`Error: Domain not found under `));
|
||||||
t.is(exitCode, 1);
|
t.is(exitCode, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1964,7 +1964,7 @@ test('try to create a builds deployments with wrong now.json', async t => {
|
|||||||
t.is(exitCode, 1);
|
t.is(exitCode, 1);
|
||||||
t.true(
|
t.true(
|
||||||
stderr.includes(
|
stderr.includes(
|
||||||
'Error! Invalid now.json - should NOT have additional property `builder`. Did you mean `builds`?'
|
'Error: Invalid now.json - should NOT have additional property `builder`. Did you mean `builds`?'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
t.true(stderr.includes('https://vercel.com/docs/configuration'));
|
t.true(stderr.includes('https://vercel.com/docs/configuration'));
|
||||||
@@ -1988,7 +1988,7 @@ test('try to create a builds deployments with wrong vercel.json', async t => {
|
|||||||
t.is(exitCode, 1);
|
t.is(exitCode, 1);
|
||||||
t.true(
|
t.true(
|
||||||
stderr.includes(
|
stderr.includes(
|
||||||
'Error! Invalid vercel.json - should NOT have additional property `fake`. Please remove it.'
|
'Error: Invalid vercel.json - should NOT have additional property `fake`. Please remove it.'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
t.true(stderr.includes('https://vercel.com/docs/configuration'));
|
t.true(stderr.includes('https://vercel.com/docs/configuration'));
|
||||||
@@ -2009,7 +2009,7 @@ test('try to create a builds deployments with wrong `build.env` property', async
|
|||||||
t.is(exitCode, 1, formatOutput({ stdout, stderr }));
|
t.is(exitCode, 1, formatOutput({ stdout, stderr }));
|
||||||
t.true(
|
t.true(
|
||||||
stderr.includes(
|
stderr.includes(
|
||||||
'Error! Invalid vercel.json - should NOT have additional property `build.env`. Did you mean `{ "build": { "env": {"name": "value"} } }`?'
|
'Error: Invalid vercel.json - should NOT have additional property `build.env`. Did you mean `{ "build": { "env": {"name": "value"} } }`?'
|
||||||
),
|
),
|
||||||
formatOutput({ stdout, stderr })
|
formatOutput({ stdout, stderr })
|
||||||
);
|
);
|
||||||
@@ -2170,47 +2170,8 @@ test('use build-env', async t => {
|
|||||||
t.is(content.trim(), 'bar');
|
t.is(content.trim(), 'bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('use `--debug` CLI flag', async t => {
|
|
||||||
const directory = fixture('build-env-debug');
|
|
||||||
|
|
||||||
const { stderr, stdout, exitCode } = await execa(
|
|
||||||
binaryPath,
|
|
||||||
[
|
|
||||||
directory,
|
|
||||||
'--public',
|
|
||||||
'--name',
|
|
||||||
session,
|
|
||||||
'--debug',
|
|
||||||
...defaultArgs,
|
|
||||||
'--yes',
|
|
||||||
],
|
|
||||||
{
|
|
||||||
reject: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(stderr);
|
|
||||||
console.log(stdout);
|
|
||||||
console.log(exitCode);
|
|
||||||
|
|
||||||
// Ensure the exit code is right
|
|
||||||
t.is(exitCode, 0, `Received:\n"${stderr}"\n"${stdout}"`);
|
|
||||||
|
|
||||||
// Test if the output is really a URL
|
|
||||||
const deploymentUrl = pickUrl(stdout);
|
|
||||||
const { href, host } = new URL(deploymentUrl);
|
|
||||||
t.is(host.split('-')[0], session);
|
|
||||||
|
|
||||||
await waitForDeployment(href);
|
|
||||||
|
|
||||||
// get the content
|
|
||||||
const response = await fetch(href);
|
|
||||||
const content = await response.text();
|
|
||||||
t.is(content.trim(), 'off');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('try to deploy non-existing path', async t => {
|
test('try to deploy non-existing path', async t => {
|
||||||
const goal = `Error! The specified file or directory "${session}" does not exist.`;
|
const goal = `Error: The specified file or directory "${session}" does not exist.`;
|
||||||
|
|
||||||
const { stderr, stdout, exitCode } = await execa(
|
const { stderr, stdout, exitCode } = await execa(
|
||||||
binaryPath,
|
binaryPath,
|
||||||
@@ -2230,7 +2191,7 @@ test('try to deploy non-existing path', async t => {
|
|||||||
|
|
||||||
test('try to deploy with non-existing team', async t => {
|
test('try to deploy with non-existing team', async t => {
|
||||||
const target = fixture('static-deployment');
|
const target = fixture('static-deployment');
|
||||||
const goal = `Error! The specified scope does not exist`;
|
const goal = `Error: The specified scope does not exist`;
|
||||||
|
|
||||||
const { stderr, stdout, exitCode } = await execa(
|
const { stderr, stdout, exitCode } = await execa(
|
||||||
binaryPath,
|
binaryPath,
|
||||||
@@ -2313,7 +2274,7 @@ test('try to initialize example to existing directory', async t => {
|
|||||||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||||||
const cwd = tmpDir.name;
|
const cwd = tmpDir.name;
|
||||||
const goal =
|
const goal =
|
||||||
'Error! Destination path "angular" already exists and is not an empty directory. You may use `--force` or `-f` to override it.';
|
'Error: Destination path "angular" already exists and is not an empty directory. You may use `--force` or `-f` to override it.';
|
||||||
|
|
||||||
await ensureDir(path.join(cwd, 'angular'));
|
await ensureDir(path.join(cwd, 'angular'));
|
||||||
createFile(path.join(cwd, 'angular', '.gitignore'));
|
createFile(path.join(cwd, 'angular', '.gitignore'));
|
||||||
@@ -2330,7 +2291,7 @@ test('try to initialize misspelled example (noce) in non-tty', async t => {
|
|||||||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||||||
const cwd = tmpDir.name;
|
const cwd = tmpDir.name;
|
||||||
const goal =
|
const goal =
|
||||||
'Error! No example found for noce, run `vercel init` to see the list of available examples.';
|
'Error: No example found for noce, run `vercel init` to see the list of available examples.';
|
||||||
|
|
||||||
const { stdout, stderr, exitCode } = await execute(['init', 'noce'], { cwd });
|
const { stdout, stderr, exitCode } = await execute(['init', 'noce'], { cwd });
|
||||||
|
|
||||||
@@ -2346,7 +2307,7 @@ test('try to initialize example "example-404"', async t => {
|
|||||||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||||||
const cwd = tmpDir.name;
|
const cwd = tmpDir.name;
|
||||||
const goal =
|
const goal =
|
||||||
'Error! No example found for example-404, run `vercel init` to see the list of available examples.';
|
'Error: No example found for example-404, run `vercel init` to see the list of available examples.';
|
||||||
|
|
||||||
const { stdout, stderr, exitCode } = await execute(['init', 'example-404'], {
|
const { stdout, stderr, exitCode } = await execute(['init', 'example-404'], {
|
||||||
cwd,
|
cwd,
|
||||||
@@ -2522,7 +2483,7 @@ test('`vercel rm` should fail with unexpected option', async t => {
|
|||||||
t.is(output.exitCode, 1, formatOutput(output));
|
t.is(output.exitCode, 1, formatOutput(output));
|
||||||
t.regex(
|
t.regex(
|
||||||
output.stderr,
|
output.stderr,
|
||||||
/Error! unknown or unexpected option: --fake/gm,
|
/Error: unknown or unexpected option: --fake/gm,
|
||||||
formatOutput(output)
|
formatOutput(output)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -2816,7 +2777,7 @@ test('invalid `--token`', async t => {
|
|||||||
t.is(output.exitCode, 1, formatOutput(output));
|
t.is(output.exitCode, 1, formatOutput(output));
|
||||||
t.true(
|
t.true(
|
||||||
output.stderr.includes(
|
output.stderr.includes(
|
||||||
'Error! You defined "--token", but its contents are invalid. Must not contain: "\\n", ",", "."'
|
'Error: You defined "--token", but its contents are invalid. Must not contain: "\\n", ",", "."'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -3539,7 +3500,7 @@ test('reject deploying with invalid token', async t => {
|
|||||||
t.is(exitCode, 1, formatOutput({ stderr, stdout }));
|
t.is(exitCode, 1, formatOutput({ stderr, stdout }));
|
||||||
t.regex(
|
t.regex(
|
||||||
stderr,
|
stderr,
|
||||||
/Error! Could not retrieve Project Settings\. To link your Project, remove the `\.vercel` directory and deploy again\./g
|
/Error: Could not retrieve Project Settings\. To link your Project, remove the `\.vercel` directory and deploy again\./g
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user