mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 12:57:46 +00:00
Compare commits
171 Commits
@vercel/py
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a630e19896 | ||
|
|
00430eeabf | ||
|
|
49f453742b | ||
|
|
bcb8d4f812 | ||
|
|
d42a8a6588 | ||
|
|
301bcf58fb | ||
|
|
11d0091393 | ||
|
|
6405fb51a1 | ||
|
|
edd477e602 | ||
|
|
4f8f8a5b98 | ||
|
|
a8e66eef41 | ||
|
|
23dd29e269 | ||
|
|
4eb4d2b355 | ||
|
|
3590ea06a4 | ||
|
|
314a97b318 | ||
|
|
d41d9e7374 | ||
|
|
80b211fb4a | ||
|
|
ffaf5c9143 | ||
|
|
ba1c2a7e54 | ||
|
|
30a9183836 | ||
|
|
df9accfd6c | ||
|
|
b388357c0b | ||
|
|
90291525c2 | ||
|
|
812dd43b6a | ||
|
|
9e97e0fd58 | ||
|
|
74528c2160 | ||
|
|
82fd2b8068 | ||
|
|
dd94dcab32 | ||
|
|
300e6c6ebb | ||
|
|
cfe6550ac8 | ||
|
|
dfe009ffe2 | ||
|
|
40f38948a0 | ||
|
|
87eba56063 | ||
|
|
d0a5676c26 | ||
|
|
da9fa997ed | ||
|
|
3d79a9d4d4 | ||
|
|
ae13c5ee92 | ||
|
|
ad654139df | ||
|
|
74f8414e12 | ||
|
|
65c2860e14 | ||
|
|
3b5b397b35 | ||
|
|
051e061176 | ||
|
|
30d46321cc | ||
|
|
c2563535ea | ||
|
|
5f2bed4f24 | ||
|
|
cfb7946f4b | ||
|
|
05c5b3a80d | ||
|
|
45bd855250 | ||
|
|
49de8ad9a0 | ||
|
|
a1ea56fd67 | ||
|
|
e88addc9ed | ||
|
|
5d50013f93 | ||
|
|
44e1eb3983 | ||
|
|
f8af013349 | ||
|
|
972cc495ec | ||
|
|
1c580da3d8 | ||
|
|
244554ab1b | ||
|
|
053c185481 | ||
|
|
8805b586ea | ||
|
|
681070ffa0 | ||
|
|
362b17d60a | ||
|
|
c7c9b1a791 | ||
|
|
c42f309463 | ||
|
|
a0ead28369 | ||
|
|
8814fc1515 | ||
|
|
0d044b4eac | ||
|
|
f6bd1aa8c0 | ||
|
|
8cd84ec066 | ||
|
|
a8df231e4c | ||
|
|
f674842bed | ||
|
|
bf5cfa9a41 | ||
|
|
12121b7a71 | ||
|
|
baa56aed2c | ||
|
|
6f767367e4 | ||
|
|
0e4124f94c | ||
|
|
30503d0a3f | ||
|
|
6c9164f67d | ||
|
|
906b7a8f2c | ||
|
|
43499b13d8 | ||
|
|
7d6e56670f | ||
|
|
dba337f148 | ||
|
|
a825bc9540 | ||
|
|
f5486a8297 | ||
|
|
225e0a4de3 | ||
|
|
0ecfdc1325 | ||
|
|
628409d233 | ||
|
|
51d968314f | ||
|
|
b4e2cbc6e5 | ||
|
|
7323beea8e | ||
|
|
e4bb311144 | ||
|
|
288546659d | ||
|
|
822224e212 | ||
|
|
6b23950b65 | ||
|
|
1558f21e49 | ||
|
|
a6aee8b904 | ||
|
|
27f4034bdc | ||
|
|
e43b968fbe | ||
|
|
48eb720556 | ||
|
|
30e4d74db6 | ||
|
|
75840896ee | ||
|
|
9657bda86d | ||
|
|
4536029d42 | ||
|
|
a35654d047 | ||
|
|
f6a01a1af3 | ||
|
|
c2bca954e0 | ||
|
|
21701c3c3d | ||
|
|
7f4723b3f8 | ||
|
|
619ca93421 | ||
|
|
cea2981512 | ||
|
|
1f30e3a4b7 | ||
|
|
20e8fdd049 | ||
|
|
3df8313d69 | ||
|
|
95c9ea92c4 | ||
|
|
7ddebb099d | ||
|
|
6498fd1aab | ||
|
|
9d7d822da3 | ||
|
|
b8110d97d1 | ||
|
|
2d9373b0f1 | ||
|
|
cc7577a648 | ||
|
|
f3f3d7df5b | ||
|
|
0bafea6d51 | ||
|
|
5abb74eddd | ||
|
|
23d7eaccab | ||
|
|
2cb008e8ed | ||
|
|
b5c3610113 | ||
|
|
f23c7fc4fc | ||
|
|
3dc1ad6437 | ||
|
|
4dc0bef62b | ||
|
|
8b3c52b9e8 | ||
|
|
fc74300ad0 | ||
|
|
28f8a38e00 | ||
|
|
c318ce9695 | ||
|
|
5b36eaacff | ||
|
|
c9f7ca23a8 | ||
|
|
57e0db0f65 | ||
|
|
d5537500d8 | ||
|
|
4b1736b2f2 | ||
|
|
9da1c6fa66 | ||
|
|
a6c320846e | ||
|
|
4973814978 | ||
|
|
f1289ff263 | ||
|
|
3ff93279cd | ||
|
|
58f9d649a8 | ||
|
|
cadc082ad1 | ||
|
|
2906d83eae | ||
|
|
675c3e2915 | ||
|
|
13a8a7dbf6 | ||
|
|
b480e632a3 | ||
|
|
bc6c364888 | ||
|
|
6cad10c899 | ||
|
|
0d78ec4666 | ||
|
|
b05d653cf1 | ||
|
|
5a38b94de2 | ||
|
|
6668d6cf21 | ||
|
|
b63f444abd | ||
|
|
9d71d4332f | ||
|
|
151db21a2f | ||
|
|
aba9c95ea2 | ||
|
|
e7e0a55b72 | ||
|
|
c2163e3e4f | ||
|
|
873f549637 | ||
|
|
ead1e411ee | ||
|
|
7c1c089a70 | ||
|
|
a7dbd9649b | ||
|
|
2e439045c9 | ||
|
|
520e0d01f4 | ||
|
|
3856623785 | ||
|
|
1c14e945f9 | ||
|
|
32357fc06f | ||
|
|
f7c57dc539 | ||
|
|
34f7c35c13 |
28
.github/CODEOWNERS
vendored
28
.github/CODEOWNERS
vendored
@@ -1,23 +1,17 @@
|
||||
# Documentation
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
* @TooTallNate @EndangeredMassa @styfle
|
||||
/.github/workflows @TooTallNate @EndangeredMassa @styfle @ijjk
|
||||
/packages/frameworks @TooTallNate @EndangeredMassa @styfle @AndyBitz
|
||||
/packages/cli/src/commands/domains @javivelasco @mglagola @anatrajkovska
|
||||
/packages/cli/src/commands/certs @javivelasco @mglagola @anatrajkovska
|
||||
/packages/cli/src/commands/env @styfle @lucleray
|
||||
/packages/client @TooTallNate @EndangeredMassa @styfle
|
||||
/packages/build-utils @TooTallNate @EndangeredMassa @styfle @AndyBitz
|
||||
/packages/middleware @gdborton @javivelasco
|
||||
/packages/node @TooTallNate @EndangeredMassa @styfle
|
||||
/packages/node-bridge @TooTallNate @EndangeredMassa @styfle @ijjk
|
||||
/packages/next @TooTallNate @ijjk
|
||||
/packages/go @TooTallNate @EndangeredMassa @styfle
|
||||
/packages/python @TooTallNate @EndangeredMassa @styfle
|
||||
/packages/ruby @TooTallNate @EndangeredMassa @styfle
|
||||
/packages/static-build @TooTallNate @EndangeredMassa @styfle @AndyBitz
|
||||
/packages/routing-utils @TooTallNate @EndangeredMassa @styfle @ijjk
|
||||
* @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood
|
||||
/.github/workflows @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||
/packages/cli/src/commands/domains @mglagola @anatrajkovska
|
||||
/packages/cli/src/commands/certs @mglagola @anatrajkovska
|
||||
/packages/cli/src/commands/env @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood
|
||||
/packages/fs-detectors @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @agadzik @chloetedder
|
||||
/packages/middleware @gdborton @vercel/edge-function
|
||||
/packages/node-bridge @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||
/packages/next @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||
/packages/routing-utils @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||
/packages/edge @vercel/edge-function
|
||||
/examples @leerob
|
||||
/examples/create-react-app @Timer
|
||||
/examples/nextjs @timneutkens @ijjk @styfle
|
||||
|
||||
1
.github/CONTRIBUTING.md
vendored
1
.github/CONTRIBUTING.md
vendored
@@ -13,6 +13,7 @@ To get started, execute the following:
|
||||
```
|
||||
git clone https://github.com/vercel/vercel
|
||||
cd vercel
|
||||
corepack enable
|
||||
yarn install
|
||||
yarn bootstrap
|
||||
yarn build
|
||||
|
||||
17
.github/workflows/cancel.yml
vendored
17
.github/workflows/cancel.yml
vendored
@@ -1,17 +0,0 @@
|
||||
name: Cancel
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
- '!main'
|
||||
|
||||
jobs:
|
||||
cancel:
|
||||
name: 'Cancel Previous Runs'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- uses: styfle/cancel-workflow-action@0.9.1
|
||||
with:
|
||||
workflow_id: test.yml, test-integration-cli.yml, test-unit.yml
|
||||
access_token: ${{ github.token }}
|
||||
11
.github/workflows/publish.yml
vendored
11
.github/workflows/publish.yml
vendored
@@ -7,13 +7,20 @@ on:
|
||||
tags:
|
||||
- '!*'
|
||||
|
||||
env:
|
||||
TURBO_REMOTE_ONLY: 'true'
|
||||
TURBO_TEAM: 'vercel'
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Check Release
|
||||
id: check-release
|
||||
run: |
|
||||
@@ -32,8 +39,10 @@ jobs:
|
||||
- name: Setup Node
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
uses: actions/setup-node@v3
|
||||
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
|
||||
with:
|
||||
node-version: 14
|
||||
cache: 'yarn'
|
||||
- name: Install
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
run: yarn install --check-files --frozen-lockfile --network-timeout 1000000
|
||||
|
||||
26
.github/workflows/required-pr-label.yml
vendored
Normal file
26
.github/workflows/required-pr-label.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
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: |
|
||||
let missing = false;
|
||||
const labels = context.payload.pull_request.labels.map(l => l.name);
|
||||
if (labels.filter(l => l.startsWith('area:')).length === 0) {
|
||||
console.error('::error::Missing label: Please add at least one "area" label.');
|
||||
missing = true;
|
||||
}
|
||||
if (labels.filter(l => l.startsWith('semver:')).length !== 1) {
|
||||
console.error('::error::Missing label: Please add exactly one "semver" label.');
|
||||
missing = true;
|
||||
}
|
||||
if (missing) {
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('::notice::Success: This pull request has correct labels, thanks!');
|
||||
31
.github/workflows/test-integration-cli.yml
vendored
31
.github/workflows/test-integration-cli.yml
vendored
@@ -8,6 +8,15 @@ on:
|
||||
- '!*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TURBO_REMOTE_ONLY: 'true'
|
||||
TURBO_TEAM: 'vercel'
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: CLI
|
||||
@@ -19,28 +28,20 @@ jobs:
|
||||
node: [14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Conditionally set remote env
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
run: |
|
||||
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- uses: actions/setup-node@v3
|
||||
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 100
|
||||
- run: git --version
|
||||
- run: git fetch origin main --depth=100
|
||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
||||
- run: git diff origin/main...HEAD --name-only
|
||||
cache: 'yarn'
|
||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||
- run: yarn run build
|
||||
- run: yarn test-integration-cli
|
||||
env:
|
||||
VERCEL_TEAM_TOKEN: ${{ secrets.VERCEL_TEAM_TOKEN }}
|
||||
VERCEL_REGISTRATION_URL: ${{ secrets.VERCEL_REGISTRATION_URL }}
|
||||
VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }}
|
||||
VERCEL_TEST_REGISTRATION_URL: ${{ secrets.VERCEL_TEST_REGISTRATION_URL }}
|
||||
|
||||
29
.github/workflows/test-unit.yml
vendored
29
.github/workflows/test-unit.yml
vendored
@@ -8,6 +8,15 @@ on:
|
||||
- '!*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TURBO_REMOTE_ONLY: 'true'
|
||||
TURBO_TEAM: 'vercel'
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Unit
|
||||
@@ -19,25 +28,17 @@ jobs:
|
||||
node: [14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Conditionally set remote env
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
run: |
|
||||
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 100
|
||||
- run: git --version
|
||||
- run: git fetch origin main --depth=100
|
||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
||||
- run: git diff origin/main...HEAD --name-only
|
||||
fetch-depth: 2
|
||||
- uses: actions/setup-node@v3
|
||||
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: 'yarn'
|
||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||
- run: yarn run build
|
||||
- run: yarn run lint
|
||||
|
||||
30
.github/workflows/test.yml
vendored
30
.github/workflows/test.yml
vendored
@@ -10,6 +10,13 @@ on:
|
||||
|
||||
env:
|
||||
NODE_VERSION: '14'
|
||||
TURBO_REMOTE_ONLY: 'true'
|
||||
TURBO_TEAM: 'vercel'
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
@@ -20,9 +27,13 @@ jobs:
|
||||
dplUrl: ${{ steps.waitForTarball.outputs.url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: git --version
|
||||
- run: git fetch origin main
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- uses: actions/setup-node@v3
|
||||
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'yarn'
|
||||
@@ -52,12 +63,6 @@ jobs:
|
||||
matrix:
|
||||
include: ${{ fromJson(needs.setup.outputs['tests']) }}
|
||||
steps:
|
||||
- name: Conditionally set remote env
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
run: |
|
||||
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
@@ -65,6 +70,7 @@ jobs:
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- uses: actions/setup-node@v3
|
||||
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'yarn'
|
||||
@@ -76,16 +82,16 @@ jobs:
|
||||
- run: yarn install --network-timeout 1000000
|
||||
|
||||
- name: Build ${{matrix.packageName}} and all its dependencies
|
||||
run: node_modules/.bin/turbo run build --cache-dir=".turbo" --scope=${{matrix.packageName}} --include-dependencies --no-deps
|
||||
run: node utils/gen.js && node_modules/.bin/turbo run build --cache-dir=".turbo" --scope=${{matrix.packageName}} --include-dependencies --no-deps
|
||||
env:
|
||||
FORCE_COLOR: '1'
|
||||
- name: Test ${{matrix.packageName}}
|
||||
run: node_modules/.bin/turbo run test --cache-dir=".turbo" --scope=${{matrix.packageName}} --no-deps -- ${{ join(matrix.testPaths, ' ') }}
|
||||
run: node utils/gen.js && node_modules/.bin/turbo run test --cache-dir=".turbo" --scope=${{matrix.packageName}} --no-deps -- ${{ join(matrix.testPaths, ' ') }}
|
||||
shell: bash
|
||||
env:
|
||||
VERCEL_CLI_VERSION: ${{ needs.setup.outputs.dplUrl }}/tarballs/vercel.tgz
|
||||
VERCEL_TEAM_TOKEN: ${{ secrets.VERCEL_TEAM_TOKEN }}
|
||||
VERCEL_REGISTRATION_URL: ${{ secrets.VERCEL_REGISTRATION_URL }}
|
||||
VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }}
|
||||
VERCEL_TEST_REGISTRATION_URL: ${{ secrets.VERCEL_TEST_REGISTRATION_URL }}
|
||||
FORCE_COLOR: '1'
|
||||
|
||||
conclusion:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -28,3 +28,4 @@ test/lib/deployment/failed-page.txt
|
||||
__pycache__
|
||||
.vercel
|
||||
.turbo
|
||||
turbo-cache-key.json
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version = 1
|
||||
|
||||
[merge]
|
||||
automerge_label = ["semver-major","semver-minor","semver-patch"]
|
||||
automerge_label = ["pr: automerge"]
|
||||
blacklist_title_regex = "^WIP.*"
|
||||
blacklist_labels = ["work in progress"]
|
||||
method = "squash"
|
||||
|
||||
@@ -380,8 +380,8 @@ This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refere
|
||||
|
||||
This is an abstract enumeration type that is implemented by one of the following possible `String` values:
|
||||
|
||||
- `nodejs16.x`
|
||||
- `nodejs14.x`
|
||||
- `nodejs12.x`
|
||||
- `go1.x`
|
||||
- `java11`
|
||||
- `python3.9`
|
||||
|
||||
@@ -19,11 +19,9 @@
|
||||
|
||||
## Vercel
|
||||
|
||||
Vercel is a platform for **static sites and frontend frameworks**, built to integrate with your headless content, commerce, or database.
|
||||
Vercel is the platform for frontend developers, providing the speed and reliability innovators need to create at the moment of inspiration.
|
||||
|
||||
We provide a **frictionless developer experience** to take care of the hard things: deploy instantly, scale automatically, and serve personalized content around the globe.
|
||||
|
||||
We make it easy for frontend teams to **develop, preview, and ship** delightful user experiences, where performance is the default.
|
||||
We enable teams to iterate quickly and develop, preview, and ship delightful user experiences. Vercel has zero-configuration support for 35+ frontend frameworks and integrates with your headless content, commerce, or database of choice.
|
||||
|
||||
## Deploy
|
||||
|
||||
@@ -37,4 +35,4 @@ For details on how to use Vercel, check out our [documentation](https://vercel.c
|
||||
|
||||
- [Code of Conduct](./.github/CODE_OF_CONDUCT.md)
|
||||
- [Contributing Guidelines](./.github/CONTRIBUTING.md)
|
||||
- [MIT License](./LICENSE)
|
||||
- [Apache 2.0 License](./LICENSE)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import fs from 'fs/promises';
|
||||
import { join, dirname } from 'path';
|
||||
import execa from 'execa';
|
||||
import { getExampleList } from '../examples/example-list';
|
||||
import { mapOldToNew } from '../examples/map-old-to-new';
|
||||
|
||||
@@ -13,6 +12,18 @@ async function main() {
|
||||
await fs.rm(pubDir, { recursive: true, force: true });
|
||||
await fs.mkdir(pubDir);
|
||||
|
||||
await fs.cp(
|
||||
join(repoRoot, 'packages', 'frameworks', 'logos'),
|
||||
join(pubDir, 'framework-logos'),
|
||||
{ recursive: true, force: true }
|
||||
);
|
||||
|
||||
await fs.cp(
|
||||
join(repoRoot, 'packages', 'fs-detectors', 'logos'),
|
||||
join(pubDir, 'monorepo-logos'),
|
||||
{ recursive: true, force: true }
|
||||
);
|
||||
|
||||
const examples = await getExampleList();
|
||||
const pathListAll = join(pubDir, 'list-all.json');
|
||||
await fs.writeFile(pathListAll, JSON.stringify(examples));
|
||||
@@ -41,10 +52,6 @@ async function main() {
|
||||
JSON.stringify([...existingExamples, ...oldExamples])
|
||||
);
|
||||
|
||||
const { stdout: sha } = await execa('git', ['rev-parse', '--short', 'HEAD'], {
|
||||
cwd: repoRoot,
|
||||
});
|
||||
|
||||
const tarballsDir = join(pubDir, 'tarballs');
|
||||
const packagesDir = join(repoRoot, 'packages');
|
||||
const packages = await fs.readdir(packagesDir);
|
||||
@@ -55,12 +62,21 @@ async function main() {
|
||||
'utf-8'
|
||||
);
|
||||
const packageJson = JSON.parse(packageJsonRaw);
|
||||
const tarballName = `${packageJson.name
|
||||
.replace('@', '')
|
||||
.replace('/', '-')}-v${packageJson.version}-${sha.trim()}.tgz`;
|
||||
const files = await fs.readdir(fullDir);
|
||||
const tarballName = files.find(f => /^vercel-.+\.tgz$/.test(f));
|
||||
if (!tarballName) {
|
||||
throw new Error(
|
||||
`Expected vercel-*.tgz in ${fullDir} but found ${JSON.stringify(
|
||||
files,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
}
|
||||
const srcTarballPath = join(fullDir, tarballName);
|
||||
const destTarballPath = join(tarballsDir, `${packageJson.name}.tgz`);
|
||||
await fs.mkdir(dirname(destTarballPath), { recursive: true });
|
||||
await fs.copyFile(join(fullDir, tarballName), destTarballPath);
|
||||
await fs.copyFile(srcTarballPath, destTarballPath);
|
||||
}
|
||||
|
||||
console.log('Completed building static frontend.');
|
||||
|
||||
@@ -16,10 +16,6 @@ const frameworks = (_frameworks as Framework[])
|
||||
defaultRoutes: undefined,
|
||||
};
|
||||
|
||||
if (framework.logo) {
|
||||
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
||||
}
|
||||
|
||||
return framework;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
9
examples/nextjs/.gitignore
vendored
9
examples/nextjs/.gitignore
vendored
@@ -26,10 +26,11 @@ yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
4739
examples/nextjs/package-lock.json
generated
4739
examples/nextjs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,6 @@
|
||||
{
|
||||
"name": "nextjs",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -7,12 +9,8 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "12.1.4",
|
||||
"react": "18.0.0",
|
||||
"react-dom": "18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "8.12.0",
|
||||
"eslint-config-next": "12.1.4"
|
||||
"next": "13.0.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ export default function Home() {
|
||||
|
||||
<a
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.card}
|
||||
>
|
||||
<h2>Deploy →</h2>
|
||||
|
||||
@@ -114,3 +114,16 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.card,
|
||||
.footer {
|
||||
border-color: #222;
|
||||
}
|
||||
.code {
|
||||
background: #111;
|
||||
}
|
||||
.logo img {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,3 +14,13 @@ a {
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
body {
|
||||
color: white;
|
||||
background: black;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,21 +8,9 @@ Everything you need to build a Svelte project, powered by [`create-svelte`](http
|
||||
|
||||
_Live Example: https://sveltekit-template.vercel.app_
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm init svelte
|
||||
|
||||
# create a new project in my-app
|
||||
npm init svelte my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `pnpm install`, start a development server:
|
||||
Once you've installed dependencies with `pnpm install`, start a development server:
|
||||
|
||||
```bash
|
||||
pnpm run dev
|
||||
|
||||
@@ -10,4 +10,8 @@
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "svelte-kit dev",
|
||||
"build": "svelte-kit build",
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"package": "svelte-kit package",
|
||||
"preview": "svelte-kit preview",
|
||||
"prepare": "svelte-kit sync",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./jsconfig.json",
|
||||
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
|
||||
"lint": "prettier --check --plugin-search-dir=. .",
|
||||
"format": "prettier --write --plugin-search-dir=. ."
|
||||
"lint": "prettier --check .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "1.0.0-next.50",
|
||||
"@sveltejs/kit": "1.0.0-next.347",
|
||||
"@types/cookie": "^0.4.1",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier-plugin-svelte": "^2.5.0",
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
"@sveltejs/kit": "next",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-plugin-svelte": "^2.7.0",
|
||||
"svelte": "^3.46.0",
|
||||
"svelte-check": "^2.2.6",
|
||||
"typescript": "~4.6.2"
|
||||
"svelte-check": "^2.7.1",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.8"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
1458
examples/sveltekit/pnpm-lock.yaml
generated
Normal file
1458
examples/sveltekit/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
7
examples/sveltekit/src/app.d.ts
vendored
7
examples/sveltekit/src/app.d.ts
vendored
@@ -1,7 +1,6 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
interface Locals {
|
||||
userid: string;
|
||||
@@ -9,7 +8,7 @@ declare namespace App {
|
||||
|
||||
// interface Platform {}
|
||||
|
||||
// interface Session {}
|
||||
// interface PrivateEnv {}
|
||||
|
||||
// interface Stuff {}
|
||||
// interface PublicEnv {}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,107 +1,107 @@
|
||||
<script>
|
||||
import { spring } from 'svelte/motion';
|
||||
import { spring } from 'svelte/motion';
|
||||
|
||||
let count = 0;
|
||||
let count = 0;
|
||||
|
||||
const displayed_count = spring();
|
||||
$: displayed_count.set(count);
|
||||
$: offset = modulo($displayed_count, 1);
|
||||
const displayed_count = spring();
|
||||
$: displayed_count.set(count);
|
||||
$: offset = modulo($displayed_count, 1);
|
||||
|
||||
/**
|
||||
* @param {number} n
|
||||
* @param {number} m
|
||||
*/
|
||||
function modulo(n, m) {
|
||||
// handle negative numbers
|
||||
return ((n % m) + m) % m;
|
||||
}
|
||||
/**
|
||||
* @param {number} n
|
||||
* @param {number} m
|
||||
*/
|
||||
function modulo(n, m) {
|
||||
// handle negative numbers
|
||||
return ((n % m) + m) % m;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="counter">
|
||||
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5" />
|
||||
</svg>
|
||||
</button>
|
||||
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="counter-viewport">
|
||||
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
||||
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
||||
<strong>{Math.floor($displayed_count)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter-viewport">
|
||||
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
||||
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
||||
<strong>{Math.floor($displayed_count)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
||||
</svg>
|
||||
</button>
|
||||
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.counter {
|
||||
display: flex;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.counter {
|
||||
display: flex;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.counter button {
|
||||
width: 2em;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
touch-action: manipulation;
|
||||
color: var(--text-color);
|
||||
font-size: 2rem;
|
||||
}
|
||||
.counter button {
|
||||
width: 2em;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
touch-action: manipulation;
|
||||
color: var(--text-color);
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.counter button:hover {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
.counter button:hover {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 25%;
|
||||
height: 25%;
|
||||
}
|
||||
svg {
|
||||
width: 25%;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
path {
|
||||
vector-effect: non-scaling-stroke;
|
||||
stroke-width: 2px;
|
||||
stroke: var(--text-color);
|
||||
}
|
||||
path {
|
||||
vector-effect: non-scaling-stroke;
|
||||
stroke-width: 2px;
|
||||
stroke: var(--text-color);
|
||||
}
|
||||
|
||||
.counter-viewport {
|
||||
width: 8em;
|
||||
height: 4em;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
.counter-viewport {
|
||||
width: 8em;
|
||||
height: 4em;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.counter-viewport strong {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-weight: 400;
|
||||
color: var(--accent-color);
|
||||
font-size: 4rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.counter-viewport strong {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-weight: 400;
|
||||
color: var(--accent-color);
|
||||
font-size: 4rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.counter-digits {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.counter-digits {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
top: -100%;
|
||||
user-select: none;
|
||||
}
|
||||
.hidden {
|
||||
top: -100%;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,11 +31,11 @@ import { invalidate } from '$app/navigation';
|
||||
export function enhance(form, { pending, error, result } = {}) {
|
||||
let current_token;
|
||||
|
||||
/** @param {SubmitEvent} e */
|
||||
async function handle_submit(e) {
|
||||
/** @param {SubmitEvent} event */
|
||||
async function handle_submit(event) {
|
||||
const token = (current_token = {});
|
||||
|
||||
e.preventDefault();
|
||||
event.preventDefault();
|
||||
|
||||
const data = new FormData(form);
|
||||
|
||||
@@ -63,11 +63,11 @@ export function enhance(form, { pending, error, result } = {}) {
|
||||
} else {
|
||||
console.error(await response.text());
|
||||
}
|
||||
} catch (e) {
|
||||
if (error && e instanceof Error) {
|
||||
error({ data, form, error: e, response: null });
|
||||
} catch (err) {
|
||||
if (error && err instanceof Error) {
|
||||
error({ data, form, error: err, response: null });
|
||||
} else {
|
||||
throw e;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,124 +1,124 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import logo from './svelte-logo.svg';
|
||||
import { page } from '$app/stores';
|
||||
import logo from './svelte-logo.svg';
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<div class="corner">
|
||||
<a href="https://kit.svelte.dev">
|
||||
<img src={logo} alt="SvelteKit" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="corner">
|
||||
<a href="https://kit.svelte.dev">
|
||||
<img src={logo} alt="SvelteKit" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
||||
</svg>
|
||||
<ul>
|
||||
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
||||
<li class:active={$page.url.pathname === '/about'}>
|
||||
<a sveltekit:prefetch href="/about">About</a>
|
||||
</li>
|
||||
<li class:active={$page.url.pathname === '/todos'}>
|
||||
<a sveltekit:prefetch href="/todos">Todos</a>
|
||||
</li>
|
||||
</ul>
|
||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
|
||||
</svg>
|
||||
</nav>
|
||||
<nav>
|
||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
||||
</svg>
|
||||
<ul>
|
||||
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
||||
<li class:active={$page.url.pathname === '/about'}>
|
||||
<a sveltekit:prefetch href="/about">About</a>
|
||||
</li>
|
||||
<li class:active={$page.url.pathname === '/todos'}>
|
||||
<a sveltekit:prefetch href="/todos">Todos</a>
|
||||
</li>
|
||||
</ul>
|
||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
|
||||
</svg>
|
||||
</nav>
|
||||
|
||||
<div class="corner">
|
||||
<!-- TODO put something else here? github link? -->
|
||||
</div>
|
||||
<div class="corner">
|
||||
<!-- TODO put something else here? github link? -->
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.corner {
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
}
|
||||
.corner {
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
.corner a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.corner a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.corner img {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
object-fit: contain;
|
||||
}
|
||||
.corner img {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
--background: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
--background: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 2em;
|
||||
height: 3em;
|
||||
display: block;
|
||||
}
|
||||
svg {
|
||||
width: 2em;
|
||||
height: 3em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
path {
|
||||
fill: var(--background);
|
||||
}
|
||||
path {
|
||||
fill: var(--background);
|
||||
}
|
||||
|
||||
ul {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 3em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
list-style: none;
|
||||
background: var(--background);
|
||||
background-size: contain;
|
||||
}
|
||||
ul {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 3em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
list-style: none;
|
||||
background: var(--background);
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
li {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
li.active::before {
|
||||
--size: 6px;
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(50% - var(--size));
|
||||
border: var(--size) solid transparent;
|
||||
border-top: var(--size) solid var(--accent-color);
|
||||
}
|
||||
li.active::before {
|
||||
--size: 6px;
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(50% - var(--size));
|
||||
border: var(--size) solid transparent;
|
||||
border-top: var(--size) solid var(--accent-color);
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
padding: 0 1em;
|
||||
color: var(--heading-color);
|
||||
font-weight: 700;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s linear;
|
||||
}
|
||||
nav a {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
padding: 0 1em;
|
||||
color: var(--heading-color);
|
||||
font-weight: 700;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s linear;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
a:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
58
examples/sveltekit/src/routes/+layout.svelte
Normal file
58
examples/sveltekit/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,58 @@
|
||||
<script>
|
||||
import Header from '$lib/header/Header.svelte';
|
||||
import { webVitals } from '$lib/vitals';
|
||||
import { browser } from '$app/env';
|
||||
import { page } from '$app/stores';
|
||||
import '../app.css';
|
||||
|
||||
let analyticsId = import.meta.env.VERCEL_ANALYTICS_ID;
|
||||
|
||||
$: if (browser && analyticsId) {
|
||||
webVitals({
|
||||
path: $page.url.pathname,
|
||||
params: $page.params,
|
||||
analyticsId
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
footer {
|
||||
padding: 40px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1
examples/sveltekit/src/routes/+page.js
Normal file
1
examples/sveltekit/src/routes/+page.js
Normal file
@@ -0,0 +1 @@
|
||||
export const prerender = true;
|
||||
57
examples/sveltekit/src/routes/+page.svelte
Normal file
57
examples/sveltekit/src/routes/+page.svelte
Normal file
@@ -0,0 +1,57 @@
|
||||
<script>
|
||||
import Counter from '$lib/Counter.svelte';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Home</title>
|
||||
<meta name="description" content="Svelte demo app" />
|
||||
</svelte:head>
|
||||
|
||||
<section>
|
||||
<h1>
|
||||
<span class="welcome">
|
||||
<picture>
|
||||
<source srcset="svelte-welcome.webp" type="image/webp" />
|
||||
<img src="svelte-welcome.png" alt="Welcome" />
|
||||
</picture>
|
||||
</span>
|
||||
|
||||
to your new<br />SvelteKit app
|
||||
</h1>
|
||||
|
||||
<h2>
|
||||
try editing <strong>src/routes/index.svelte</strong>
|
||||
</h2>
|
||||
|
||||
<Counter />
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
h1 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding: 0 0 calc(100% * 495 / 2048) 0;
|
||||
}
|
||||
|
||||
.welcome img {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -1,58 +0,0 @@
|
||||
<script>
|
||||
import Header from '$lib/header/Header.svelte';
|
||||
import { webVitals } from '$lib/vitals';
|
||||
import { browser } from '$app/env';
|
||||
import { page } from '$app/stores';
|
||||
import '../app.css';
|
||||
|
||||
let analyticsId = import.meta.env.VERCEL_ANALYTICS_ID;
|
||||
|
||||
$: if (browser && analyticsId) {
|
||||
webVitals({
|
||||
path: $page.url.pathname,
|
||||
params: $page.params,
|
||||
analyticsId
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
footer {
|
||||
padding: 40px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,50 +0,0 @@
|
||||
<script context="module">
|
||||
import { browser, dev } from '$app/env';
|
||||
|
||||
// we don't need any JS on this page, though we'll load
|
||||
// it in dev so that we get hot module replacement...
|
||||
export const hydrate = dev;
|
||||
|
||||
// ...but if the client-side router is already loaded
|
||||
// (i.e. we came here from elsewhere in the app), use it
|
||||
export const router = browser;
|
||||
|
||||
// since there's no dynamic data here, we can prerender
|
||||
// it so that it gets served as a static asset in prod
|
||||
export const prerender = true;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>About</title>
|
||||
<meta name="description" content="About this app" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="content">
|
||||
<h1>About this app</h1>
|
||||
|
||||
<p>
|
||||
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
|
||||
following into your command line and following the prompts:
|
||||
</p>
|
||||
|
||||
<pre>npm init svelte</pre>
|
||||
|
||||
<p>
|
||||
The page you're looking at is purely static HTML, with no client-side interactivity needed.
|
||||
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
|
||||
the devtools network panel and reloading.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
|
||||
it with JavaScript disabled!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: var(--column-width);
|
||||
margin: var(--column-margin-top) auto 0 auto;
|
||||
}
|
||||
</style>
|
||||
13
examples/sveltekit/src/routes/about/+page.js
Normal file
13
examples/sveltekit/src/routes/about/+page.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { browser, dev } from '$app/env';
|
||||
|
||||
// we don't need any JS on this page, though we'll load
|
||||
// it in dev so that we get hot module replacement...
|
||||
export const hydrate = dev;
|
||||
|
||||
// ...but if the client-side router is already loaded
|
||||
// (i.e. we came here from elsewhere in the app), use it
|
||||
export const router = browser;
|
||||
|
||||
// since there's no dynamic data here, we can prerender
|
||||
// it so that it gets served as a static asset in prod
|
||||
export const prerender = true;
|
||||
34
examples/sveltekit/src/routes/about/+page.svelte
Normal file
34
examples/sveltekit/src/routes/about/+page.svelte
Normal file
@@ -0,0 +1,34 @@
|
||||
<svelte:head>
|
||||
<title>About</title>
|
||||
<meta name="description" content="About this app" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="content">
|
||||
<h1>About this app</h1>
|
||||
|
||||
<p>
|
||||
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
|
||||
following into your command line and following the prompts:
|
||||
</p>
|
||||
|
||||
<pre>npm create svelte@latest</pre>
|
||||
|
||||
<p>
|
||||
The page you're looking at is purely static HTML, with no client-side interactivity needed.
|
||||
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
|
||||
the devtools network panel and reloading.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
|
||||
it with JavaScript disabled!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: var(--column-width);
|
||||
margin: var(--column-margin-top) auto 0 auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,60 +0,0 @@
|
||||
<script context="module">
|
||||
export const prerender = true;
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import Counter from '$lib/Counter.svelte';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Home</title>
|
||||
<meta name="description" content="Svelte demo app" />
|
||||
</svelte:head>
|
||||
|
||||
<section>
|
||||
<h1>
|
||||
<div class="welcome">
|
||||
<picture>
|
||||
<source srcset="svelte-welcome.webp" type="image/webp" />
|
||||
<img src="svelte-welcome.png" alt="Welcome" />
|
||||
</picture>
|
||||
</div>
|
||||
|
||||
to your new<br />SvelteKit app
|
||||
</h1>
|
||||
|
||||
<h2>
|
||||
try editing <strong>src/routes/index.svelte</strong>
|
||||
</h2>
|
||||
|
||||
<Counter />
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
h1 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding: 0 0 calc(100% * 495 / 2048) 0;
|
||||
}
|
||||
|
||||
.welcome img {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
62
examples/sveltekit/src/routes/todos/+page.server.js
Normal file
62
examples/sveltekit/src/routes/todos/+page.server.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { api } from './api';
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* uid: string;
|
||||
* created_at: Date;
|
||||
* text: string;
|
||||
* done: boolean;
|
||||
* pending_delete: boolean;
|
||||
* }} Todo
|
||||
*/
|
||||
|
||||
/** @type {import('./$types').PageServerLoad} */
|
||||
export const load = async ({ locals }) => {
|
||||
// locals.userid comes from src/hooks.js
|
||||
const response = await api('GET', `todos/${locals.userid}`);
|
||||
|
||||
if (response.status === 404) {
|
||||
// user hasn't created a todo list.
|
||||
// start with an empty array
|
||||
return {
|
||||
/** @type {Todo[]} */
|
||||
todos: []
|
||||
};
|
||||
}
|
||||
|
||||
if (response.status === 200) {
|
||||
return {
|
||||
/** @type {Todo[]} */
|
||||
todos: await response.json()
|
||||
};
|
||||
}
|
||||
|
||||
throw error(response.status);
|
||||
};
|
||||
|
||||
/** @type {import('./$types').Action} */
|
||||
export const POST = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('POST', `todos/${locals.userid}`, {
|
||||
text: form.get('text')
|
||||
});
|
||||
};
|
||||
|
||||
/** @type {import('./$types').Action} */
|
||||
export const PATCH = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('PATCH', `todos/${locals.userid}/${form.get('uid')}`, {
|
||||
text: form.has('text') ? form.get('text') : undefined,
|
||||
done: form.has('done') ? !!form.get('done') : undefined
|
||||
});
|
||||
};
|
||||
|
||||
/** @type {import('./$types').Action} */
|
||||
export const DELETE = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('DELETE', `todos/${locals.userid}/${form.get('uid')}`);
|
||||
};
|
||||
180
examples/sveltekit/src/routes/todos/+page.svelte
Normal file
180
examples/sveltekit/src/routes/todos/+page.svelte
Normal file
@@ -0,0 +1,180 @@
|
||||
<script>
|
||||
import { enhance } from '$lib/form';
|
||||
import { scale } from 'svelte/transition';
|
||||
import { flip } from 'svelte/animate';
|
||||
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Todos</title>
|
||||
<meta name="description" content="A todo list app" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="todos">
|
||||
<h1>Todos</h1>
|
||||
|
||||
<form
|
||||
class="new"
|
||||
action="/todos"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
result: async ({ form }) => {
|
||||
form.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
|
||||
</form>
|
||||
|
||||
{#each data.todos as todo (todo.uid)}
|
||||
<div
|
||||
class="todo"
|
||||
class:done={todo.done}
|
||||
transition:scale|local={{ start: 0.7 }}
|
||||
animate:flip={{ duration: 200 }}
|
||||
>
|
||||
<form
|
||||
action="/todos?_method=PATCH"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: ({ data }) => {
|
||||
todo.done = !!data.get('done');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
|
||||
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
|
||||
</form>
|
||||
|
||||
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
|
||||
<button class="save" aria-label="Save todo" />
|
||||
</form>
|
||||
|
||||
<form
|
||||
action="/todos?_method=DELETE"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: () => (todo.pending_delete = true)
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
|
||||
</form>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.todos {
|
||||
width: 100%;
|
||||
max-width: var(--column-width);
|
||||
margin: var(--column-margin-top) auto 0 auto;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.new {
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
input:focus-visible {
|
||||
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #ff3e00 !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.new input {
|
||||
font-size: 28px;
|
||||
width: 100%;
|
||||
padding: 0.5em 1em 0.3em 1em;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.todo {
|
||||
display: grid;
|
||||
grid-template-columns: 2rem 1fr 2rem;
|
||||
grid-gap: 0.5rem;
|
||||
align-items: center;
|
||||
margin: 0 0 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
|
||||
transform: translate(-1px, -1px);
|
||||
transition: filter 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.done {
|
||||
transform: none;
|
||||
opacity: 0.4;
|
||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
form.text {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.todo input {
|
||||
flex: 1;
|
||||
padding: 0.5em 2em 0.5em 0.8em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.todo button {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
button.toggle {
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
background-size: 1em auto;
|
||||
}
|
||||
|
||||
.done .toggle {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.delete {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.delete:hover,
|
||||
.delete:focus {
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.save {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
|
||||
}
|
||||
|
||||
.todo input:focus + .save,
|
||||
.save:focus {
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,7 @@
|
||||
/*
|
||||
This module is used by the /todos endpoint to
|
||||
make calls to api.svelte.dev, which stores todos
|
||||
for each user. The leading underscore indicates that this is
|
||||
a private module, _not_ an endpoint — visiting /todos/_api
|
||||
will net you a 404 response.
|
||||
for each user.
|
||||
|
||||
(The data on the todo app will expire periodically; no
|
||||
guarantees are made. Don't use it to organise your life.)
|
||||
@@ -1,70 +0,0 @@
|
||||
import { api } from './_api';
|
||||
|
||||
/** @type {import('./__types').RequestHandler} */
|
||||
export const get = async ({ locals }) => {
|
||||
// locals.userid comes from src/hooks.js
|
||||
const response = await api('get', `todos/${locals.userid}`);
|
||||
|
||||
if (response.status === 404) {
|
||||
// user hasn't created a todo list.
|
||||
// start with an empty array
|
||||
return {
|
||||
body: {
|
||||
todos: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (response.status === 200) {
|
||||
return {
|
||||
body: {
|
||||
todos: await response.json()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status
|
||||
};
|
||||
};
|
||||
|
||||
/** @type {import('./index').RequestHandler} */
|
||||
export const post = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('post', `todos/${locals.userid}`, {
|
||||
text: form.get('text')
|
||||
});
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
// If the user has JavaScript disabled, the URL will change to
|
||||
// include the method override unless we redirect back to /todos
|
||||
const redirect = {
|
||||
status: 303,
|
||||
headers: {
|
||||
location: '/todos'
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {import('./index').RequestHandler} */
|
||||
export const patch = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('patch', `todos/${locals.userid}/${form.get('uid')}`, {
|
||||
text: form.has('text') ? form.get('text') : undefined,
|
||||
done: form.has('done') ? !!form.get('done') : undefined
|
||||
});
|
||||
|
||||
return redirect;
|
||||
};
|
||||
|
||||
/** @type {import('./index').RequestHandler} */
|
||||
export const del = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('delete', `todos/${locals.userid}/${form.get('uid')}`);
|
||||
|
||||
return redirect;
|
||||
};
|
||||
@@ -1,190 +0,0 @@
|
||||
<script>
|
||||
import { enhance } from '$lib/form';
|
||||
import { scale } from 'svelte/transition';
|
||||
import { flip } from 'svelte/animate';
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* uid: string;
|
||||
* created_at: Date;
|
||||
* text: string;
|
||||
* done: boolean;
|
||||
* pending_delete: boolean;
|
||||
* }} Todo
|
||||
*/
|
||||
|
||||
/** @type {Todo[]} */
|
||||
export let todos;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Todos</title>
|
||||
<meta name="description" content="A todo list app" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="todos">
|
||||
<h1>Todos</h1>
|
||||
|
||||
<form
|
||||
class="new"
|
||||
action="/todos"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
result: async ({ form }) => {
|
||||
form.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
|
||||
</form>
|
||||
|
||||
{#each todos as todo (todo.uid)}
|
||||
<div
|
||||
class="todo"
|
||||
class:done={todo.done}
|
||||
transition:scale|local={{ start: 0.7 }}
|
||||
animate:flip={{ duration: 200 }}
|
||||
>
|
||||
<form
|
||||
action="/todos?_method=PATCH"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: ({ data }) => {
|
||||
todo.done = !!data.get('done');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
|
||||
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
|
||||
</form>
|
||||
|
||||
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
|
||||
<button class="save" aria-label="Save todo" />
|
||||
</form>
|
||||
|
||||
<form
|
||||
action="/todos?_method=DELETE"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: () => (todo.pending_delete = true)
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
|
||||
</form>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.todos {
|
||||
width: 100%;
|
||||
max-width: var(--column-width);
|
||||
margin: var(--column-margin-top) auto 0 auto;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.new {
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
input:focus-visible {
|
||||
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #ff3e00 !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.new input {
|
||||
font-size: 28px;
|
||||
width: 100%;
|
||||
padding: 0.5em 1em 0.3em 1em;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.todo {
|
||||
display: grid;
|
||||
grid-template-columns: 2rem 1fr 2rem;
|
||||
grid-gap: 0.5rem;
|
||||
align-items: center;
|
||||
margin: 0 0 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
|
||||
transform: translate(-1px, -1px);
|
||||
transition: filter 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.done {
|
||||
transform: none;
|
||||
opacity: 0.4;
|
||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
form.text {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.todo input {
|
||||
flex: 1;
|
||||
padding: 0.5em 2em 0.5em 0.8em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.todo button {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
button.toggle {
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
background-size: 1em auto;
|
||||
}
|
||||
|
||||
.done .toggle {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.delete {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.delete:hover,
|
||||
.delete:focus {
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.save {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
|
||||
}
|
||||
|
||||
.todo input:focus + .save,
|
||||
.save:focus {
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -8,11 +8,6 @@ const config = {
|
||||
// Override http methods in the Todo forms
|
||||
methodOverride: {
|
||||
allowed: ['PATCH', 'DELETE']
|
||||
},
|
||||
vite: {
|
||||
define: {
|
||||
'import.meta.env.VERCEL_ANALYTICS_ID': JSON.stringify(process.env.VERCEL_ANALYTICS_ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
11
examples/sveltekit/vite.config.js
Normal file
11
examples/sveltekit/vite.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
|
||||
/** @type {import('vite').UserConfig} */
|
||||
const config = {
|
||||
plugins: [sveltekit()],
|
||||
define: {
|
||||
'import.meta.env.VERCEL_ANALYTICS_ID': JSON.stringify(process.env.VERCEL_ANALYTICS_ID)
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@@ -3,12 +3,16 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"packageManager": "yarn@1.22.19",
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"nohoist": [
|
||||
"**/@types/**"
|
||||
"**/@types/**",
|
||||
"**/typedoc",
|
||||
"**/typedoc-plugin-markdown",
|
||||
"**/typedoc-plugin-mdn-links"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -31,7 +35,7 @@
|
||||
"prettier": "2.6.2",
|
||||
"ts-eager": "2.0.2",
|
||||
"ts-jest": "28.0.5",
|
||||
"turbo": "1.3.2-canary.1"
|
||||
"turbo": "1.4.7"
|
||||
},
|
||||
"scripts": {
|
||||
"lerna": "lerna",
|
||||
@@ -40,14 +44,14 @@
|
||||
"publish-canary": "git checkout main && git pull && lerna version prerelease --preid canary --message \"Publish Canary\" --exact",
|
||||
"publish-from-github": "./utils/publish.sh",
|
||||
"changelog": "node utils/changelog.js",
|
||||
"build": "turbo run build",
|
||||
"build": "node utils/gen.js && turbo run build",
|
||||
"vercel-build": "yarn build && yarn run pack && cd api && node -r ts-eager/register ./_lib/script/build.ts",
|
||||
"pre-commit": "lint-staged",
|
||||
"test": "jest --rootDir=\"test\" --testPathPattern=\"\\.test.js\"",
|
||||
"test-unit": "yarn test && turbo run test-unit",
|
||||
"test-integration-cli": "turbo run test-integration-cli",
|
||||
"test-integration-once": "turbo run test-integration-once",
|
||||
"test-integration-dev": "turbo run test-integration-dev",
|
||||
"test-unit": "yarn test && node utils/gen.js && turbo run test-unit",
|
||||
"test-integration-cli": "node utils/gen.js && turbo run test-integration-cli",
|
||||
"test-integration-once": "node utils/gen.js && turbo run test-integration-once",
|
||||
"test-integration-dev": "node utils/gen.js && turbo run test-integration-dev",
|
||||
"lint": "eslint . --ext .ts,.js",
|
||||
"prepare": "husky install",
|
||||
"pack": "cd utils && node -r ts-eager/register ./pack.ts"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "5.3.2",
|
||||
"version": "5.5.6",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
34
packages/build-utils/src/clone-env.ts
Normal file
34
packages/build-utils/src/clone-env.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
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;
|
||||
}
|
||||
|
||||
// mixin the env first
|
||||
obj = Object.assign(obj, env);
|
||||
|
||||
if (hasOwnProperty.call(env, 'Path')) {
|
||||
// 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, but we must also remove the
|
||||
// `Path` property since we can't have both a `PATH` and `Path`
|
||||
obj.PATH = obj.Path;
|
||||
delete obj.Path;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
@@ -3,5 +3,7 @@ import { getPlatformEnv } from './get-platform-env';
|
||||
export default function debug(message: string, ...additional: any[]) {
|
||||
if (getPlatformEnv('BUILDER_DEBUG')) {
|
||||
console.log(message, ...additional);
|
||||
} else if (process.env.VERCEL_DEBUG_PREFIX) {
|
||||
console.log(`${process.env.VERCEL_DEBUG_PREFIX}${message}`, ...additional);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ export class EdgeFunction {
|
||||
*/
|
||||
assets?: { name: string; path: string }[];
|
||||
|
||||
/** The regions where the edge function will be executed on */
|
||||
regions?: string | string[];
|
||||
|
||||
constructor(params: Omit<EdgeFunction, 'type'>) {
|
||||
this.type = 'EdgeFunction';
|
||||
this.name = params.name;
|
||||
@@ -46,5 +49,6 @@ export class EdgeFunction {
|
||||
this.files = params.files;
|
||||
this.envVarsInUse = params.envVarsInUse;
|
||||
this.assets = params.assets;
|
||||
this.regions = params.regions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,14 +61,14 @@ export function getPrettyError(obj: {
|
||||
}
|
||||
|
||||
return new NowBuildError({
|
||||
code: 'DEV_VALIDATE_CONFIG',
|
||||
code: 'INVALID_VERCEL_CONFIG',
|
||||
message: message,
|
||||
link: prop ? `${docsUrl}#project/${prop.toLowerCase()}` : docsUrl,
|
||||
action: 'View Documentation',
|
||||
});
|
||||
} catch (e) {
|
||||
return new NowBuildError({
|
||||
code: 'DEV_VALIDATE_CONFIG',
|
||||
code: 'INVALID_VERCEL_CONFIG',
|
||||
message: `Failed to validate configuration.`,
|
||||
link: docsUrl,
|
||||
action: 'View Documentation',
|
||||
|
||||
@@ -5,7 +5,13 @@ import path from 'path';
|
||||
import Sema from 'async-sema';
|
||||
import { FileBase } from './types';
|
||||
|
||||
const semaToPreventEMFILE = new Sema(20);
|
||||
const DEFAULT_SEMA = 20;
|
||||
const semaToPreventEMFILE = new Sema(
|
||||
parseInt(
|
||||
process.env.VERCEL_INTERNAL_FILE_FS_REF_SEMA || String(DEFAULT_SEMA),
|
||||
10
|
||||
) || DEFAULT_SEMA
|
||||
);
|
||||
|
||||
interface FileFsRefOptions {
|
||||
mode?: number;
|
||||
|
||||
@@ -12,7 +12,13 @@ interface FileRefOptions {
|
||||
mutable?: boolean;
|
||||
}
|
||||
|
||||
const semaToDownloadFromS3 = new Sema(5);
|
||||
const DEFAULT_SEMA = 5;
|
||||
const semaToDownloadFromS3 = new Sema(
|
||||
parseInt(
|
||||
process.env.VERCEL_INTERNAL_FILE_REF_SEMA || String(DEFAULT_SEMA),
|
||||
10
|
||||
) || DEFAULT_SEMA
|
||||
);
|
||||
|
||||
class BailableError extends Error {
|
||||
public bail: boolean;
|
||||
|
||||
@@ -3,28 +3,37 @@ import { NodeVersion } from '../types';
|
||||
import { NowBuildError } from '../errors';
|
||||
import debug from '../debug';
|
||||
|
||||
const allOptions = [
|
||||
{ major: 16, range: '16.x', runtime: 'nodejs16.x' },
|
||||
{ major: 14, range: '14.x', runtime: 'nodejs14.x' },
|
||||
{
|
||||
major: 12,
|
||||
range: '12.x',
|
||||
runtime: 'nodejs12.x',
|
||||
discontinueDate: new Date('2022-10-01'),
|
||||
},
|
||||
{
|
||||
major: 10,
|
||||
range: '10.x',
|
||||
runtime: 'nodejs10.x',
|
||||
discontinueDate: new Date('2021-04-20'),
|
||||
},
|
||||
{
|
||||
major: 8,
|
||||
range: '8.10.x',
|
||||
runtime: 'nodejs8.10',
|
||||
discontinueDate: new Date('2020-01-06'),
|
||||
},
|
||||
] as const;
|
||||
function getOptions() {
|
||||
const options = [
|
||||
{ major: 16, range: '16.x', runtime: 'nodejs16.x' },
|
||||
{ major: 14, range: '14.x', runtime: 'nodejs14.x' },
|
||||
{
|
||||
major: 12,
|
||||
range: '12.x',
|
||||
runtime: 'nodejs12.x',
|
||||
discontinueDate: new Date('2022-10-03'),
|
||||
},
|
||||
{
|
||||
major: 10,
|
||||
range: '10.x',
|
||||
runtime: 'nodejs10.x',
|
||||
discontinueDate: new Date('2021-04-20'),
|
||||
},
|
||||
{
|
||||
major: 8,
|
||||
range: '8.10.x',
|
||||
runtime: 'nodejs8.10',
|
||||
discontinueDate: new Date('2020-01-06'),
|
||||
},
|
||||
] as const;
|
||||
if (process.env.VERCEL_ALLOW_NODEJS18 === '1') {
|
||||
return [
|
||||
{ major: 18, range: '18.x', runtime: 'nodejs18.x' },
|
||||
...options,
|
||||
] as const;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function getHint(isAuto = false) {
|
||||
const { major, range } = getLatestNodeVersion();
|
||||
@@ -34,11 +43,11 @@ function getHint(isAuto = false) {
|
||||
}
|
||||
|
||||
export function getLatestNodeVersion() {
|
||||
return allOptions[0];
|
||||
return getOptions()[0];
|
||||
}
|
||||
|
||||
export function getDiscontinuedNodeVersions(): NodeVersion[] {
|
||||
return allOptions.filter(isDiscontinued);
|
||||
return getOptions().filter(isDiscontinued);
|
||||
}
|
||||
|
||||
export async function getSupportedNodeVersion(
|
||||
@@ -50,7 +59,7 @@ export async function getSupportedNodeVersion(
|
||||
if (engineRange) {
|
||||
const found =
|
||||
validRange(engineRange) &&
|
||||
allOptions.some(o => {
|
||||
getOptions().some(o => {
|
||||
// the array is already in order so return the first
|
||||
// match which will be the newest version of node
|
||||
selection = o;
|
||||
|
||||
@@ -11,6 +11,7 @@ import { NowBuildError } from '../errors';
|
||||
import { Meta, PackageJson, NodeVersion, Config } from '../types';
|
||||
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
|
||||
import { readConfigFile } from './read-config-file';
|
||||
import { cloneEnv } from '../clone-env';
|
||||
|
||||
// Only allow one `runNpmInstall()` invocation to run concurrently
|
||||
const runNpmInstallSema = new Sema(1);
|
||||
@@ -23,8 +24,7 @@ export interface ScanParentDirsResult {
|
||||
*/
|
||||
cliType: CliType;
|
||||
/**
|
||||
* The file path of found `package.json` file, or `undefined` if none was
|
||||
* found.
|
||||
* The file path of found `package.json` file, or `undefined` if not found.
|
||||
*/
|
||||
packageJsonPath?: string;
|
||||
/**
|
||||
@@ -33,8 +33,13 @@ export interface ScanParentDirsResult {
|
||||
*/
|
||||
packageJson?: PackageJson;
|
||||
/**
|
||||
* The `lockfileVersion` number from the `package-lock.json` file,
|
||||
* when present.
|
||||
* The file path of the lockfile (`yarn.lock`, `package-lock.json`, or `pnpm-lock.yaml`)
|
||||
* or `undefined` if not found.
|
||||
*/
|
||||
lockfilePath?: string;
|
||||
/**
|
||||
* The `lockfileVersion` number from lockfile (`package-lock.json` or `pnpm-lock.yaml`),
|
||||
* or `undefined` if not found.
|
||||
*/
|
||||
lockfileVersion?: number;
|
||||
}
|
||||
@@ -178,25 +183,9 @@ export async function getNodeBinPath({
|
||||
}: {
|
||||
cwd: string;
|
||||
}): Promise<string> {
|
||||
const { code, stdout, stderr } = await execAsync('npm', ['bin'], {
|
||||
cwd,
|
||||
prettyCommand: 'npm bin',
|
||||
|
||||
// in some rare cases, we saw `npm bin` exit with a non-0 code, but still
|
||||
// output the right bin path, so we ignore the exit code
|
||||
ignoreNon0Exit: true,
|
||||
});
|
||||
|
||||
const nodeBinPath = stdout.trim();
|
||||
|
||||
if (path.isAbsolute(nodeBinPath)) {
|
||||
return nodeBinPath;
|
||||
}
|
||||
|
||||
throw new NowBuildError({
|
||||
code: `BUILD_UTILS_GET_NODE_BIN_PATH`,
|
||||
message: `Running \`npm bin\` failed to return a valid bin path (code=${code}, stdout=${stdout}, stderr=${stderr})`,
|
||||
});
|
||||
const { lockfilePath } = await scanParentDirs(cwd);
|
||||
const dir = path.dirname(lockfilePath || cwd);
|
||||
return path.join(dir, 'node_modules', '.bin');
|
||||
}
|
||||
|
||||
async function chmodPlusX(fsPath: string) {
|
||||
@@ -229,7 +218,7 @@ export function getSpawnOptions(
|
||||
nodeVersion: NodeVersion
|
||||
): SpawnOptions {
|
||||
const opts = {
|
||||
env: { ...process.env },
|
||||
env: cloneEnv(process.env),
|
||||
};
|
||||
|
||||
if (!meta.isDev) {
|
||||
@@ -262,33 +251,24 @@ export async function getNodeVersion(
|
||||
meta: Meta = {}
|
||||
): Promise<NodeVersion> {
|
||||
const latest = getLatestNodeVersion();
|
||||
if (meta && meta.isDev) {
|
||||
if (meta.isDev) {
|
||||
// Use the system-installed version of `node` in PATH for `vercel dev`
|
||||
return { ...latest, runtime: 'nodejs' };
|
||||
}
|
||||
const { packageJson } = await scanParentDirs(destPath, true);
|
||||
let { nodeVersion } = config;
|
||||
let isAuto = true;
|
||||
if (packageJson && packageJson.engines && packageJson.engines.node) {
|
||||
if (packageJson?.engines?.node) {
|
||||
const { node } = packageJson.engines;
|
||||
if (
|
||||
nodeVersion &&
|
||||
validRange(node) &&
|
||||
!intersects(nodeVersion, node) &&
|
||||
!meta.isDev
|
||||
) {
|
||||
if (nodeVersion && validRange(node) && !intersects(nodeVersion, node)) {
|
||||
console.warn(
|
||||
`Warning: Due to "engines": { "node": "${node}" } in your \`package.json\` file, the Node.js Version defined in your Project Settings ("${nodeVersion}") will not apply. Learn More: http://vercel.link/node-version`
|
||||
);
|
||||
} else if (coerce(node)?.raw === node && !meta.isDev) {
|
||||
} else if (coerce(node)?.raw === node) {
|
||||
console.warn(
|
||||
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` with major.minor.patch, but only major Node.js Version can be selected. Learn More: http://vercel.link/node-version`
|
||||
);
|
||||
} else if (
|
||||
validRange(node) &&
|
||||
intersects(`${latest.major + 1}.x`, node) &&
|
||||
!meta.isDev
|
||||
) {
|
||||
} else if (validRange(node) && intersects(`${latest.major + 1}.x`, node)) {
|
||||
console.warn(
|
||||
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version`
|
||||
);
|
||||
@@ -319,6 +299,7 @@ export async function scanParentDirs(
|
||||
start: destPath,
|
||||
filenames: ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml'],
|
||||
});
|
||||
let lockfilePath: string | undefined;
|
||||
let lockfileVersion: number | undefined;
|
||||
let cliType: CliType = 'yarn';
|
||||
|
||||
@@ -335,17 +316,25 @@ export async function scanParentDirs(
|
||||
// Priority order is Yarn > pnpm > npm
|
||||
if (hasYarnLock) {
|
||||
cliType = 'yarn';
|
||||
lockfilePath = yarnLockPath;
|
||||
} else if (pnpmLockYaml) {
|
||||
cliType = 'pnpm';
|
||||
// just ensure that it is read as a number and not a string
|
||||
lockfilePath = pnpmLockPath;
|
||||
lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
|
||||
} else if (packageLockJson) {
|
||||
cliType = 'npm';
|
||||
lockfilePath = npmLockPath;
|
||||
lockfileVersion = packageLockJson.lockfileVersion;
|
||||
}
|
||||
|
||||
const packageJsonPath = pkgJsonPath || undefined;
|
||||
return { cliType, packageJson, lockfileVersion, packageJsonPath };
|
||||
return {
|
||||
cliType,
|
||||
packageJson,
|
||||
lockfilePath,
|
||||
lockfileVersion,
|
||||
packageJsonPath,
|
||||
};
|
||||
}
|
||||
|
||||
export async function walkParentDirs({
|
||||
@@ -452,7 +441,7 @@ export async function runNpmInstall(
|
||||
debug(`Installing to ${destPath}`);
|
||||
|
||||
const opts: SpawnOptionsExtended = { cwd: destPath, ...spawnOpts };
|
||||
const env = opts.env ? { ...opts.env } : { ...process.env };
|
||||
const env = cloneEnv(opts.env || process.env);
|
||||
delete env.NODE_ENV;
|
||||
opts.env = getEnvForPackageManager({
|
||||
cliType,
|
||||
@@ -461,12 +450,30 @@ export async function runNpmInstall(
|
||||
env,
|
||||
});
|
||||
let commandArgs: string[];
|
||||
const isPotentiallyBrokenNpm =
|
||||
cliType === 'npm' &&
|
||||
(nodeVersion?.major === 16 ||
|
||||
opts.env.PATH?.includes('/node16/bin-npm7')) &&
|
||||
!args.includes('--legacy-peer-deps') &&
|
||||
spawnOpts?.env?.ENABLE_EXPERIMENTAL_COREPACK !== '1';
|
||||
|
||||
if (cliType === 'npm') {
|
||||
opts.prettyCommand = 'npm install';
|
||||
commandArgs = args
|
||||
.filter(a => a !== '--prefer-offline')
|
||||
.concat(['install', '--no-audit', '--unsafe-perm']);
|
||||
if (
|
||||
isPotentiallyBrokenNpm &&
|
||||
spawnOpts?.env?.VERCEL_NPM_LEGACY_PEER_DEPS === '1'
|
||||
) {
|
||||
// Starting in npm@8.6.0, if you ran `npm install --legacy-peer-deps`,
|
||||
// and then later ran `npm install`, it would fail. So the only way
|
||||
// to safely upgrade npm from npm@8.5.0 is to set this flag. The docs
|
||||
// say this flag is not recommended so its is behind a feature flag
|
||||
// so we can remove it in node@18, which can introduce breaking changes.
|
||||
// See https://docs.npmjs.com/cli/v8/using-npm/config#legacy-peer-deps
|
||||
commandArgs.push('--legacy-peer-deps');
|
||||
}
|
||||
} else if (cliType === 'pnpm') {
|
||||
// PNPM's install command is similar to NPM's but without the audit nonsense
|
||||
// @see options https://pnpm.io/cli/install
|
||||
@@ -483,7 +490,26 @@ export async function runNpmInstall(
|
||||
commandArgs.push('--production');
|
||||
}
|
||||
|
||||
await spawnAsync(cliType, commandArgs, opts);
|
||||
try {
|
||||
await spawnAsync(cliType, commandArgs, opts);
|
||||
} catch (_) {
|
||||
const potentialErrorPath = path.join(
|
||||
process.env.HOME || '/',
|
||||
'.npm',
|
||||
'eresolve-report.txt'
|
||||
);
|
||||
if (
|
||||
isPotentiallyBrokenNpm &&
|
||||
!commandArgs.includes('--legacy-peer-deps') &&
|
||||
fs.existsSync(potentialErrorPath)
|
||||
) {
|
||||
console.warn(
|
||||
'Warning: Retrying "Install Command" with `--legacy-peer-deps` which may accept a potentially broken dependency and slow install time.'
|
||||
);
|
||||
commandArgs.push('--legacy-peer-deps');
|
||||
await spawnAsync(cliType, commandArgs, opts);
|
||||
}
|
||||
}
|
||||
debug(`Install complete [${Date.now() - installTime}ms]`);
|
||||
return true;
|
||||
} finally {
|
||||
@@ -594,10 +620,7 @@ export async function runPackageJsonScript(
|
||||
cliType,
|
||||
lockfileVersion,
|
||||
nodeVersion: undefined,
|
||||
env: {
|
||||
...process.env,
|
||||
...spawnOpts?.env,
|
||||
},
|
||||
env: cloneEnv(process.env, spawnOpts?.env),
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import debug from './debug';
|
||||
import getIgnoreFilter from './get-ignore-filter';
|
||||
import { getPlatformEnv } from './get-platform-env';
|
||||
import { getPrefixedEnvVars } from './get-prefixed-env-vars';
|
||||
import { cloneEnv } from './clone-env';
|
||||
|
||||
export {
|
||||
FileBlob,
|
||||
@@ -84,6 +85,7 @@ export {
|
||||
getLambdaOptionsFromFunction,
|
||||
scanParentDirs,
|
||||
getIgnoreFilter,
|
||||
cloneEnv,
|
||||
};
|
||||
|
||||
export { EdgeFunction } from './edge-function';
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface LambdaOptionsBase {
|
||||
regions?: string[];
|
||||
supportsMultiPayloads?: boolean;
|
||||
supportsWrapper?: boolean;
|
||||
experimentalResponseStreaming?: boolean;
|
||||
}
|
||||
|
||||
export interface LambdaOptionsWithFiles extends LambdaOptionsBase {
|
||||
@@ -60,6 +61,7 @@ export class Lambda {
|
||||
zipBuffer?: Buffer;
|
||||
supportsMultiPayloads?: boolean;
|
||||
supportsWrapper?: boolean;
|
||||
experimentalResponseStreaming?: boolean;
|
||||
|
||||
constructor(opts: LambdaOptions) {
|
||||
const {
|
||||
@@ -72,6 +74,7 @@ export class Lambda {
|
||||
regions,
|
||||
supportsMultiPayloads,
|
||||
supportsWrapper,
|
||||
experimentalResponseStreaming,
|
||||
} = opts;
|
||||
if ('files' in opts) {
|
||||
assert(typeof opts.files === 'object', '"files" must be an object');
|
||||
@@ -132,6 +135,7 @@ export class Lambda {
|
||||
this.zipBuffer = 'zipBuffer' in opts ? opts.zipBuffer : undefined;
|
||||
this.supportsMultiPayloads = supportsMultiPayloads;
|
||||
this.supportsWrapper = supportsWrapper;
|
||||
this.experimentalResponseStreaming = experimentalResponseStreaming;
|
||||
}
|
||||
|
||||
async createZip(): Promise<Buffer> {
|
||||
|
||||
@@ -8,6 +8,8 @@ interface PrerenderOptions {
|
||||
group?: number;
|
||||
bypassToken?: string | null /* optional to be non-breaking change */;
|
||||
allowQuery?: string[];
|
||||
initialHeaders?: Record<string, string>;
|
||||
initialStatus?: number;
|
||||
}
|
||||
|
||||
export class Prerender {
|
||||
@@ -18,6 +20,8 @@ export class Prerender {
|
||||
public group?: number;
|
||||
public bypassToken: string | null;
|
||||
public allowQuery?: string[];
|
||||
public initialHeaders?: Record<string, string>;
|
||||
public initialStatus?: number;
|
||||
|
||||
constructor({
|
||||
expiration,
|
||||
@@ -26,6 +30,8 @@ export class Prerender {
|
||||
group,
|
||||
bypassToken,
|
||||
allowQuery,
|
||||
initialHeaders,
|
||||
initialStatus,
|
||||
}: PrerenderOptions) {
|
||||
this.type = 'Prerender';
|
||||
this.expiration = expiration;
|
||||
@@ -64,6 +70,30 @@ export class Prerender {
|
||||
}
|
||||
this.fallback = fallback;
|
||||
|
||||
if (initialHeaders !== undefined) {
|
||||
if (
|
||||
!initialHeaders ||
|
||||
typeof initialHeaders !== 'object' ||
|
||||
Object.entries(initialHeaders).some(
|
||||
([key, value]) => typeof key !== 'string' || typeof value !== 'string'
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`The \`initialHeaders\` argument for \`Prerender\` must be an object with string key/values`
|
||||
);
|
||||
}
|
||||
this.initialHeaders = initialHeaders;
|
||||
}
|
||||
|
||||
if (initialStatus !== undefined) {
|
||||
if (initialStatus <= 0 || !Number.isInteger(initialStatus)) {
|
||||
throw new Error(
|
||||
`The \`initialStatus\` argument for \`Prerender\` must be a natural number.`
|
||||
);
|
||||
}
|
||||
this.initialStatus = initialStatus;
|
||||
}
|
||||
|
||||
if (allowQuery !== undefined) {
|
||||
if (!Array.isArray(allowQuery)) {
|
||||
throw new Error(
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface Config {
|
||||
devCommand?: string;
|
||||
framework?: string | null;
|
||||
nodeVersion?: string;
|
||||
middleware?: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
@@ -341,6 +342,7 @@ export interface BuilderV2 {
|
||||
version: 2;
|
||||
build: BuildV2;
|
||||
prepareCache?: PrepareCache;
|
||||
shouldServe?: ShouldServe;
|
||||
}
|
||||
|
||||
export interface BuilderV3 {
|
||||
|
||||
2
packages/build-utils/test/fixtures/14-npm-6-legacy-peer-deps/.gitignore
vendored
Normal file
2
packages/build-utils/test/fixtures/14-npm-6-legacy-peer-deps/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
.vercel
|
||||
64
packages/build-utils/test/fixtures/14-npm-6-legacy-peer-deps/package-lock.json
generated
vendored
Normal file
64
packages/build-utils/test/fixtures/14-npm-6-legacy-peer-deps/package-lock.json
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"requires": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
|
||||
},
|
||||
"prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"react": {
|
||||
"version": "16.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.8.0.tgz",
|
||||
"integrity": "sha512-g+nikW2D48kqgWSPwNo0NH9tIGG3DsQFlrtrQ1kj6W77z5ahyIHG0w8kPpz4Sdj6gyLnz0lEd/xsjOoGge2MYQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.0"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"scheduler": {
|
||||
"version": "0.13.6",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
|
||||
"integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"swr": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/swr/-/swr-1.3.0.tgz",
|
||||
"integrity": "sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
11
packages/build-utils/test/fixtures/14-npm-6-legacy-peer-deps/package.json
vendored
Normal file
11
packages/build-utils/test/fixtures/14-npm-6-legacy-peer-deps/package.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "mkdir -p public && echo 'legacy peer deps' > public/index.txt"
|
||||
},
|
||||
"packageManager": "npm@6.14.17",
|
||||
"dependencies": {
|
||||
"swr": "1.3.0",
|
||||
"react": "16.8.0"
|
||||
}
|
||||
}
|
||||
3
packages/build-utils/test/fixtures/14-npm-6-legacy-peer-deps/probes.json
vendored
Normal file
3
packages/build-utils/test/fixtures/14-npm-6-legacy-peer-deps/probes.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"probes": [{ "path": "/", "mustContain": "legacy peer deps" }]
|
||||
}
|
||||
2
packages/build-utils/test/fixtures/15-npm-8-legacy-peer-deps/.gitignore
vendored
Normal file
2
packages/build-utils/test/fixtures/15-npm-8-legacy-peer-deps/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
.vercel
|
||||
143
packages/build-utils/test/fixtures/15-npm-8-legacy-peer-deps/package-lock.json
generated
vendored
Normal file
143
packages/build-utils/test/fixtures/15-npm-8-legacy-peer-deps/package-lock.json
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"name": "15-npm-8-legacy-peer-deps",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"react": "16.8.0",
|
||||
"swr": "1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "16.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.8.0.tgz",
|
||||
"integrity": "sha512-g+nikW2D48kqgWSPwNo0NH9tIGG3DsQFlrtrQ1kj6W77z5ahyIHG0w8kPpz4Sdj6gyLnz0lEd/xsjOoGge2MYQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.13.6",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
|
||||
"integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/swr": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/swr/-/swr-1.3.0.tgz",
|
||||
"integrity": "sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.11.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"requires": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
|
||||
},
|
||||
"prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"react": {
|
||||
"version": "16.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.8.0.tgz",
|
||||
"integrity": "sha512-g+nikW2D48kqgWSPwNo0NH9tIGG3DsQFlrtrQ1kj6W77z5ahyIHG0w8kPpz4Sdj6gyLnz0lEd/xsjOoGge2MYQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.0"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"scheduler": {
|
||||
"version": "0.13.6",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
|
||||
"integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"swr": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/swr/-/swr-1.3.0.tgz",
|
||||
"integrity": "sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
11
packages/build-utils/test/fixtures/15-npm-8-legacy-peer-deps/package.json
vendored
Normal file
11
packages/build-utils/test/fixtures/15-npm-8-legacy-peer-deps/package.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "mkdir -p public && echo 'legacy peer deps' > public/index.txt"
|
||||
},
|
||||
"packageManager": "npm@8.6.0",
|
||||
"dependencies": {
|
||||
"swr": "1.3.0",
|
||||
"react": "16.8.0"
|
||||
}
|
||||
}
|
||||
3
packages/build-utils/test/fixtures/15-npm-8-legacy-peer-deps/probes.json
vendored
Normal file
3
packages/build-utils/test/fixtures/15-npm-8-legacy-peer-deps/probes.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"probes": [{ "path": "/", "mustContain": "legacy peer deps" }]
|
||||
}
|
||||
1
packages/build-utils/test/fixtures/20-npm-7/package-lock.json
generated
vendored
1
packages/build-utils/test/fixtures/20-npm-7/package-lock.json
generated
vendored
@@ -5,6 +5,7 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "20-npm-7",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ const skipFixtures: string[] = [
|
||||
'08-zero-config-middleman',
|
||||
'21-npm-workspaces',
|
||||
'23-pnpm-workspaces',
|
||||
'41-nx-monorepo',
|
||||
'42-npm-workspace-with-nx',
|
||||
];
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
|
||||
117
packages/build-utils/test/unit.clone-env.test.ts
vendored
Normal file
117
packages/build-utils/test/unit.clone-env.test.ts
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
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',
|
||||
});
|
||||
});
|
||||
|
||||
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: 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',
|
||||
});
|
||||
});
|
||||
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'));
|
||||
});
|
||||
});
|
||||
197
packages/build-utils/test/unit.run-npm-install.test.ts
vendored
Normal file
197
packages/build-utils/test/unit.run-npm-install.test.ts
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
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 = 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 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 include peer dependencies when VERCEL_NPM_LEGACY_PEER_DEPS=1 on node14 and npm7+', 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',
|
||||
'--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 and npm6', async () => {
|
||||
const fixture = path.join(__dirname, 'fixtures', '14-npm-6-legacy-peer-deps');
|
||||
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),
|
||||
});
|
||||
});
|
||||
158
packages/build-utils/test/unit.test.ts
vendored
158
packages/build-utils/test/unit.test.ts
vendored
@@ -1,7 +1,6 @@
|
||||
import ms from 'ms';
|
||||
import path from 'path';
|
||||
import fs, { readlink } from 'fs-extra';
|
||||
import retry from 'async-retry';
|
||||
import { strict as assert, strictEqual } from 'assert';
|
||||
import { createZip } from '../src/lambda';
|
||||
import { getSupportedNodeVersion } from '../src/fs/node-version';
|
||||
@@ -16,7 +15,6 @@ import {
|
||||
runPackageJsonScript,
|
||||
scanParentDirs,
|
||||
FileBlob,
|
||||
Meta,
|
||||
} from '../src';
|
||||
|
||||
jest.setTimeout(10 * 1000);
|
||||
@@ -218,10 +216,6 @@ it('should download symlinks even with incorrect file', async () => {
|
||||
});
|
||||
|
||||
it('should only match supported node versions, otherwise throw an error', async () => {
|
||||
expect(await getSupportedNodeVersion('12.x', false)).toHaveProperty(
|
||||
'major',
|
||||
12
|
||||
);
|
||||
expect(await getSupportedNodeVersion('14.x', false)).toHaveProperty(
|
||||
'major',
|
||||
14
|
||||
@@ -242,10 +236,6 @@ it('should only match supported node versions, otherwise throw an error', async
|
||||
await expectBuilderError(getSupportedNodeVersion('foo', true), autoMessage);
|
||||
await expectBuilderError(getSupportedNodeVersion('=> 10', true), autoMessage);
|
||||
|
||||
expect(await getSupportedNodeVersion('12.x', true)).toHaveProperty(
|
||||
'major',
|
||||
12
|
||||
);
|
||||
expect(await getSupportedNodeVersion('14.x', true)).toHaveProperty(
|
||||
'major',
|
||||
14
|
||||
@@ -275,24 +265,41 @@ it('should only match supported node versions, otherwise throw an error', async
|
||||
|
||||
it('should match all semver ranges', async () => {
|
||||
// See https://docs.npmjs.com/files/package.json#engines
|
||||
expect(await getSupportedNodeVersion('12.0.0')).toHaveProperty('major', 12);
|
||||
expect(await getSupportedNodeVersion('12.x')).toHaveProperty('major', 12);
|
||||
expect(await getSupportedNodeVersion('14.0.0')).toHaveProperty('major', 14);
|
||||
expect(await getSupportedNodeVersion('14.x')).toHaveProperty('major', 14);
|
||||
expect(await getSupportedNodeVersion('>=10')).toHaveProperty('major', 16);
|
||||
expect(await getSupportedNodeVersion('>=10.3.0')).toHaveProperty('major', 16);
|
||||
expect(await getSupportedNodeVersion('11.5.0 - 12.5.0')).toHaveProperty(
|
||||
expect(await getSupportedNodeVersion('16.5.0 - 16.9.0')).toHaveProperty(
|
||||
'major',
|
||||
12
|
||||
16
|
||||
);
|
||||
expect(await getSupportedNodeVersion('>=9.5.0 <=12.5.0')).toHaveProperty(
|
||||
'major',
|
||||
12
|
||||
);
|
||||
expect(await getSupportedNodeVersion('~12.5.0')).toHaveProperty('major', 12);
|
||||
expect(await getSupportedNodeVersion('^12.5.0')).toHaveProperty('major', 12);
|
||||
expect(await getSupportedNodeVersion('12.5.0 - 14.5.0')).toHaveProperty(
|
||||
expect(await getSupportedNodeVersion('>=9.5.0 <=14.5.0')).toHaveProperty(
|
||||
'major',
|
||||
14
|
||||
);
|
||||
expect(await getSupportedNodeVersion('~14.5.0')).toHaveProperty('major', 14);
|
||||
expect(await getSupportedNodeVersion('^14.5.0')).toHaveProperty('major', 14);
|
||||
expect(await getSupportedNodeVersion('14.5.0 - 14.20.0')).toHaveProperty(
|
||||
'major',
|
||||
14
|
||||
);
|
||||
});
|
||||
|
||||
it('should only allow nodejs18.x when env var is set', async () => {
|
||||
try {
|
||||
expect(getLatestNodeVersion()).toHaveProperty('major', 16);
|
||||
expect(getSupportedNodeVersion('18.x')).rejects.toThrow();
|
||||
|
||||
process.env.VERCEL_ALLOW_NODEJS18 = '1';
|
||||
|
||||
expect(getLatestNodeVersion()).toHaveProperty('major', 18);
|
||||
expect(await getSupportedNodeVersion('18.x')).toHaveProperty('major', 18);
|
||||
expect(await getSupportedNodeVersion('18')).toHaveProperty('major', 18);
|
||||
expect(await getSupportedNodeVersion('18.1.0')).toHaveProperty('major', 18);
|
||||
expect(await getSupportedNodeVersion('>=16')).toHaveProperty('major', 18);
|
||||
} finally {
|
||||
delete process.env.VERCEL_ALLOW_NODEJS18;
|
||||
}
|
||||
});
|
||||
|
||||
it('should ignore node version in vercel dev getNodeVersion()', async () => {
|
||||
@@ -436,13 +443,45 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
|
||||
expect(warningMessages).toStrictEqual([
|
||||
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
||||
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-01 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-01 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-03 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-03 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
||||
]);
|
||||
|
||||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
it('should support initialHeaders and initialStatus correctly', async () => {
|
||||
const { Prerender } = require('@vercel/build-utils/dist/prerender.js');
|
||||
new Prerender({
|
||||
expiration: 1,
|
||||
fallback: null,
|
||||
group: 1,
|
||||
bypassToken: 'some-long-bypass-token-to-make-it-work',
|
||||
initialHeaders: {
|
||||
'content-type': 'application/json',
|
||||
'x-initial': 'true',
|
||||
},
|
||||
initialStatus: 308,
|
||||
});
|
||||
new Prerender({
|
||||
expiration: 1,
|
||||
fallback: null,
|
||||
group: 1,
|
||||
bypassToken: 'some-long-bypass-token-to-make-it-work',
|
||||
initialStatus: 308,
|
||||
});
|
||||
new Prerender({
|
||||
expiration: 1,
|
||||
fallback: null,
|
||||
group: 1,
|
||||
bypassToken: 'some-long-bypass-token-to-make-it-work',
|
||||
initialHeaders: {
|
||||
'content-type': 'application/json',
|
||||
'x-initial': 'true',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should support require by path for legacy builders', () => {
|
||||
const index = require('@vercel/build-utils');
|
||||
|
||||
@@ -501,6 +540,7 @@ it('should return lockfileVersion 2 with npm7', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('npm');
|
||||
expect(result.lockfileVersion).toEqual(2);
|
||||
expect(result.lockfilePath).toEqual(path.join(fixture, 'package-lock.json'));
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
@@ -509,6 +549,7 @@ it('should not return lockfileVersion with yarn', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('yarn');
|
||||
expect(result.lockfileVersion).toEqual(undefined);
|
||||
expect(result.lockfilePath).toEqual(path.join(fixture, 'yarn.lock'));
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
@@ -517,6 +558,7 @@ it('should return lockfileVersion 1 with older versions of npm', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('npm');
|
||||
expect(result.lockfileVersion).toEqual(1);
|
||||
expect(result.lockfilePath).toEqual(path.join(fixture, 'package-lock.json'));
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
@@ -525,6 +567,9 @@ it('should detect npm Workspaces', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('npm');
|
||||
expect(result.lockfileVersion).toEqual(2);
|
||||
expect(result.lockfilePath).toEqual(
|
||||
path.join(fixture, '..', 'package-lock.json')
|
||||
);
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
@@ -533,6 +578,7 @@ it('should detect pnpm without workspace', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('pnpm');
|
||||
expect(result.lockfileVersion).toEqual(5.3);
|
||||
expect(result.lockfilePath).toEqual(path.join(fixture, 'pnpm-lock.yaml'));
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
@@ -541,6 +587,9 @@ it('should detect pnpm with workspaces', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('pnpm');
|
||||
expect(result.lockfileVersion).toEqual(5.3);
|
||||
expect(result.lockfilePath).toEqual(
|
||||
path.join(fixture, '..', 'pnpm-lock.yaml')
|
||||
);
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
@@ -552,6 +601,7 @@ it('should detect package.json in nested backend', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('yarn');
|
||||
expect(result.lockfileVersion).toEqual(undefined);
|
||||
// There is no lockfile but this test will pick up vercel/vercel/yarn.lock
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
@@ -563,56 +613,20 @@ it('should detect package.json in nested frontend', async () => {
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('yarn');
|
||||
expect(result.lockfileVersion).toEqual(undefined);
|
||||
// There is no lockfile but this test will pick up vercel/vercel/yarn.lock
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
it('should only invoke `runNpmInstall()` once per `package.json` file (serial)', async () => {
|
||||
const meta: Meta = {};
|
||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
||||
const apiDir = path.join(fixture, 'api');
|
||||
const retryOpts = { maxRetryTime: 1000 };
|
||||
let run1, run2, run3;
|
||||
await retry(async () => {
|
||||
run1 = await runNpmInstall(apiDir, [], undefined, meta);
|
||||
expect(run1).toEqual(true);
|
||||
expect(
|
||||
(meta.runNpmInstallSet as Set<string>).has(
|
||||
path.join(fixture, 'package.json')
|
||||
)
|
||||
).toEqual(true);
|
||||
}, retryOpts);
|
||||
await retry(async () => {
|
||||
run2 = await runNpmInstall(apiDir, [], undefined, meta);
|
||||
expect(run2).toEqual(false);
|
||||
}, retryOpts);
|
||||
await retry(async () => {
|
||||
run3 = await runNpmInstall(fixture, [], undefined, meta);
|
||||
expect(run3).toEqual(false);
|
||||
}, retryOpts);
|
||||
});
|
||||
|
||||
it('should only invoke `runNpmInstall()` once per `package.json` file (parallel)', async () => {
|
||||
const meta: Meta = {};
|
||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
||||
const apiDir = path.join(fixture, 'api');
|
||||
let results: [boolean, boolean, boolean] | undefined;
|
||||
await retry(
|
||||
async () => {
|
||||
results = await Promise.all([
|
||||
runNpmInstall(apiDir, [], undefined, meta),
|
||||
runNpmInstall(apiDir, [], undefined, meta),
|
||||
runNpmInstall(fixture, [], undefined, meta),
|
||||
]);
|
||||
},
|
||||
{ maxRetryTime: 3000 }
|
||||
);
|
||||
const [run1, run2, run3] = results || [];
|
||||
expect(run1).toEqual(true);
|
||||
expect(run2).toEqual(false);
|
||||
expect(run3).toEqual(false);
|
||||
expect(
|
||||
(meta.runNpmInstallSet as Set<string>).has(
|
||||
path.join(fixture, 'package.json')
|
||||
)
|
||||
).toEqual(true);
|
||||
it('should retry npm install when peer deps invalid and npm@8 on node@16', async () => {
|
||||
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
||||
if (nodeMajor !== 16) {
|
||||
console.log(`Skipping test on node@${nodeMajor}`);
|
||||
return;
|
||||
}
|
||||
const fixture = path.join(__dirname, 'fixtures', '15-npm-8-legacy-peer-deps');
|
||||
const nodeVersion = { major: nodeMajor } as any;
|
||||
await runNpmInstall(fixture, [], {}, {}, nodeVersion);
|
||||
expect(warningMessages).toStrictEqual([
|
||||
'Warning: Retrying "Install Command" with `--legacy-peer-deps` which may accept a potentially broken dependency and slow install time.',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -10,11 +10,9 @@
|
||||
|
||||
## Usage
|
||||
|
||||
Vercel is a platform for **static sites and frontend frameworks**, built to integrate with your headless content, commerce, or database.
|
||||
Vercel is the platform for frontend developers, providing the speed and reliability innovators need to create at the moment of inspiration.
|
||||
|
||||
We provide a **frictionless developer experience** to take care of the hard things: deploy instantly, scale automatically, and serve personalized content around the globe.
|
||||
|
||||
We make it easy for frontend teams to **develop, preview, and ship** delightful user experiences, where performance is the default.
|
||||
We enable teams to iterate quickly and develop, preview, and ship delightful user experiences. Vercel has zero-configuration support for 35+ frontend frameworks and integrates with your headless content, commerce, or database of choice.
|
||||
|
||||
To install the latest version of Vercel CLI, run this command:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "28.1.4",
|
||||
"version": "28.4.13",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -41,16 +41,16 @@
|
||||
"node": ">= 14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "5.3.2",
|
||||
"@vercel/go": "2.2.2",
|
||||
"@vercel/hydrogen": "0.0.15",
|
||||
"@vercel/next": "3.1.21",
|
||||
"@vercel/node": "2.5.10",
|
||||
"@vercel/python": "3.1.11",
|
||||
"@vercel/redwood": "1.0.19",
|
||||
"@vercel/remix": "1.0.20",
|
||||
"@vercel/ruby": "1.3.28",
|
||||
"@vercel/static-build": "1.0.19",
|
||||
"@vercel/build-utils": "5.5.6",
|
||||
"@vercel/go": "2.2.14",
|
||||
"@vercel/hydrogen": "0.0.27",
|
||||
"@vercel/next": "3.2.7",
|
||||
"@vercel/node": "2.6.0",
|
||||
"@vercel/python": "3.1.23",
|
||||
"@vercel/redwood": "1.0.32",
|
||||
"@vercel/remix": "1.0.33",
|
||||
"@vercel/ruby": "1.3.40",
|
||||
"@vercel/static-build": "1.0.33",
|
||||
"update-notifier": "5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -85,7 +85,6 @@
|
||||
"@types/node-fetch": "2.5.10",
|
||||
"@types/npm-package-arg": "6.1.0",
|
||||
"@types/pluralize": "0.0.29",
|
||||
"@types/progress": "2.0.3",
|
||||
"@types/psl": "1.1.0",
|
||||
"@types/semver": "6.0.1",
|
||||
"@types/tar-fs": "1.16.1",
|
||||
@@ -96,9 +95,10 @@
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@types/yauzl-promise": "2.1.0",
|
||||
"@vercel/client": "12.2.1",
|
||||
"@vercel/frameworks": "1.1.3",
|
||||
"@vercel/fs-detectors": "2.0.5",
|
||||
"@vercel/client": "12.2.14",
|
||||
"@vercel/error-utils": "1.0.1",
|
||||
"@vercel/frameworks": "1.1.9",
|
||||
"@vercel/fs-detectors": "3.4.6",
|
||||
"@vercel/fun": "1.0.4",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
@@ -153,7 +153,6 @@
|
||||
"ora": "3.4.0",
|
||||
"pcre-to-regexp": "1.0.0",
|
||||
"pluralize": "7.0.0",
|
||||
"progress": "2.0.3",
|
||||
"promisepipe": "3.0.0",
|
||||
"psl": "1.1.31",
|
||||
"qr-image": "3.2.0",
|
||||
|
||||
@@ -4,7 +4,7 @@ const { statSync } = require('fs');
|
||||
const pkg = require('../package');
|
||||
|
||||
function error(command) {
|
||||
console.error('> Error!', command);
|
||||
console.error('> Error:', command);
|
||||
}
|
||||
|
||||
function debug(str) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import open from 'open';
|
||||
import boxen from 'boxen';
|
||||
import execa from 'execa';
|
||||
import plural from 'pluralize';
|
||||
import inquirer from 'inquirer';
|
||||
import { resolve } from 'path';
|
||||
import chalk, { Chalk } from 'chalk';
|
||||
import { URLSearchParams, parse } from 'url';
|
||||
@@ -150,7 +149,9 @@ export default async function main(client: Client): Promise<number> {
|
||||
|
||||
if (badDeployment) {
|
||||
if (badDeployment instanceof Error) {
|
||||
badDeployment.message += ` "${bad}"`;
|
||||
badDeployment.message += ` when requesting bad deployment "${normalizeURL(
|
||||
bad
|
||||
)}"`;
|
||||
output.prettyError(badDeployment);
|
||||
return 1;
|
||||
}
|
||||
@@ -165,7 +166,9 @@ export default async function main(client: Client): Promise<number> {
|
||||
|
||||
if (goodDeployment) {
|
||||
if (goodDeployment instanceof Error) {
|
||||
goodDeployment.message += ` "${good}"`;
|
||||
goodDeployment.message += ` when requesting good deployment "${normalizeURL(
|
||||
good
|
||||
)}"`;
|
||||
output.prettyError(goodDeployment);
|
||||
return 1;
|
||||
}
|
||||
@@ -226,7 +229,8 @@ export default async function main(client: Client): Promise<number> {
|
||||
// If we have the "good" deployment in this chunk, then we're done
|
||||
for (let i = 0; i < newDeployments.length; i++) {
|
||||
if (newDeployments[i].url === good) {
|
||||
newDeployments = newDeployments.slice(0, i + 1);
|
||||
// grab all deployments up until the good one
|
||||
newDeployments = newDeployments.slice(0, i);
|
||||
next = undefined;
|
||||
break;
|
||||
}
|
||||
@@ -316,7 +320,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
if (openEnabled) {
|
||||
await open(testUrl);
|
||||
}
|
||||
const answer = await inquirer.prompt({
|
||||
const answer = await client.prompt({
|
||||
type: 'expand',
|
||||
name: 'action',
|
||||
message: 'Select an action:',
|
||||
|
||||
@@ -3,6 +3,7 @@ import chalk from 'chalk';
|
||||
import dotenv from 'dotenv';
|
||||
import { join, normalize, relative, resolve } from 'path';
|
||||
import {
|
||||
getDiscontinuedNodeVersions,
|
||||
normalizePath,
|
||||
Files,
|
||||
FileFsRef,
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
MergeRoutesProps,
|
||||
Route,
|
||||
} from '@vercel/routing-utils';
|
||||
import { fileNameSymbol } from '@vercel/client';
|
||||
import type { VercelConfig } from '@vercel/client';
|
||||
|
||||
import pull from './pull';
|
||||
@@ -37,6 +39,7 @@ import cliPkg from '../util/pkg';
|
||||
import readJSONFile from '../util/read-json-file';
|
||||
import { CantParseJSONFile } from '../util/errors-ts';
|
||||
import {
|
||||
pickOverrides,
|
||||
ProjectLinkAndSettings,
|
||||
readProjectSettings,
|
||||
} from '../util/projects/project-settings';
|
||||
@@ -53,6 +56,7 @@ import { importBuilders } from '../util/build/import-builders';
|
||||
import { initCorepack, cleanupCorepack } from '../util/build/corepack';
|
||||
import { sortBuilders } from '../util/build/sort-builders';
|
||||
import { toEnumerableError } from '../util/error';
|
||||
import { validateConfig } from '../util/validate-config';
|
||||
|
||||
type BuildResult = BuildResultV2 | BuildResultV3;
|
||||
|
||||
@@ -231,7 +235,8 @@ export default async function main(client: Client): Promise<number> {
|
||||
process.env.VERCEL = '1';
|
||||
process.env.NOW_BUILDER = '1';
|
||||
|
||||
return await doBuild(client, project, buildsJson, cwd, outputDir);
|
||||
await doBuild(client, project, buildsJson, cwd, outputDir);
|
||||
return 0;
|
||||
} catch (err: any) {
|
||||
output.prettyError(err);
|
||||
|
||||
@@ -264,31 +269,49 @@ async function doBuild(
|
||||
buildsJson: BuildsManifest,
|
||||
cwd: string,
|
||||
outputDir: string
|
||||
): Promise<number> {
|
||||
): Promise<void> {
|
||||
const { output } = client;
|
||||
const workPath = join(cwd, project.settings.rootDirectory || '.');
|
||||
|
||||
// Load `package.json` and `vercel.json` files
|
||||
const [pkg, vercelConfig] = await Promise.all([
|
||||
const [pkg, vercelConfig, nowConfig] = await Promise.all([
|
||||
readJSONFile<PackageJson>(join(workPath, 'package.json')),
|
||||
readJSONFile<VercelConfig>(join(workPath, 'vercel.json')).then(
|
||||
config => config || readJSONFile<VercelConfig>(join(workPath, 'now.json'))
|
||||
),
|
||||
readJSONFile<VercelConfig>(join(workPath, 'vercel.json')),
|
||||
readJSONFile<VercelConfig>(join(workPath, 'now.json')),
|
||||
]);
|
||||
|
||||
if (pkg instanceof CantParseJSONFile) throw pkg;
|
||||
if (vercelConfig instanceof CantParseJSONFile) throw vercelConfig;
|
||||
if (nowConfig instanceof CantParseJSONFile) throw nowConfig;
|
||||
|
||||
if (vercelConfig) {
|
||||
vercelConfig[fileNameSymbol] = 'vercel.json';
|
||||
} else if (nowConfig) {
|
||||
nowConfig[fileNameSymbol] = 'now.json';
|
||||
}
|
||||
|
||||
const localConfig = vercelConfig || nowConfig || {};
|
||||
const validateError = validateConfig(localConfig);
|
||||
|
||||
if (validateError) {
|
||||
throw validateError;
|
||||
}
|
||||
|
||||
const projectSettings = {
|
||||
...project.settings,
|
||||
...pickOverrides(localConfig),
|
||||
};
|
||||
|
||||
// Get a list of source files
|
||||
const files = (await getFiles(workPath, client)).map(f =>
|
||||
normalizePath(relative(workPath, f))
|
||||
);
|
||||
|
||||
const routesResult = getTransformedRoutes(vercelConfig || {});
|
||||
const routesResult = getTransformedRoutes(localConfig);
|
||||
if (routesResult.error) {
|
||||
throw routesResult.error;
|
||||
}
|
||||
|
||||
if (vercelConfig?.builds && vercelConfig.functions) {
|
||||
if (localConfig.builds && localConfig.functions) {
|
||||
throw new NowBuildError({
|
||||
code: 'bad_request',
|
||||
message:
|
||||
@@ -297,7 +320,7 @@ async function doBuild(
|
||||
});
|
||||
}
|
||||
|
||||
let builds = vercelConfig?.builds || [];
|
||||
let builds = localConfig.builds || [];
|
||||
let zeroConfigRoutes: Route[] = [];
|
||||
let isZeroConfig = false;
|
||||
|
||||
@@ -312,8 +335,8 @@ async function doBuild(
|
||||
|
||||
// Detect the Vercel Builders that will need to be invoked
|
||||
const detectedBuilders = await detectBuilders(files, pkg, {
|
||||
...vercelConfig,
|
||||
projectSettings: project.settings,
|
||||
...localConfig,
|
||||
projectSettings,
|
||||
ignoreBuildScript: true,
|
||||
featHandleMiss: true,
|
||||
});
|
||||
@@ -389,13 +412,10 @@ async function doBuild(
|
||||
})
|
||||
);
|
||||
buildsJson.builds = Array.from(buildsJsonBuilds.values());
|
||||
const buildsJsonPath = join(outputDir, 'builds.json');
|
||||
const writeBuildsJsonPromise = fs.writeJSON(buildsJsonPath, buildsJson, {
|
||||
await fs.writeJSON(join(outputDir, 'builds.json'), buildsJson, {
|
||||
spaces: 2,
|
||||
});
|
||||
|
||||
ops.push(writeBuildsJsonPromise);
|
||||
|
||||
// The `meta` config property is re-used for each Builder
|
||||
// invocation so that Builders can share state between
|
||||
// subsequent entrypoint builds.
|
||||
@@ -425,14 +445,14 @@ async function doBuild(
|
||||
|
||||
const buildConfig: Config = isZeroConfig
|
||||
? {
|
||||
outputDirectory: project.settings.outputDirectory ?? undefined,
|
||||
outputDirectory: projectSettings.outputDirectory ?? undefined,
|
||||
...build.config,
|
||||
projectSettings: project.settings,
|
||||
installCommand: project.settings.installCommand ?? undefined,
|
||||
devCommand: project.settings.devCommand ?? undefined,
|
||||
buildCommand: project.settings.buildCommand ?? undefined,
|
||||
framework: project.settings.framework,
|
||||
nodeVersion: project.settings.nodeVersion,
|
||||
projectSettings,
|
||||
installCommand: projectSettings.installCommand ?? undefined,
|
||||
devCommand: projectSettings.devCommand ?? undefined,
|
||||
buildCommand: projectSettings.buildCommand ?? undefined,
|
||||
framework: projectSettings.framework,
|
||||
nodeVersion: projectSettings.nodeVersion,
|
||||
}
|
||||
: build.config || {};
|
||||
const buildOptions: BuildOptions = {
|
||||
@@ -448,6 +468,25 @@ async function doBuild(
|
||||
);
|
||||
const buildResult = await builder.build(buildOptions);
|
||||
|
||||
if (
|
||||
buildResult &&
|
||||
'output' in buildResult &&
|
||||
'runtime' in buildResult.output &&
|
||||
'type' in buildResult.output &&
|
||||
buildResult.output.type === 'Lambda'
|
||||
) {
|
||||
const lambdaRuntime = buildResult.output.runtime;
|
||||
if (
|
||||
getDiscontinuedNodeVersions().some(o => o.runtime === lambdaRuntime)
|
||||
) {
|
||||
throw new NowBuildError({
|
||||
code: 'NODEJS_DISCONTINUED_VERSION',
|
||||
message: `The Runtime "${build.use}" is using "${lambdaRuntime}", which is discontinued. Please upgrade your Runtime to a more recent version or consult the author for more details.`,
|
||||
link: 'https://github.com/vercel/vercel/blob/main/DEVELOPING_A_RUNTIME.md#lambdaruntime',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Store the build result to generate the final `config.json` after
|
||||
// all builds have completed
|
||||
buildResults.set(build, buildResult);
|
||||
@@ -460,7 +499,7 @@ async function doBuild(
|
||||
build,
|
||||
builder,
|
||||
builderPkg,
|
||||
vercelConfig
|
||||
localConfig
|
||||
).then(
|
||||
override => {
|
||||
if (override) overrides.push(override);
|
||||
@@ -469,26 +508,11 @@ async function doBuild(
|
||||
)
|
||||
);
|
||||
} catch (err: any) {
|
||||
output.prettyError(err);
|
||||
|
||||
const writeConfigJsonPromise = fs.writeJSON(
|
||||
join(outputDir, 'config.json'),
|
||||
{ version: 3 },
|
||||
{ spaces: 2 }
|
||||
);
|
||||
|
||||
await Promise.all([writeBuildsJsonPromise, writeConfigJsonPromise]);
|
||||
|
||||
const buildJsonBuild = buildsJsonBuilds.get(build);
|
||||
if (buildJsonBuild) {
|
||||
buildJsonBuild.error = toEnumerableError(err);
|
||||
|
||||
await fs.writeJSON(buildsJsonPath, buildsJson, {
|
||||
spaces: 2,
|
||||
});
|
||||
}
|
||||
|
||||
return 1;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,7 +573,7 @@ async function doBuild(
|
||||
builds: builderRoutes,
|
||||
});
|
||||
|
||||
const mergedImages = mergeImages(buildResults.values());
|
||||
const mergedImages = mergeImages(localConfig.images, buildResults.values());
|
||||
const mergedWildcard = mergeWildcard(buildResults.values());
|
||||
const mergedOverrides: Record<string, PathOverride> =
|
||||
overrides.length > 0 ? Object.assign({}, ...overrides) : undefined;
|
||||
@@ -575,8 +599,6 @@ async function doBuild(
|
||||
emoji('success')
|
||||
)}\n`
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function expandBuild(files: string[], build: Builder): Builder[] {
|
||||
@@ -618,9 +640,9 @@ function expandBuild(files: string[], build: Builder): Builder[] {
|
||||
}
|
||||
|
||||
function mergeImages(
|
||||
images: BuildResultV2Typical['images'],
|
||||
buildResults: Iterable<BuildResult>
|
||||
): BuildResultV2Typical['images'] {
|
||||
let images: BuildResultV2Typical['images'] = undefined;
|
||||
for (const result of buildResults) {
|
||||
if ('images' in result && result.images) {
|
||||
images = Object.assign({}, images, result.images);
|
||||
|
||||
@@ -47,9 +47,7 @@ import {
|
||||
import { SchemaValidationFailed } from '../../util/errors';
|
||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import editProjectSettings, {
|
||||
PartialProjectSettings,
|
||||
} from '../../util/input/edit-project-settings';
|
||||
import editProjectSettings from '../../util/input/edit-project-settings';
|
||||
import {
|
||||
getLinkedProject,
|
||||
linkFolderToProject,
|
||||
@@ -72,7 +70,8 @@ import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
|
||||
import { createGitMeta } from '../../util/create-git-meta';
|
||||
import { isValidArchive } from '../../util/deploy/validate-archive-format';
|
||||
import { parseEnv } from '../../util/parse-env';
|
||||
import { errorToString, isErrnoException, isError } from '../../util/is-error';
|
||||
import { errorToString, isErrnoException, isError } from '@vercel/error-utils';
|
||||
import { pickOverrides } from '../../util/projects/project-settings';
|
||||
|
||||
export default async (client: Client): Promise<number> => {
|
||||
const { output } = client;
|
||||
@@ -509,14 +508,7 @@ export default async (client: Client): Promise<number> => {
|
||||
let deployStamp = stamp();
|
||||
let deployment = null;
|
||||
|
||||
const localConfigurationOverrides: PartialProjectSettings = {
|
||||
buildCommand: localConfig?.buildCommand,
|
||||
devCommand: localConfig?.devCommand,
|
||||
framework: localConfig?.framework,
|
||||
commandForIgnoringBuildStep: localConfig?.ignoreCommand,
|
||||
installCommand: localConfig?.installCommand,
|
||||
outputDirectory: localConfig?.outputDirectory,
|
||||
};
|
||||
const localConfigurationOverrides = pickOverrides(localConfig);
|
||||
|
||||
try {
|
||||
const createArgs: any = {
|
||||
@@ -530,7 +522,12 @@ export default async (client: Client): Promise<number> => {
|
||||
quiet,
|
||||
wantsPublic: argv['--public'] || localConfig.public,
|
||||
type: null,
|
||||
nowConfig: localConfig,
|
||||
nowConfig: {
|
||||
...localConfig,
|
||||
// `images` is allowed in "vercel.json" and processed
|
||||
// by `vc build`, but don't send it to the API endpoint
|
||||
images: undefined,
|
||||
},
|
||||
regions,
|
||||
meta,
|
||||
gitMetadata,
|
||||
|
||||
@@ -2,18 +2,15 @@ import { resolve, join } from 'path';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import DevServer from '../../util/dev/server';
|
||||
import parseListen from '../../util/dev/parse-listen';
|
||||
import { ProjectEnvVariable } from '../../types';
|
||||
import { parseListen } from '../../util/dev/parse-listen';
|
||||
import Client from '../../util/client';
|
||||
import { getLinkedProject } from '../../util/projects/link';
|
||||
import { getFrameworks } from '../../util/get-frameworks';
|
||||
import { ProjectSettings } from '../../types';
|
||||
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
||||
import setupAndLink from '../../util/link/setup-and-link';
|
||||
import getSystemEnvValues from '../../util/env/get-system-env-values';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import param from '../../util/output/param';
|
||||
import { OUTPUT_DIR } from '../../util/build/write-build-result';
|
||||
import { pullEnvRecords } from '../../util/env/get-env-records';
|
||||
|
||||
type Options = {
|
||||
'--listen': string;
|
||||
@@ -31,10 +28,7 @@ export default async function dev(
|
||||
const listen = parseListen(opts['--listen'] || '3000');
|
||||
|
||||
// retrieve dev command
|
||||
let [link, frameworks] = await Promise.all([
|
||||
getLinkedProject(client, cwd),
|
||||
getFrameworks(client),
|
||||
]);
|
||||
let link = await getLinkedProject(client, cwd);
|
||||
|
||||
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
|
||||
link = await setupAndLink(client, cwd, {
|
||||
@@ -60,51 +54,33 @@ export default async function dev(
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
let devCommand: string | undefined;
|
||||
let projectSettings: ProjectSettings | undefined;
|
||||
let projectEnvs: ProjectEnvVariable[] = [];
|
||||
let systemEnvValues: string[] = [];
|
||||
let envValues: Record<string, string> = {};
|
||||
if (link.status === 'linked') {
|
||||
const { project, org } = link;
|
||||
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
|
||||
|
||||
projectSettings = project;
|
||||
|
||||
if (project.devCommand) {
|
||||
devCommand = project.devCommand;
|
||||
} else if (project.framework) {
|
||||
const framework = frameworks.find(f => f.slug === project.framework);
|
||||
|
||||
if (framework) {
|
||||
const defaults = framework.settings.devCommand.value;
|
||||
if (defaults) {
|
||||
devCommand = defaults;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (project.rootDirectory) {
|
||||
cwd = join(cwd, project.rootDirectory);
|
||||
}
|
||||
|
||||
[{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([
|
||||
getDecryptedEnvRecords(output, client, project.id, 'vercel-cli:dev'),
|
||||
project.autoExposeSystemEnvs
|
||||
? getSystemEnvValues(output, client, project.id)
|
||||
: { systemEnvValues: [] },
|
||||
]);
|
||||
envValues = (
|
||||
await pullEnvRecords(output, client, project.id, 'vercel-cli:dev')
|
||||
).env;
|
||||
}
|
||||
|
||||
// This is just for tests - can be removed once project settings
|
||||
// are respected locally in `.vercel/project.json`
|
||||
if (process.env.VERCEL_DEV_COMMAND) {
|
||||
devCommand = process.env.VERCEL_DEV_COMMAND;
|
||||
}
|
||||
const devServer = new DevServer(cwd, {
|
||||
output,
|
||||
projectSettings,
|
||||
envValues,
|
||||
});
|
||||
|
||||
// If there is no Development Command, we must delete the
|
||||
// v3 Build Output because it will incorrectly be detected by
|
||||
// @vercel/static-build in BuildOutputV3.getBuildOutputDirectory()
|
||||
if (!devCommand) {
|
||||
if (!devServer.devCommand) {
|
||||
const outputDir = join(cwd, OUTPUT_DIR);
|
||||
if (await fs.pathExists(outputDir)) {
|
||||
output.log(`Removing ${OUTPUT_DIR}`);
|
||||
@@ -112,13 +88,5 @@ export default async function dev(
|
||||
}
|
||||
}
|
||||
|
||||
const devServer = new DevServer(cwd, {
|
||||
output,
|
||||
devCommand,
|
||||
projectSettings,
|
||||
projectEnvs,
|
||||
systemEnvValues,
|
||||
});
|
||||
|
||||
await devServer.start(...listen);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import readConfig from '../../util/config/read-config';
|
||||
import readJSONFile from '../../util/read-json-file';
|
||||
import { getPkgName, getCommandName } from '../../util/pkg-name';
|
||||
import { CantParseJSONFile } from '../../util/errors-ts';
|
||||
import { isErrnoException } from '../../util/is-error';
|
||||
import { isErrnoException } from '@vercel/error-utils';
|
||||
|
||||
const COMMAND_CONFIG = {
|
||||
dev: ['dev'],
|
||||
|
||||
@@ -11,7 +11,7 @@ import promptBool from '../../util/input/prompt-bool';
|
||||
import purchaseDomain from '../../util/domains/purchase-domain';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { errorToString } from '../../util/is-error';
|
||||
import { errorToString } from '@vercel/error-utils';
|
||||
|
||||
type Options = {};
|
||||
|
||||
|
||||
29
packages/cli/src/commands/env/pull.ts
vendored
29
packages/cli/src/commands/env/pull.ts
vendored
@@ -4,21 +4,21 @@ import { closeSync, openSync, readSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { Project, ProjectEnvTarget } from '../../types';
|
||||
import Client from '../../util/client';
|
||||
import exposeSystemEnvs from '../../util/dev/expose-system-envs';
|
||||
import { emoji, prependEmoji } from '../../util/emoji';
|
||||
import getSystemEnvValues from '../../util/env/get-system-env-values';
|
||||
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import { Output } from '../../util/output';
|
||||
import param from '../../util/output/param';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { EnvRecordsSource } from '../../util/env/get-env-records';
|
||||
import {
|
||||
EnvRecordsSource,
|
||||
pullEnvRecords,
|
||||
} from '../../util/env/get-env-records';
|
||||
import {
|
||||
buildDeltaString,
|
||||
createEnvObject,
|
||||
} from '../../util/env/diff-env-files';
|
||||
import { isErrnoException } from '../../util/is-error';
|
||||
import { isErrnoException } from '@vercel/error-utils';
|
||||
|
||||
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
|
||||
|
||||
@@ -97,20 +97,11 @@ export default async function pull(
|
||||
const pullStamp = stamp();
|
||||
output.spinner('Downloading');
|
||||
|
||||
const [{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([
|
||||
getDecryptedEnvRecords(output, client, project.id, source, environment),
|
||||
project.autoExposeSystemEnvs
|
||||
? getSystemEnvValues(output, client, project.id)
|
||||
: { systemEnvValues: [] },
|
||||
]);
|
||||
|
||||
const records = exposeSystemEnvs(
|
||||
projectEnvs,
|
||||
systemEnvValues,
|
||||
project.autoExposeSystemEnvs,
|
||||
undefined,
|
||||
environment
|
||||
);
|
||||
const records = (
|
||||
await pullEnvRecords(output, client, project.id, source, {
|
||||
target: environment || ProjectEnvTarget.Development,
|
||||
})
|
||||
).env;
|
||||
|
||||
let deltaString = '';
|
||||
let oldEnv;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import chalk from 'chalk';
|
||||
import Client from '../../util/client';
|
||||
import { ensureLink } from '../../util/ensure-link';
|
||||
import { ensureLink } from '../../util/link/ensure-link';
|
||||
import getArgs from '../../util/get-args';
|
||||
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
|
||||
import handleError from '../../util/handle-error';
|
||||
@@ -25,7 +25,7 @@ const help = () => {
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
-y, --yes Skip confirmation when connecting a Git provider
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -35,7 +35,9 @@ const help = () => {
|
||||
|
||||
${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(
|
||||
`$ ${getPkgName()} git connect https://github.com/user/repo.git`
|
||||
@@ -78,7 +80,7 @@ export default async function main(client: Client) {
|
||||
argv._ = argv._.slice(1);
|
||||
subcommand = argv._[0];
|
||||
const args = argv._.slice(1);
|
||||
const confirm = Boolean(argv['--yes']);
|
||||
const autoConfirm = Boolean(argv['--yes']);
|
||||
const { output } = client;
|
||||
|
||||
let paths = [process.cwd()];
|
||||
@@ -88,7 +90,7 @@ export default async function main(client: Client) {
|
||||
}
|
||||
const { path } = pathValidation;
|
||||
|
||||
const linkedProject = await ensureLink('git', client, path, confirm);
|
||||
const linkedProject = await ensureLink('git', client, path, { autoConfirm });
|
||||
if (typeof linkedProject === 'number') {
|
||||
return linkedProject;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import handleError from '../../util/handle-error';
|
||||
import logo from '../../util/output/logo';
|
||||
import init from './init';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
import { isError } from '../../util/is-error';
|
||||
import { isError } from '@vercel/error-utils';
|
||||
|
||||
const COMMAND_CONFIG = {
|
||||
init: ['init'],
|
||||
|
||||
@@ -13,7 +13,7 @@ import { getDeployment } from '../util/get-deployment';
|
||||
import { Deployment } from '@vercel/client';
|
||||
import { Build } from '../types';
|
||||
import title from 'title';
|
||||
import { isErrnoException } from '../util/is-error';
|
||||
import { isErrnoException } from '@vercel/error-utils';
|
||||
import { isAPIError } from '../util/errors-ts';
|
||||
import { URL } from 'url';
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@ import Client from '../../util/client';
|
||||
import getArgs from '../../util/get-args';
|
||||
import logo from '../../util/output/logo';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
import setupAndLink from '../../util/link/setup-and-link';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import param from '../../util/output/param';
|
||||
import { ensureLink } from '../../util/link/ensure-link';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -70,31 +68,16 @@ export default async function main(client: Client) {
|
||||
}
|
||||
|
||||
const cwd = argv._[1] || process.cwd();
|
||||
const link = await setupAndLink(client, cwd, {
|
||||
|
||||
const link = await ensureLink('link', client, cwd, {
|
||||
autoConfirm: !!argv['--yes'],
|
||||
forceDelete: true,
|
||||
autoConfirm: argv['--yes'],
|
||||
projectName: argv['--project'],
|
||||
successEmoji: 'success',
|
||||
setupMsg: 'Set up',
|
||||
});
|
||||
|
||||
if (link.status === 'error') {
|
||||
if (link.reason === 'HEADLESS') {
|
||||
client.output.error(
|
||||
`Command ${getCommandName(
|
||||
'link'
|
||||
)} requires confirmation. Use option ${param('--yes')} to confirm.`
|
||||
);
|
||||
}
|
||||
return link.exitCode;
|
||||
} else if (link.status === 'not_linked') {
|
||||
// User aborted project linking questions
|
||||
return 0;
|
||||
} else if (link.status === 'linked') {
|
||||
// Successfully linked
|
||||
return 0;
|
||||
} else {
|
||||
const err: never = link;
|
||||
throw new Error('Unknown link status: ' + err);
|
||||
if (typeof link === 'number') {
|
||||
return link;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ import Client from '../util/client';
|
||||
import { Deployment } from '@vercel/client';
|
||||
import validatePaths from '../util/validate-paths';
|
||||
import { getLinkedProject } from '../util/projects/link';
|
||||
import { ensureLink } from '../util/ensure-link';
|
||||
import { ensureLink } from '../util/link/ensure-link';
|
||||
import getScope from '../util/get-scope';
|
||||
import { isAPIError } from '../util/errors-ts';
|
||||
import { isErrnoException } from '../util/is-error';
|
||||
import { isErrnoException } from '@vercel/error-utils';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -112,7 +112,7 @@ export default async function main(client: Client) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
const yes = !!argv['--yes'];
|
||||
const autoConfirm = !!argv['--yes'];
|
||||
const prod = argv['--prod'] || false;
|
||||
|
||||
const meta = parseMeta(argv['--meta']);
|
||||
@@ -145,7 +145,9 @@ export default async function main(client: Client) {
|
||||
// If there's no linked project and user doesn't pass `app` arg,
|
||||
// prompt to link their current directory.
|
||||
if (status === 'not_linked' && !app) {
|
||||
const linkedProject = await ensureLink('list', client, path, yes);
|
||||
const linkedProject = await ensureLink('list', client, path, {
|
||||
autoConfirm,
|
||||
});
|
||||
if (typeof linkedProject === 'number') {
|
||||
return linkedProject;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import getArgs from '../util/get-args';
|
||||
import Client from '../util/client';
|
||||
import { getCommandName, getPkgName } from '../util/pkg-name';
|
||||
import { isAPIError } from '../util/errors-ts';
|
||||
import { errorToString } from '../util/is-error';
|
||||
import { errorToString } from '@vercel/error-utils';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
|
||||
@@ -4,24 +4,18 @@ import Client from '../util/client';
|
||||
import { ProjectEnvTarget } from '../types';
|
||||
import { emoji, prependEmoji } from '../util/emoji';
|
||||
import getArgs from '../util/get-args';
|
||||
import setupAndLink from '../util/link/setup-and-link';
|
||||
import logo from '../util/output/logo';
|
||||
import stamp from '../util/output/stamp';
|
||||
import { getPkgName } from '../util/pkg-name';
|
||||
import {
|
||||
getLinkedProject,
|
||||
VERCEL_DIR,
|
||||
VERCEL_DIR_PROJECT,
|
||||
} from '../util/projects/link';
|
||||
import { VERCEL_DIR, VERCEL_DIR_PROJECT } from '../util/projects/link';
|
||||
import { writeProjectSettings } from '../util/projects/project-settings';
|
||||
import envPull from './env/pull';
|
||||
import { getCommandName } from '../util/pkg-name';
|
||||
import param from '../util/output/param';
|
||||
import type { Project, Org } from '../types';
|
||||
import type { Project } from '../types';
|
||||
import {
|
||||
isValidEnvTarget,
|
||||
getEnvTargetPlaceholder,
|
||||
} from '../util/env/env-target';
|
||||
import { ensureLink } from '../util/link/ensure-link';
|
||||
|
||||
const help = () => {
|
||||
return console.log(`
|
||||
@@ -83,43 +77,6 @@ function parseArgs(client: Client) {
|
||||
return argv;
|
||||
}
|
||||
|
||||
type LinkResult = {
|
||||
org: Org;
|
||||
project: Project;
|
||||
};
|
||||
async function ensureLink(
|
||||
client: Client,
|
||||
cwd: string,
|
||||
yes: boolean
|
||||
): Promise<LinkResult | number> {
|
||||
let link = await getLinkedProject(client, cwd);
|
||||
if (link.status === 'not_linked') {
|
||||
link = await setupAndLink(client, cwd, {
|
||||
autoConfirm: yes,
|
||||
successEmoji: 'link',
|
||||
setupMsg: 'Set up',
|
||||
});
|
||||
|
||||
if (link.status === 'not_linked') {
|
||||
// User aborted project linking questions
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (link.status === 'error') {
|
||||
if (link.reason === 'HEADLESS') {
|
||||
client.output.error(
|
||||
`Command ${getCommandName(
|
||||
'pull'
|
||||
)} requires confirmation. Use option ${param('--yes')} to confirm.`
|
||||
);
|
||||
}
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
return { org: link.org, project: link.project };
|
||||
}
|
||||
|
||||
async function pullAllEnvFiles(
|
||||
environment: ProjectEnvTarget,
|
||||
client: Client,
|
||||
@@ -140,7 +97,9 @@ async function pullAllEnvFiles(
|
||||
);
|
||||
}
|
||||
|
||||
function parseEnvironment(environment = 'development'): ProjectEnvTarget {
|
||||
export function parseEnvironment(
|
||||
environment = 'development'
|
||||
): ProjectEnvTarget {
|
||||
if (!isValidEnvTarget(environment)) {
|
||||
throw new Error(
|
||||
`environment "${environment}" not supported; must be one of ${getEnvTargetPlaceholder()}`
|
||||
@@ -156,10 +115,10 @@ export default async function main(client: Client) {
|
||||
}
|
||||
|
||||
const cwd = argv._[1] || process.cwd();
|
||||
const yes = Boolean(argv['--yes']);
|
||||
const autoConfirm = Boolean(argv['--yes']);
|
||||
const environment = parseEnvironment(argv['--environment'] || undefined);
|
||||
|
||||
const link = await ensureLink(client, cwd, yes);
|
||||
const link = await ensureLink('pull', client, cwd, { autoConfirm });
|
||||
if (typeof link === 'number') {
|
||||
return link;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { getPkgName, getCommandName } from '../../util/pkg-name';
|
||||
import Client from '../../util/client';
|
||||
import createTeam from '../../util/teams/create-team';
|
||||
import patchTeam from '../../util/teams/patch-team';
|
||||
import { errorToString, isError } from '../../util/is-error';
|
||||
import { errorToString, isError } from '@vercel/error-utils';
|
||||
|
||||
const validateSlugKeypress = (data: string, value: string) =>
|
||||
// TODO: the `value` here should contain the current value + the keypress
|
||||
|
||||
@@ -12,7 +12,7 @@ import { email as regexEmail } from '../../util/input/regexes';
|
||||
import getTeams from '../../util/teams/get-teams';
|
||||
import inviteUserToTeam from '../../util/teams/invite-user-to-team';
|
||||
import { isAPIError } from '../../util/errors-ts';
|
||||
import { errorToString, isError } from '../../util/is-error';
|
||||
import { errorToString, isError } from '@vercel/error-utils';
|
||||
|
||||
const validateEmail = (data: string) =>
|
||||
regexEmail.test(data.trim()) || data.length === 0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
import { isErrnoException, isError, errorToString } from './util/is-error';
|
||||
import { isErrnoException, isError, errorToString } from '@vercel/error-utils';
|
||||
|
||||
try {
|
||||
// Test to see if cwd has been deleted before
|
||||
@@ -7,7 +7,7 @@ try {
|
||||
process.cwd();
|
||||
} catch (err: unknown) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -610,6 +610,21 @@ const main = async () => {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (isErrnoException(err) && err.code === 'ECONNRESET') {
|
||||
// Error message will look like the following:
|
||||
// request to https://api.vercel.com/v2/user failed, reason: socket hang up
|
||||
const matches = /request to https:\/\/(.*?)\//.exec(err.message || '');
|
||||
const hostname = matches?.[1];
|
||||
if (hostname) {
|
||||
output.error(
|
||||
`Connection to ${highlight(
|
||||
hostname
|
||||
)} interrupted. Please verify your internet connectivity and DNS configuration.`
|
||||
);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (
|
||||
isErrnoException(err) &&
|
||||
(err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED')
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { URL } from 'url';
|
||||
import plural from 'pluralize';
|
||||
import npa from 'npm-package-arg';
|
||||
import { satisfies } from 'semver';
|
||||
import { dirname, join } from 'path';
|
||||
import { mkdirp, outputJSON, readJSON, symlink } from 'fs-extra';
|
||||
import { isStaticRuntime } from '@vercel/fs-detectors';
|
||||
import {
|
||||
BuilderV2,
|
||||
BuilderV3,
|
||||
@@ -13,6 +16,9 @@ import { VERCEL_DIR } from '../projects/link';
|
||||
import { Output } from '../output';
|
||||
import readJSONFile from '../read-json-file';
|
||||
import { CantParseJSONFile } from '../errors-ts';
|
||||
import { errorToString, isErrnoException, isError } from '@vercel/error-utils';
|
||||
import cmd from '../output/cmd';
|
||||
import code from '../output/code';
|
||||
|
||||
export interface BuilderWithPkg {
|
||||
path: string;
|
||||
@@ -81,7 +87,7 @@ export async function resolveBuilders(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name === '@vercel/static' || name === '@now/static') {
|
||||
if (isStaticRuntime(name)) {
|
||||
// `@vercel/static` is a special-case built-in builder
|
||||
builders.set(name, {
|
||||
builder: staticBuilder,
|
||||
@@ -201,15 +207,54 @@ async function installBuilders(
|
||||
if (err.code !== 'EEXIST') throw err;
|
||||
}
|
||||
|
||||
output.debug(`Installing Builders: ${Array.from(buildersToAdd).join(', ')}`);
|
||||
await spawnAsync('yarn', ['add', '@vercel/build-utils', ...buildersToAdd], {
|
||||
cwd: buildersDir,
|
||||
});
|
||||
output.log(
|
||||
`Installing ${plural('Builder', buildersToAdd.size)}: ${Array.from(
|
||||
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
|
||||
const nowScopePath = join(buildersDir, 'node_modules/@now');
|
||||
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,
|
||||
// in case they were installed from a URL
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
|
||||
@@ -39,3 +40,18 @@ export const build: BuildV2 = async ({ entrypoint, files, config }) => {
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import Client from '../client';
|
||||
import { Cert } from '../../types';
|
||||
import { isErrnoException } from '../is-error';
|
||||
import { isErrnoException } from '@vercel/error-utils';
|
||||
import { isAPIError } from '../errors-ts';
|
||||
|
||||
export default async function createCertFromFile(
|
||||
|
||||
@@ -2,7 +2,7 @@ import retry from 'async-retry';
|
||||
import { Cert } from '../../types';
|
||||
import Client from '../client';
|
||||
import { isAPIError } from '../errors-ts';
|
||||
import { isError } from '../is-error';
|
||||
import { isError } from '@vercel/error-utils';
|
||||
|
||||
// When it's a configuration error we should retry because of the DNS propagation
|
||||
// otherwise we bail to handle the error in the upper level
|
||||
|
||||
@@ -23,6 +23,7 @@ import type {
|
||||
} from '../types';
|
||||
import { sharedPromise } from './promise';
|
||||
import { APIError } from './errors-ts';
|
||||
import { normalizeError } from '@vercel/error-utils';
|
||||
|
||||
const isSAMLError = (v: any): v is SAMLError => {
|
||||
return v && v.saml;
|
||||
@@ -146,10 +147,15 @@ export default class Client extends EventEmitter implements Stdio {
|
||||
const error = await responseError(res);
|
||||
|
||||
if (isSAMLError(error)) {
|
||||
// A SAML error means the token is expired, or is not
|
||||
// designated for the requested team, so the user needs
|
||||
// to re-authenticate
|
||||
await this.reauthenticate(error);
|
||||
try {
|
||||
// A SAML error means the token is expired, or is not
|
||||
// designated for the requested team, so the user needs
|
||||
// to re-authenticate
|
||||
await this.reauthenticate(error);
|
||||
} catch (reauthError) {
|
||||
// there's no sense in retrying
|
||||
return bail(normalizeError(reauthError));
|
||||
}
|
||||
} else if (res.status >= 400 && res.status < 500) {
|
||||
// Any other 4xx should bail without retrying
|
||||
return bail(error);
|
||||
@@ -186,7 +192,7 @@ export default class Client extends EventEmitter implements Stdio {
|
||||
`Failed to re-authenticate for ${bold(error.scope)} scope`
|
||||
);
|
||||
}
|
||||
process.exit(1);
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.authConfig.token = result.token;
|
||||
|
||||
@@ -10,7 +10,7 @@ import error from '../output/error';
|
||||
import highlight from '../output/highlight';
|
||||
import { VercelConfig } from '../dev/types';
|
||||
import { AuthConfig, GlobalConfig } from '../../types';
|
||||
import { isErrnoException, isError } from '../is-error';
|
||||
import { isErrnoException, isError } from '@vercel/error-utils';
|
||||
|
||||
const VERCEL_DIR = getGlobalPathConfig();
|
||||
const CONFIG_FILE_PATH = join(VERCEL_DIR, 'config.json');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user