mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 21:07:47 +00:00
Compare commits
236 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 | ||
|
|
6700630feb | ||
|
|
34cd8b4144 | ||
|
|
ad0ed6d852 | ||
|
|
0bad09b47a | ||
|
|
5120689bf2 | ||
|
|
5a39fd9242 | ||
|
|
352cd00ef0 | ||
|
|
abfe817f86 | ||
|
|
ebb5e2b208 | ||
|
|
e34858d082 | ||
|
|
f03c947f91 | ||
|
|
0d13fe7e34 | ||
|
|
4afec9d373 | ||
|
|
09c85f63d2 | ||
|
|
9963965e9a | ||
|
|
f3ed279007 | ||
|
|
4fe489edad | ||
|
|
e2911aac0b | ||
|
|
f3cbc5d746 | ||
|
|
40df88b483 | ||
|
|
75c4f45b73 | ||
|
|
05a236f944 | ||
|
|
4b7383f521 | ||
|
|
c263c31e48 | ||
|
|
c80530f9b1 | ||
|
|
16fd4396ef | ||
|
|
4e7138f400 | ||
|
|
5273279cf1 | ||
|
|
37290039d6 | ||
|
|
b52d01f809 | ||
|
|
ffefaf82a1 | ||
|
|
6d8dbfc7d6 | ||
|
|
551cd7f688 | ||
|
|
2dfb6b45cd | ||
|
|
65ae2a289e | ||
|
|
72ea3532b1 | ||
|
|
78fac00823 | ||
|
|
9e255afa37 | ||
|
|
e4be68270f | ||
|
|
9c636dc1ba | ||
|
|
c98c9996bf | ||
|
|
0fcf172a10 | ||
|
|
99e5c4a6db | ||
|
|
b8269b0111 | ||
|
|
decac0fe3f | ||
|
|
591d1686d0 | ||
|
|
5e1d5c921c | ||
|
|
603b1256c6 | ||
|
|
6957c72828 | ||
|
|
9be3650cb7 | ||
|
|
6e1ee7a7d6 | ||
|
|
767ce2cff1 | ||
|
|
bb1d0ce1b7 | ||
|
|
31f79c7de1 | ||
|
|
4c230c8436 | ||
|
|
7941f5a104 | ||
|
|
5b931afbf3 | ||
|
|
15080364b8 | ||
|
|
47e3381c6d | ||
|
|
33aefdc029 | ||
|
|
30fe76a0cf | ||
|
|
97ef88dc28 | ||
|
|
f679098d7a | ||
|
|
2b57e12ad3 | ||
|
|
c4e94ad03f |
28
.github/CODEOWNERS
vendored
28
.github/CODEOWNERS
vendored
@@ -1,23 +1,17 @@
|
|||||||
# Documentation
|
# Documentation
|
||||||
# https://help.github.com/en/articles/about-code-owners
|
# https://help.github.com/en/articles/about-code-owners
|
||||||
|
|
||||||
* @TooTallNate @EndangeredMassa @styfle
|
* @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood
|
||||||
/.github/workflows @TooTallNate @EndangeredMassa @styfle @ijjk
|
/.github/workflows @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||||
/packages/frameworks @TooTallNate @EndangeredMassa @styfle @AndyBitz
|
/packages/cli/src/commands/domains @mglagola @anatrajkovska
|
||||||
/packages/cli/src/commands/domains @javivelasco @mglagola @anatrajkovska
|
/packages/cli/src/commands/certs @mglagola @anatrajkovska
|
||||||
/packages/cli/src/commands/certs @javivelasco @mglagola @anatrajkovska
|
/packages/cli/src/commands/env @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood
|
||||||
/packages/cli/src/commands/env @styfle @lucleray
|
/packages/fs-detectors @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @agadzik @chloetedder
|
||||||
/packages/client @TooTallNate @EndangeredMassa @styfle
|
/packages/middleware @gdborton @vercel/edge-function
|
||||||
/packages/build-utils @TooTallNate @EndangeredMassa @styfle @AndyBitz
|
/packages/node-bridge @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||||
/packages/middleware @gdborton @javivelasco
|
/packages/next @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||||
/packages/node @TooTallNate @EndangeredMassa @styfle
|
/packages/routing-utils @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
|
||||||
/packages/node-bridge @TooTallNate @EndangeredMassa @styfle @ijjk
|
/packages/edge @vercel/edge-function
|
||||||
/packages/next @TooTallNate @ijjk
|
|
||||||
/packages/go @TooTallNate @EndangeredMassa @styfle
|
|
||||||
/packages/python @TooTallNate @EndangeredMassa @styfle
|
|
||||||
/packages/ruby @TooTallNate @EndangeredMassa @styfle
|
|
||||||
/packages/static-build @TooTallNate @EndangeredMassa @styfle @AndyBitz
|
|
||||||
/packages/routing-utils @TooTallNate @EndangeredMassa @styfle @ijjk
|
|
||||||
/examples @leerob
|
/examples @leerob
|
||||||
/examples/create-react-app @Timer
|
/examples/create-react-app @Timer
|
||||||
/examples/nextjs @timneutkens @ijjk @styfle
|
/examples/nextjs @timneutkens @ijjk @styfle
|
||||||
|
|||||||
1
.github/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
|
git clone https://github.com/vercel/vercel
|
||||||
cd vercel
|
cd vercel
|
||||||
|
corepack enable
|
||||||
yarn install
|
yarn install
|
||||||
yarn bootstrap
|
yarn bootstrap
|
||||||
yarn build
|
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 }}
|
|
||||||
25
.github/workflows/cron-update-next.yml
vendored
Normal file
25
.github/workflows/cron-update-next.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
name: Cron Update Next
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Run every 4 hours https://crontab.guru/every-4-hours
|
||||||
|
schedule:
|
||||||
|
- cron: '0 */4 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-pull-request:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
# 0 means fetch all commits so we can commit and push in the script below
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
# See https://github.com/actions/github-script#run-a-separate-file-with-an-async-function
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const script = require('./utils/update-next.js')
|
||||||
|
await script({ github, context })
|
||||||
15
.github/workflows/publish.yml
vendored
15
.github/workflows/publish.yml
vendored
@@ -7,13 +7,20 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- '!*'
|
- '!*'
|
||||||
|
|
||||||
|
env:
|
||||||
|
TURBO_REMOTE_ONLY: 'true'
|
||||||
|
TURBO_TEAM: 'vercel'
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
name: Publish
|
name: Publish
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
- name: Check Release
|
- name: Check Release
|
||||||
id: check-release
|
id: check-release
|
||||||
run: |
|
run: |
|
||||||
@@ -26,14 +33,16 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.13.15'
|
go-version: '1.13.15'
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v3
|
||||||
|
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
- name: Install
|
- name: Install
|
||||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||||
run: yarn install --check-files --frozen-lockfile --network-timeout 1000000
|
run: yarn install --check-files --frozen-lockfile --network-timeout 1000000
|
||||||
|
|||||||
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!');
|
||||||
37
.github/workflows/test-integration-cli.yml
vendored
37
.github/workflows/test-integration-cli.yml
vendored
@@ -8,6 +8,15 @@ on:
|
|||||||
- '!*'
|
- '!*'
|
||||||
pull_request:
|
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:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: CLI
|
name: CLI
|
||||||
@@ -19,28 +28,20 @@ jobs:
|
|||||||
node: [14]
|
node: [14]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Conditionally set remote env
|
- uses: actions/checkout@v3
|
||||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
with:
|
||||||
run: |
|
fetch-depth: 2
|
||||||
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
- uses: actions/setup-go@v3
|
||||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
|
||||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
|
||||||
- uses: actions/setup-go@v2
|
|
||||||
with:
|
with:
|
||||||
go-version: '1.13.15'
|
go-version: '1.13.15'
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v3
|
||||||
|
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
- uses: actions/checkout@v2
|
cache: 'yarn'
|
||||||
with:
|
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||||
fetch-depth: 100
|
|
||||||
- run: git --version
|
|
||||||
- run: git fetch origin main --depth=100
|
|
||||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
|
||||||
- run: git diff origin/main...HEAD --name-only
|
|
||||||
- run: yarn install --network-timeout 1000000
|
|
||||||
- run: yarn run build
|
- run: yarn run build
|
||||||
- run: yarn test-integration-cli
|
- run: yarn test-integration-cli
|
||||||
env:
|
env:
|
||||||
VERCEL_TEAM_TOKEN: ${{ secrets.VERCEL_TEAM_TOKEN }}
|
VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }}
|
||||||
VERCEL_REGISTRATION_URL: ${{ secrets.VERCEL_REGISTRATION_URL }}
|
VERCEL_TEST_REGISTRATION_URL: ${{ secrets.VERCEL_TEST_REGISTRATION_URL }}
|
||||||
|
|||||||
33
.github/workflows/test-unit.yml
vendored
33
.github/workflows/test-unit.yml
vendored
@@ -8,6 +8,15 @@ on:
|
|||||||
- '!*'
|
- '!*'
|
||||||
pull_request:
|
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:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Unit
|
name: Unit
|
||||||
@@ -19,26 +28,18 @@ jobs:
|
|||||||
node: [14]
|
node: [14]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Conditionally set remote env
|
- uses: actions/setup-go@v3
|
||||||
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@v2
|
|
||||||
with:
|
with:
|
||||||
go-version: '1.13.15'
|
go-version: '1.13.15'
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
fetch-depth: 2
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/setup-node@v3
|
||||||
|
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
|
||||||
with:
|
with:
|
||||||
fetch-depth: 100
|
node-version: ${{ matrix.node }}
|
||||||
- run: git --version
|
cache: 'yarn'
|
||||||
- run: git fetch origin main --depth=100
|
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
|
||||||
- run: git diff origin/main...HEAD --name-only
|
|
||||||
- run: yarn install --network-timeout 1000000
|
|
||||||
- run: yarn run build
|
- run: yarn run build
|
||||||
- run: yarn run lint
|
- run: yarn run lint
|
||||||
if: matrix.os == 'ubuntu-latest' && matrix.node == 14 # only run lint once
|
if: matrix.os == 'ubuntu-latest' && matrix.node == 14 # only run lint once
|
||||||
|
|||||||
42
.github/workflows/test.yml
vendored
42
.github/workflows/test.yml
vendored
@@ -10,6 +10,13 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
NODE_VERSION: '14'
|
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:
|
jobs:
|
||||||
setup:
|
setup:
|
||||||
@@ -19,14 +26,18 @@ jobs:
|
|||||||
tests: ${{ steps['set-tests'].outputs['tests'] }}
|
tests: ${{ steps['set-tests'].outputs['tests'] }}
|
||||||
dplUrl: ${{ steps.waitForTarball.outputs.url }}
|
dplUrl: ${{ steps.waitForTarball.outputs.url }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- run: git --version
|
with:
|
||||||
- run: git fetch origin main
|
fetch-depth: 2
|
||||||
- uses: actions/setup-node@v2
|
- 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:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
- run: yarn install --network-timeout 1000000
|
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||||
- id: set-tests
|
- id: set-tests
|
||||||
run: |
|
run: |
|
||||||
TESTS_ARRAY=$(node utils/chunk-tests.js $SCRIPT_NAME)
|
TESTS_ARRAY=$(node utils/chunk-tests.js $SCRIPT_NAME)
|
||||||
@@ -52,19 +63,14 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include: ${{ fromJson(needs.setup.outputs['tests']) }}
|
include: ${{ fromJson(needs.setup.outputs['tests']) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Conditionally set remote env
|
- uses: actions/checkout@v3
|
||||||
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@v2
|
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.13.15'
|
go-version: '1.13.15'
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v3
|
||||||
|
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
@@ -76,16 +82,16 @@ jobs:
|
|||||||
- run: yarn install --network-timeout 1000000
|
- run: yarn install --network-timeout 1000000
|
||||||
|
|
||||||
- name: Build ${{matrix.packageName}} and all its dependencies
|
- 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:
|
env:
|
||||||
FORCE_COLOR: '1'
|
FORCE_COLOR: '1'
|
||||||
- name: Test ${{matrix.packageName}}
|
- 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
|
shell: bash
|
||||||
env:
|
env:
|
||||||
VERCEL_CLI_VERSION: ${{ needs.setup.outputs.dplUrl }}/tarballs/vercel.tgz
|
VERCEL_CLI_VERSION: ${{ needs.setup.outputs.dplUrl }}/tarballs/vercel.tgz
|
||||||
VERCEL_TEAM_TOKEN: ${{ secrets.VERCEL_TEAM_TOKEN }}
|
VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }}
|
||||||
VERCEL_REGISTRATION_URL: ${{ secrets.VERCEL_REGISTRATION_URL }}
|
VERCEL_TEST_REGISTRATION_URL: ${{ secrets.VERCEL_TEST_REGISTRATION_URL }}
|
||||||
FORCE_COLOR: '1'
|
FORCE_COLOR: '1'
|
||||||
|
|
||||||
conclusion:
|
conclusion:
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -28,3 +28,4 @@ test/lib/deployment/failed-page.txt
|
|||||||
__pycache__
|
__pycache__
|
||||||
.vercel
|
.vercel
|
||||||
.turbo
|
.turbo
|
||||||
|
turbo-cache-key.json
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
version = 1
|
version = 1
|
||||||
|
|
||||||
[merge]
|
[merge]
|
||||||
automerge_label = ["semver-major","semver-minor","semver-patch"]
|
automerge_label = ["pr: automerge"]
|
||||||
blacklist_title_regex = "^WIP.*"
|
blacklist_title_regex = "^WIP.*"
|
||||||
blacklist_labels = ["work in progress"]
|
blacklist_labels = ["work in progress"]
|
||||||
method = "squash"
|
method = "squash"
|
||||||
|
|||||||
@@ -63,9 +63,6 @@ export async function build(options: BuildOptions) {
|
|||||||
const lambda = createLambda(/* … */);
|
const lambda = createLambda(/* … */);
|
||||||
return {
|
return {
|
||||||
output: lambda,
|
output: lambda,
|
||||||
watch: [
|
|
||||||
// Dependent files to trigger a rebuild in `vercel dev` go here…
|
|
||||||
],
|
|
||||||
routes: [
|
routes: [
|
||||||
// If your Runtime needs to define additional routing, define it here…
|
// If your Runtime needs to define additional routing, define it here…
|
||||||
],
|
],
|
||||||
@@ -115,7 +112,8 @@ export async function shouldServe(options: ShouldServeOptions) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If this function is not defined, Vercel CLI will use the [default implementation](https://github.com/vercel/vercel/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
|
If this function is not defined, Vercel CLI will use the [default
|
||||||
|
implementation](https://github.com/vercel/vercel/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
|
||||||
|
|
||||||
### `startDevServer()`
|
### `startDevServer()`
|
||||||
|
|
||||||
@@ -189,7 +187,8 @@ If you need to share state between those steps, use the filesystem.
|
|||||||
|
|
||||||
### Directory and Cache Lifecycle
|
### Directory and Cache Lifecycle
|
||||||
|
|
||||||
When a new build is created, we pre-populate the `workPath` supplied to `analyze` with the results of the `prepareCache` step of the previous build.
|
When a new build is created, we pre-populate the `workPath` supplied to `analyze` with the results of the `prepareCache` step of the
|
||||||
|
previous build.
|
||||||
|
|
||||||
The `analyze` step can modify that directory, and it will not be re-created when it's supplied to `build` and `prepareCache`.
|
The `analyze` step can modify that directory, and it will not be re-created when it's supplied to `build` and `prepareCache`.
|
||||||
|
|
||||||
@@ -197,6 +196,77 @@ The `analyze` step can modify that directory, and it will not be re-created when
|
|||||||
|
|
||||||
The env and secrets specified by the user as `build.env` are passed to the Runtime process. This means you can access user env via `process.env` in Node.js.
|
The env and secrets specified by the user as `build.env` are passed to the Runtime process. This means you can access user env via `process.env` in Node.js.
|
||||||
|
|
||||||
|
### Supporting Large Environment
|
||||||
|
|
||||||
|
We provide the ability to support more than 4KB of environment (up to 64KB) by way of
|
||||||
|
a Lambda runtime wrapper that is added to every Lambda function we create. These are
|
||||||
|
supported by many of the existing Lambda runtimes, but custom runtimes may require
|
||||||
|
additional work.
|
||||||
|
|
||||||
|
The following Lambda runtime families have built-in support for the runtime wrapper:
|
||||||
|
|
||||||
|
- `nodejs`
|
||||||
|
- `python` (>= 3.8)
|
||||||
|
- `ruby`
|
||||||
|
- `java11`
|
||||||
|
- `java8.al2` (not `java8`)
|
||||||
|
- `dotnetcore`
|
||||||
|
|
||||||
|
If a custom runtime is based on one of these Lambda runtimes, large environment
|
||||||
|
support will be available without further configuration. Custom runtimes based on
|
||||||
|
other Lambda runtimes, including those that provide the runtime via `provided` and
|
||||||
|
`provided.al2`, must implement runtime wrapper support and indicate it via the
|
||||||
|
`supportsWrapper` flag when calling [`createLambda`](#createlambda()).
|
||||||
|
|
||||||
|
To add support for runtime wrappers to a custom runtime, first check the value of the
|
||||||
|
`AWS_LAMBDA_EXEC_WRAPPER` environment variable in the bootstrap script. Its value is
|
||||||
|
the path to the wrapper executable.
|
||||||
|
|
||||||
|
The wrapper must be passed the path to the runtime as well as any parameters that the
|
||||||
|
runtime requires. This is most easily done in a small `bootstrap` script.
|
||||||
|
|
||||||
|
In this simple `bash` example, the runtime is called directly if
|
||||||
|
`AWS_LAMBDA_EXEC_WRAPPER` has no value, otherwise the wrapper is called with the
|
||||||
|
runtime command as parameters.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
exec $AWS_LAMBDA_EXEC_WRAPPER path/to/runtime param1 param2
|
||||||
|
```
|
||||||
|
|
||||||
|
If the `bootstrap` file is not a launcher script, but the entrypoint of the runtime
|
||||||
|
itself, replace the bootstrap process with the wrapper. Pass the path and parameters
|
||||||
|
of the executing file, ensuring the `AWS_LAMBDA_EXEC_WRAPPER` environment variable is
|
||||||
|
set to blank.
|
||||||
|
|
||||||
|
This `bash` example uses `exec` to replace the running bootstrap process with the
|
||||||
|
wrapper, passing its own path and parameters.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ -n $AWS_LAMBDA_EXEC_WRAPPER ]]
|
||||||
|
__WRAPPER=$AWS_LAMBDA_EXEC_WRAPPER
|
||||||
|
AWS_LAMBDA_EXEC_WRAPPER=""
|
||||||
|
exec $__WRAPPER "$0" "${@}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# start the actual runtime functionality
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that unsetting the variable may not have the desired effect due to the way
|
||||||
|
Lambda spawns runtime processes. It is better to explicitly set it to blank.
|
||||||
|
|
||||||
|
The best way to replace the existing bootstrap process is with the
|
||||||
|
[`execve`](https://www.man7.org/linux/man-pages/man2/execve.2.html) syscall.
|
||||||
|
This is achieved by using `exec` in `bash` to replace the running process with the wrapper,
|
||||||
|
maintaining the same PID and environment.
|
||||||
|
|
||||||
|
Once support for runtime wrappers is included, ensure `supportsWrapper` is set to
|
||||||
|
`true` in the call to [`createLambda`](#createlambda()). This will inform the build
|
||||||
|
process to enable large environment support for this runtime.
|
||||||
|
|
||||||
### Utilities as peerDependencies
|
### Utilities as peerDependencies
|
||||||
|
|
||||||
When you publish your Runtime to npm, make sure to not specify `@vercel/build-utils` (as seen below in the API definitions) as a dependency, but rather as part of `peerDependencies`.
|
When you publish your Runtime to npm, make sure to not specify `@vercel/build-utils` (as seen below in the API definitions) as a dependency, but rather as part of `peerDependencies`.
|
||||||
@@ -304,13 +374,14 @@ This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refere
|
|||||||
- `handler: String` path to handler file and (optionally) a function name it exports
|
- `handler: String` path to handler file and (optionally) a function name it exports
|
||||||
- `runtime: LambdaRuntime` the name of the lambda runtime
|
- `runtime: LambdaRuntime` the name of the lambda runtime
|
||||||
- `environment: Object` key-value map of handler-related (aside of those passed by user) environment variables
|
- `environment: Object` key-value map of handler-related (aside of those passed by user) environment variables
|
||||||
|
- `supportsWrapper: Boolean` set to true to indicate that Lambda runtime wrappers are supported by this runtime
|
||||||
|
|
||||||
### `LambdaRuntime`
|
### `LambdaRuntime`
|
||||||
|
|
||||||
This is an abstract enumeration type that is implemented by one of the following possible `String` values:
|
This is an abstract enumeration type that is implemented by one of the following possible `String` values:
|
||||||
|
|
||||||
|
- `nodejs16.x`
|
||||||
- `nodejs14.x`
|
- `nodejs14.x`
|
||||||
- `nodejs12.x`
|
|
||||||
- `go1.x`
|
- `go1.x`
|
||||||
- `java11`
|
- `java11`
|
||||||
- `python3.9`
|
- `python3.9`
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -19,11 +19,9 @@
|
|||||||
|
|
||||||
## Vercel
|
## 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 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.
|
||||||
|
|
||||||
We make it easy for frontend teams to **develop, preview, and ship** delightful user experiences, where performance is the default.
|
|
||||||
|
|
||||||
## Deploy
|
## Deploy
|
||||||
|
|
||||||
@@ -35,6 +33,6 @@ For details on how to use Vercel, check out our [documentation](https://vercel.c
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
- [Code of Conduct](https://github.com/vercel/vercel/blob/main/.github/CODE_OF_CONDUCT.md)
|
- [Code of Conduct](./.github/CODE_OF_CONDUCT.md)
|
||||||
- [Contributing Guidelines](https://github.com/vercel/vercel/blob/main/.github/CONTRIBUTING.md)
|
- [Contributing Guidelines](./.github/CONTRIBUTING.md)
|
||||||
- [MIT License](https://github.com/vercel/vercel/blob/main/LICENSE)
|
- [Apache 2.0 License](./LICENSE)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import { join, dirname } from 'path';
|
import { join, dirname } from 'path';
|
||||||
import execa from 'execa';
|
|
||||||
import { getExampleList } from '../examples/example-list';
|
import { getExampleList } from '../examples/example-list';
|
||||||
import { mapOldToNew } from '../examples/map-old-to-new';
|
import { mapOldToNew } from '../examples/map-old-to-new';
|
||||||
|
|
||||||
@@ -13,6 +12,18 @@ async function main() {
|
|||||||
await fs.rm(pubDir, { recursive: true, force: true });
|
await fs.rm(pubDir, { recursive: true, force: true });
|
||||||
await fs.mkdir(pubDir);
|
await fs.mkdir(pubDir);
|
||||||
|
|
||||||
|
await fs.cp(
|
||||||
|
join(repoRoot, 'packages', 'frameworks', 'logos'),
|
||||||
|
join(pubDir, 'framework-logos'),
|
||||||
|
{ recursive: true, force: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
await fs.cp(
|
||||||
|
join(repoRoot, 'packages', 'fs-detectors', 'logos'),
|
||||||
|
join(pubDir, 'monorepo-logos'),
|
||||||
|
{ recursive: true, force: true }
|
||||||
|
);
|
||||||
|
|
||||||
const examples = await getExampleList();
|
const examples = await getExampleList();
|
||||||
const pathListAll = join(pubDir, 'list-all.json');
|
const pathListAll = join(pubDir, 'list-all.json');
|
||||||
await fs.writeFile(pathListAll, JSON.stringify(examples));
|
await fs.writeFile(pathListAll, JSON.stringify(examples));
|
||||||
@@ -41,10 +52,6 @@ async function main() {
|
|||||||
JSON.stringify([...existingExamples, ...oldExamples])
|
JSON.stringify([...existingExamples, ...oldExamples])
|
||||||
);
|
);
|
||||||
|
|
||||||
const { stdout: sha } = await execa('git', ['rev-parse', '--short', 'HEAD'], {
|
|
||||||
cwd: repoRoot,
|
|
||||||
});
|
|
||||||
|
|
||||||
const tarballsDir = join(pubDir, 'tarballs');
|
const tarballsDir = join(pubDir, 'tarballs');
|
||||||
const packagesDir = join(repoRoot, 'packages');
|
const packagesDir = join(repoRoot, 'packages');
|
||||||
const packages = await fs.readdir(packagesDir);
|
const packages = await fs.readdir(packagesDir);
|
||||||
@@ -55,12 +62,21 @@ async function main() {
|
|||||||
'utf-8'
|
'utf-8'
|
||||||
);
|
);
|
||||||
const packageJson = JSON.parse(packageJsonRaw);
|
const packageJson = JSON.parse(packageJsonRaw);
|
||||||
const tarballName = `${packageJson.name
|
const files = await fs.readdir(fullDir);
|
||||||
.replace('@', '')
|
const tarballName = files.find(f => /^vercel-.+\.tgz$/.test(f));
|
||||||
.replace('/', '-')}-v${packageJson.version}-${sha.trim()}.tgz`;
|
if (!tarballName) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected vercel-*.tgz in ${fullDir} but found ${JSON.stringify(
|
||||||
|
files,
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const srcTarballPath = join(fullDir, tarballName);
|
||||||
const destTarballPath = join(tarballsDir, `${packageJson.name}.tgz`);
|
const destTarballPath = join(tarballsDir, `${packageJson.name}.tgz`);
|
||||||
await fs.mkdir(dirname(destTarballPath), { recursive: true });
|
await fs.mkdir(dirname(destTarballPath), { recursive: true });
|
||||||
await fs.copyFile(join(fullDir, tarballName), destTarballPath);
|
await fs.copyFile(srcTarballPath, destTarballPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Completed building static frontend.');
|
console.log('Completed building static frontend.');
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ const frameworks = (_frameworks as Framework[])
|
|||||||
defaultRoutes: undefined,
|
defaultRoutes: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (framework.logo) {
|
|
||||||
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return framework;
|
return framework;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
1
examples/astro/.gitignore
vendored
1
examples/astro/.gitignore
vendored
@@ -18,3 +18,4 @@ pnpm-debug.log*
|
|||||||
|
|
||||||
# macOS-specific files
|
# macOS-specific files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.vercel
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
README.md
|
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
# Welcome to [Astro](https://astro.build)
|
# Astro
|
||||||
|
|
||||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/starter)
|
This directory is a brief example of an [Astro](https://astro.build/) site that can be deployed to Vercel with zero configuration.
|
||||||
|
|
||||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
## Deploy Your Own
|
||||||
|
|
||||||
## 🚀 Project Structure
|
Deploy your own Astro project with Vercel.
|
||||||
|
|
||||||
|
[](https://vercel.com/new/clone?repository-url=https://github.com/vercel/vercel/tree/main/examples/astro&template=astro)
|
||||||
|
|
||||||
|
_Live Example: https://astro-template.vercel.app_
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
Inside of your Astro project, you'll see the following folders and files:
|
Inside of your Astro project, you'll see the following folders and files:
|
||||||
|
|
||||||
@@ -26,17 +32,15 @@ There's nothing special about `src/components/`, but that's where we like to put
|
|||||||
|
|
||||||
Any static assets, like images, can be placed in the `public/` directory.
|
Any static assets, like images, can be placed in the `public/` directory.
|
||||||
|
|
||||||
## 🧞 Commands
|
## Commands
|
||||||
|
|
||||||
All commands are run from the root of the project, from a terminal:
|
All commands are run from the root of the project, from a terminal:
|
||||||
|
|
||||||
| Command | Action |
|
| Command | Action |
|
||||||
| :---------------- | :------------------------------------------- |
|
| :--------------------- | :------------------------------------------------- |
|
||||||
| `npm install` | Installs dependencies |
|
| `npm install` | Installs dependencies |
|
||||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||||
| `npm run build` | Build your production site to `./dist/` |
|
| `npm run build` | Build your production site to `./dist/` |
|
||||||
| `npm run preview` | Preview your build locally, before deploying |
|
| `npm run preview` | Preview your build locally, before deploying |
|
||||||
|
| `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` |
|
||||||
## 👀 Want to learn more?
|
| `npm run astro --help` | Get help using the Astro CLI |
|
||||||
|
|
||||||
Feel free to check [our documentation](https://github.com/withastro/astro) or jump into our [Discord server](https://astro.build/chat).
|
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@example/basics",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"start": "astro dev",
|
"start": "astro dev",
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview"
|
"preview": "astro preview",
|
||||||
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"astro": "^1.0.0-beta.20"
|
"astro": "^1.0.0-rc.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
76
examples/astro/src/components/Card.astro
Normal file
76
examples/astro/src/components/Card.astro
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
href: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { href, title, body } = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<li class="link-card">
|
||||||
|
<a href={href}>
|
||||||
|
<h2>
|
||||||
|
{title}
|
||||||
|
<span>→</span>
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--link-gradient: linear-gradient(45deg, #4f39fa, #da62c4 30%, var(--color-border) 60%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-card {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
padding: 0.15rem;
|
||||||
|
background-image: var(--link-gradient);
|
||||||
|
background-size: 400%;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-position: 100%;
|
||||||
|
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-card > a {
|
||||||
|
width: 100%;
|
||||||
|
text-decoration: none;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 1em 1.3em;
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: white;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 span {
|
||||||
|
display: inline-block;
|
||||||
|
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-card:is(:hover, :focus-within) {
|
||||||
|
background-position: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-card:is(:hover, :focus-within) h2 {
|
||||||
|
color: #4f39fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-card:is(:hover, :focus-within) h2 span {
|
||||||
|
will-change: transform;
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
---
|
|
||||||
export interface Props {
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { title } = Astro.props as Props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width">
|
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
||||||
<title>{title}</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<slot />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
|
|
||||||
--font-size-lg: clamp(1.2rem, 0.7vw + 1.2rem, 1.5rem);
|
|
||||||
--font-size-xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
|
|
||||||
|
|
||||||
--color-text: hsl(12, 5%, 4%);
|
|
||||||
--color-bg: hsl(10, 21%, 95%);
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-family: system-ui, sans-serif;
|
|
||||||
font-size: var(--font-size-base);
|
|
||||||
color: var(--color-text);
|
|
||||||
background-color: var(--color-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(h1) {
|
|
||||||
font-size: var(--font-size-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(h2) {
|
|
||||||
font-size: var(--font-size-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(code) {
|
|
||||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
|
||||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
1
examples/astro/src/env.d.ts
vendored
Normal file
1
examples/astro/src/env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="astro/client" />
|
||||||
56
examples/astro/src/layouts/Layout.astro
Normal file
56
examples/astro/src/layouts/Layout.astro
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title } = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<title>{title}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<slot />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
|
||||||
|
--font-size-lg: clamp(1.2rem, 0.7vw + 1.2rem, 1.5rem);
|
||||||
|
--font-size-xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
|
||||||
|
|
||||||
|
--color-text: hsl(12, 5%, 4%);
|
||||||
|
--color-bg: hsl(10, 21%, 95%);
|
||||||
|
--color-border: hsl(17, 24%, 90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: system-ui, sans-serif;
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--color-text);
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(h1) {
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(h2) {
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(code) {
|
||||||
|
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||||
|
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,81 +1,52 @@
|
|||||||
---
|
---
|
||||||
import Layout from '../components/Layout.astro';
|
import Layout from '../layouts/Layout.astro';
|
||||||
|
import Card from '../components/Card.astro';
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Welcome to Astro.">
|
<Layout title="Welcome to Astro.">
|
||||||
<main>
|
<main>
|
||||||
<h1>Welcome to <span class="text-gradient">Astro</span></h1>
|
<h1>Welcome to <span class="text-gradient">Astro</span></h1>
|
||||||
<p class="instructions"><strong>Your first mission:</strong> tweak this message to try our hot module reloading. Check the <code>src/pages</code> directory!</p>
|
<p class="instructions">
|
||||||
|
Check out the <code>src/pages</code> directory to get started.<br />
|
||||||
|
<strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above.
|
||||||
|
</p>
|
||||||
<ul role="list" class="link-card-grid">
|
<ul role="list" class="link-card-grid">
|
||||||
<li class="link-card">
|
<Card
|
||||||
<a href="https://astro.build/integrations/">
|
href="https://docs.astro.build/"
|
||||||
<h2>Integrations <span>→</span></h2>
|
title="Documentation"
|
||||||
<p>Add component frameworks, Tailwind, Partytown, and more!</p>
|
body="Learn how Astro works and explore the official API docs."
|
||||||
</a>
|
/>
|
||||||
</li>
|
<Card
|
||||||
<li class="link-card">
|
href="https://astro.build/integrations/"
|
||||||
<a href="https://astro.build/themes/">
|
title="Integrations"
|
||||||
<h2>Themes <span>→</span></h2>
|
body="Supercharge your project with new frameworks and libraries."
|
||||||
<p>Explore a galaxy of community-built starters.</p>
|
/>
|
||||||
</a>
|
<Card
|
||||||
</li>
|
href="https://astro.build/themes/"
|
||||||
<li class="link-card">
|
title="Themes"
|
||||||
<a href="https://docs.astro.build/">
|
body="Explore a galaxy of community-built starter themes."
|
||||||
<h2>Docs <span>→</span></h2>
|
/>
|
||||||
<p>Learn our complete feature set and explore the API.</p>
|
<Card
|
||||||
</a>
|
href="https://astro.build/chat/"
|
||||||
</li>
|
title="Chat"
|
||||||
<li class="link-card">
|
body="Come say hi to our amazing Discord community. ❤️"
|
||||||
<a href="https://astro.build/chat/">
|
/>
|
||||||
<h2>Chat <span>→</span></h2>
|
|
||||||
<p>
|
|
||||||
Ask, contribute, and have fun on our community Discord
|
|
||||||
<svg
|
|
||||||
class="heart"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<title>heart</title>
|
|
||||||
<path d="M256 448l-30.164-27.211C118.718 322.442 48 258.61 48 179.095 48 114.221 97.918 64 162.4 64c36.399 0 70.717 16.742 93.6 43.947C278.882 80.742 313.199 64 349.6 64 414.082 64 464 114.221 464 179.095c0 79.516-70.719 143.348-177.836 241.694L256 448z" />
|
|
||||||
</svg>
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--color-border: hsl(17, 24%, 90%);
|
--astro-gradient: linear-gradient(0deg, #4f39fa, #da62c4);
|
||||||
--astro-gradient: linear-gradient(0deg,#4F39FA, #DA62C4);
|
|
||||||
--link-gradient: linear-gradient(45deg, #4F39FA, #DA62C4 30%, var(--color-border) 60%);
|
|
||||||
--night-sky-gradient: linear-gradient(0deg, #392362 -33%, #431f69 10%, #30216b 50%, #1f1638 100%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h1 {
|
||||||
margin: 0;
|
margin: 2rem 0;
|
||||||
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 span {
|
|
||||||
display: inline-block;
|
|
||||||
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-size: 0.875em;
|
|
||||||
border: 0.1em solid var(--color-border);
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 0.15em 0.25em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
max-width: 60ch;
|
max-width: 60ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +54,7 @@ import Layout from '../components/Layout.astro';
|
|||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
background-image: var(--astro-gradient);
|
background-image: var(--astro-gradient);
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
background-size: 100% 200%;
|
background-size: 100% 200%;
|
||||||
background-position-y: 100%;
|
background-position-y: 100%;
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
@@ -91,7 +62,8 @@ import Layout from '../components/Layout.astro';
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% {
|
0%,
|
||||||
|
100% {
|
||||||
background-position-y: 0%;
|
background-position-y: 0%;
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
@@ -100,75 +72,25 @@ import Layout from '../components/Layout.astro';
|
|||||||
}
|
}
|
||||||
|
|
||||||
.instructions {
|
.instructions {
|
||||||
line-height: 1.8;
|
line-height: 1.6;
|
||||||
margin-bottom: 2rem;
|
margin: 1rem 0;
|
||||||
background-image: var(--night-sky-gradient);
|
background: #4f39fa;
|
||||||
padding: 1.5rem;
|
padding: 1rem;
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
color: var(--color-bg);
|
color: var(--color-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.instructions code {
|
||||||
|
font-size: 0.875em;
|
||||||
|
border: 0.1em solid var(--color-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.15em 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.link-card-grid {
|
.link-card-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-card {
|
|
||||||
list-style: none;
|
|
||||||
display: flex;
|
|
||||||
padding: 0.15rem;
|
|
||||||
background-image: var(--link-gradient);
|
|
||||||
background-size: 400%;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
background-position: 100%;
|
|
||||||
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-card > a {
|
|
||||||
width: 100%;
|
|
||||||
text-decoration: none;
|
|
||||||
line-height: 1.4;
|
|
||||||
padding: 1em 1.3em;
|
|
||||||
border-radius: 0.35rem;
|
|
||||||
color: var(--text-color);
|
|
||||||
background-color: white;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-card:is(:hover, :focus-within) {
|
|
||||||
background-position: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-card:is(:hover, :focus-within) h2 {
|
|
||||||
color: #4F39FA;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-card:is(:hover, :focus-within) h2 span {
|
|
||||||
transform: translateX(2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.heart {
|
|
||||||
display: inline-block;
|
|
||||||
color: #DA62C4;
|
|
||||||
animation: heartbeat 3s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes heartbeat {
|
|
||||||
0%,
|
|
||||||
50%,
|
|
||||||
100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
5% {
|
|
||||||
transform: scale(1.125);
|
|
||||||
}
|
|
||||||
10% {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
15% {
|
|
||||||
transform: scale(1.25);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
// Enable top-level await, and other modern ESM features.
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "ESNext",
|
|
||||||
// Enable node-style module resolution, for things like npm package imports.
|
|
||||||
"moduleResolution": "node",
|
|
||||||
// Enable JSON imports.
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
// Enable stricter transpilation for better output.
|
|
||||||
"isolatedModules": true,
|
|
||||||
// Add type definitions for our Vite runtime.
|
|
||||||
"types": ["vite/client"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env.local
|
.env*.local
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|||||||
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,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
@@ -7,12 +9,8 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "12.1.4",
|
"next": "13.0.1",
|
||||||
"react": "18.0.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.0.0"
|
"react-dom": "18.2.0"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"eslint": "8.12.0",
|
|
||||||
"eslint-config-next": "12.1.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ export default function Home() {
|
|||||||
|
|
||||||
<a
|
<a
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
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}
|
className={styles.card}
|
||||||
>
|
>
|
||||||
<h2>Deploy →</h2>
|
<h2>Deploy →</h2>
|
||||||
|
|||||||
@@ -114,3 +114,16 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.card,
|
||||||
|
.footer {
|
||||||
|
border-color: #222;
|
||||||
|
}
|
||||||
|
.code {
|
||||||
|
background: #111;
|
||||||
|
}
|
||||||
|
.logo img {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,3 +14,13 @@ a {
|
|||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
html {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
color: white;
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,21 +8,9 @@ Everything you need to build a Svelte project, powered by [`create-svelte`](http
|
|||||||
|
|
||||||
_Live Example: https://sveltekit-template.vercel.app_
|
_Live Example: https://sveltekit-template.vercel.app_
|
||||||
|
|
||||||
## Creating a project
|
|
||||||
|
|
||||||
If you're seeing this, you've probably already done this step. Congrats!
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# create a new project in the current directory
|
|
||||||
npm init svelte
|
|
||||||
|
|
||||||
# create a new project in my-app
|
|
||||||
npm init svelte my-app
|
|
||||||
```
|
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
Once you've created a project and installed dependencies with `pnpm install`, start a development server:
|
Once you've installed dependencies with `pnpm install`, start a development server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run dev
|
pnpm run dev
|
||||||
|
|||||||
@@ -10,4 +10,8 @@
|
|||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true
|
"strict": true
|
||||||
}
|
}
|
||||||
|
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
|
||||||
|
//
|
||||||
|
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||||
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "svelte-kit dev",
|
"dev": "vite dev",
|
||||||
"build": "svelte-kit build",
|
"build": "vite build",
|
||||||
"package": "svelte-kit package",
|
"package": "svelte-kit package",
|
||||||
"preview": "svelte-kit preview",
|
"preview": "vite preview",
|
||||||
"prepare": "svelte-kit sync",
|
|
||||||
"check": "svelte-check --tsconfig ./jsconfig.json",
|
"check": "svelte-check --tsconfig ./jsconfig.json",
|
||||||
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
|
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
|
||||||
"lint": "prettier --check --plugin-search-dir=. .",
|
"lint": "prettier --check .",
|
||||||
"format": "prettier --write --plugin-search-dir=. ."
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "1.0.0-next.50",
|
"@sveltejs/adapter-auto": "next",
|
||||||
"@sveltejs/kit": "1.0.0-next.347",
|
"@sveltejs/kit": "next",
|
||||||
"@types/cookie": "^0.4.1",
|
"@types/cookie": "^0.5.1",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.6.2",
|
||||||
"prettier-plugin-svelte": "^2.5.0",
|
"prettier-plugin-svelte": "^2.7.0",
|
||||||
"svelte": "^3.46.0",
|
"svelte": "^3.46.0",
|
||||||
"svelte-check": "^2.2.6",
|
"svelte-check": "^2.7.1",
|
||||||
"typescript": "~4.6.2"
|
"typescript": "^4.7.4",
|
||||||
|
"vite": "^3.0.8"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
1458
examples/sveltekit/pnpm-lock.yaml
generated
Normal file
1458
examples/sveltekit/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
7
examples/sveltekit/src/app.d.ts
vendored
7
examples/sveltekit/src/app.d.ts
vendored
@@ -1,7 +1,6 @@
|
|||||||
/// <reference types="@sveltejs/kit" />
|
|
||||||
|
|
||||||
// See https://kit.svelte.dev/docs/types#app
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
|
// and what to do when importing types
|
||||||
declare namespace App {
|
declare namespace App {
|
||||||
interface Locals {
|
interface Locals {
|
||||||
userid: string;
|
userid: string;
|
||||||
@@ -9,7 +8,7 @@ declare namespace App {
|
|||||||
|
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
|
|
||||||
// interface Session {}
|
// interface PrivateEnv {}
|
||||||
|
|
||||||
// interface Stuff {}
|
// interface PublicEnv {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,107 +1,107 @@
|
|||||||
<script>
|
<script>
|
||||||
import { spring } from 'svelte/motion';
|
import { spring } from 'svelte/motion';
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
const displayed_count = spring();
|
const displayed_count = spring();
|
||||||
$: displayed_count.set(count);
|
$: displayed_count.set(count);
|
||||||
$: offset = modulo($displayed_count, 1);
|
$: offset = modulo($displayed_count, 1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} n
|
* @param {number} n
|
||||||
* @param {number} m
|
* @param {number} m
|
||||||
*/
|
*/
|
||||||
function modulo(n, m) {
|
function modulo(n, m) {
|
||||||
// handle negative numbers
|
// handle negative numbers
|
||||||
return ((n % m) + m) % m;
|
return ((n % m) + m) % m;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="counter">
|
<div class="counter">
|
||||||
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
||||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||||
<path d="M0,0.5 L1,0.5" />
|
<path d="M0,0.5 L1,0.5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="counter-viewport">
|
<div class="counter-viewport">
|
||||||
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
||||||
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
||||||
<strong>{Math.floor($displayed_count)}</strong>
|
<strong>{Math.floor($displayed_count)}</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
||||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||||
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.counter {
|
.counter {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter button {
|
.counter button {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 0;
|
border: 0;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter button:hover {
|
.counter button:hover {
|
||||||
background-color: var(--secondary-color);
|
background-color: var(--secondary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
height: 25%;
|
height: 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
path {
|
path {
|
||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
stroke-width: 2px;
|
stroke-width: 2px;
|
||||||
stroke: var(--text-color);
|
stroke: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter-viewport {
|
.counter-viewport {
|
||||||
width: 8em;
|
width: 8em;
|
||||||
height: 4em;
|
height: 4em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter-viewport strong {
|
.counter-viewport strong {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
font-size: 4rem;
|
font-size: 4rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter-digits {
|
.counter-digits {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
top: -100%;
|
top: -100%;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ import { invalidate } from '$app/navigation';
|
|||||||
export function enhance(form, { pending, error, result } = {}) {
|
export function enhance(form, { pending, error, result } = {}) {
|
||||||
let current_token;
|
let current_token;
|
||||||
|
|
||||||
/** @param {SubmitEvent} e */
|
/** @param {SubmitEvent} event */
|
||||||
async function handle_submit(e) {
|
async function handle_submit(event) {
|
||||||
const token = (current_token = {});
|
const token = (current_token = {});
|
||||||
|
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const data = new FormData(form);
|
const data = new FormData(form);
|
||||||
|
|
||||||
@@ -63,11 +63,11 @@ export function enhance(form, { pending, error, result } = {}) {
|
|||||||
} else {
|
} else {
|
||||||
console.error(await response.text());
|
console.error(await response.text());
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
if (error && e instanceof Error) {
|
if (error && err instanceof Error) {
|
||||||
error({ data, form, error: e, response: null });
|
error({ data, form, error: err, response: null });
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,124 +1,124 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import logo from './svelte-logo.svg';
|
import logo from './svelte-logo.svg';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div class="corner">
|
<div class="corner">
|
||||||
<a href="https://kit.svelte.dev">
|
<a href="https://kit.svelte.dev">
|
||||||
<img src={logo} alt="SvelteKit" />
|
<img src={logo} alt="SvelteKit" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||||
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
||||||
</svg>
|
</svg>
|
||||||
<ul>
|
<ul>
|
||||||
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
||||||
<li class:active={$page.url.pathname === '/about'}>
|
<li class:active={$page.url.pathname === '/about'}>
|
||||||
<a sveltekit:prefetch href="/about">About</a>
|
<a sveltekit:prefetch href="/about">About</a>
|
||||||
</li>
|
</li>
|
||||||
<li class:active={$page.url.pathname === '/todos'}>
|
<li class:active={$page.url.pathname === '/todos'}>
|
||||||
<a sveltekit:prefetch href="/todos">Todos</a>
|
<a sveltekit:prefetch href="/todos">Todos</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||||
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
|
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
|
||||||
</svg>
|
</svg>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="corner">
|
<div class="corner">
|
||||||
<!-- TODO put something else here? github link? -->
|
<!-- TODO put something else here? github link? -->
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.corner {
|
.corner {
|
||||||
width: 3em;
|
width: 3em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.corner a {
|
.corner a {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.corner img {
|
.corner img {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
height: 2em;
|
height: 2em;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
--background: rgba(255, 255, 255, 0.7);
|
--background: rgba(255, 255, 255, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
path {
|
path {
|
||||||
fill: var(--background);
|
fill: var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
li.active::before {
|
li.active::before {
|
||||||
--size: 6px;
|
--size: 6px;
|
||||||
content: '';
|
content: '';
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: calc(50% - var(--size));
|
left: calc(50% - var(--size));
|
||||||
border: var(--size) solid transparent;
|
border: var(--size) solid transparent;
|
||||||
border-top: var(--size) solid var(--accent-color);
|
border-top: var(--size) solid var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
nav a {
|
nav a {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 1em;
|
padding: 0 1em;
|
||||||
color: var(--heading-color);
|
color: var(--heading-color);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: color 0.2s linear;
|
transition: color 0.2s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
58
examples/sveltekit/src/routes/+layout.svelte
Normal file
58
examples/sveltekit/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<script>
|
||||||
|
import Header from '$lib/header/Header.svelte';
|
||||||
|
import { webVitals } from '$lib/vitals';
|
||||||
|
import { browser } from '$app/env';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import '../app.css';
|
||||||
|
|
||||||
|
let analyticsId = import.meta.env.VERCEL_ANALYTICS_ID;
|
||||||
|
|
||||||
|
$: if (browser && analyticsId) {
|
||||||
|
webVitals({
|
||||||
|
path: $page.url.pathname,
|
||||||
|
params: $page.params,
|
||||||
|
analyticsId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
main {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1024px;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 480px) {
|
||||||
|
footer {
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1
examples/sveltekit/src/routes/+page.js
Normal file
1
examples/sveltekit/src/routes/+page.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const prerender = true;
|
||||||
57
examples/sveltekit/src/routes/+page.svelte
Normal file
57
examples/sveltekit/src/routes/+page.svelte
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<script>
|
||||||
|
import Counter from '$lib/Counter.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Home</title>
|
||||||
|
<meta name="description" content="Svelte demo app" />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h1>
|
||||||
|
<span class="welcome">
|
||||||
|
<picture>
|
||||||
|
<source srcset="svelte-welcome.webp" type="image/webp" />
|
||||||
|
<img src="svelte-welcome.png" alt="Welcome" />
|
||||||
|
</picture>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
to your new<br />SvelteKit app
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
try editing <strong>src/routes/index.svelte</strong>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<Counter />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
padding: 0 0 calc(100% * 495 / 2048) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome img {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Header from '$lib/header/Header.svelte';
|
|
||||||
import { webVitals } from '$lib/vitals';
|
|
||||||
import { browser } from '$app/env';
|
|
||||||
import { page } from '$app/stores';
|
|
||||||
import '../app.css';
|
|
||||||
|
|
||||||
let analyticsId = import.meta.env.VERCEL_ANALYTICS_ID;
|
|
||||||
|
|
||||||
$: if (browser && analyticsId) {
|
|
||||||
webVitals({
|
|
||||||
path: $page.url.pathname,
|
|
||||||
params: $page.params,
|
|
||||||
analyticsId
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Header />
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<slot />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
main {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 1rem;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1024px;
|
|
||||||
margin: 0 auto;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer a {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 480px) {
|
|
||||||
footer {
|
|
||||||
padding: 40px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<script context="module">
|
|
||||||
import { browser, dev } from '$app/env';
|
|
||||||
|
|
||||||
// we don't need any JS on this page, though we'll load
|
|
||||||
// it in dev so that we get hot module replacement...
|
|
||||||
export const hydrate = dev;
|
|
||||||
|
|
||||||
// ...but if the client-side router is already loaded
|
|
||||||
// (i.e. we came here from elsewhere in the app), use it
|
|
||||||
export const router = browser;
|
|
||||||
|
|
||||||
// since there's no dynamic data here, we can prerender
|
|
||||||
// it so that it gets served as a static asset in prod
|
|
||||||
export const prerender = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>About</title>
|
|
||||||
<meta name="description" content="About this app" />
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<h1>About this app</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
|
|
||||||
following into your command line and following the prompts:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<pre>npm init svelte</pre>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The page you're looking at is purely static HTML, with no client-side interactivity needed.
|
|
||||||
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
|
|
||||||
the devtools network panel and reloading.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
|
|
||||||
it with JavaScript disabled!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.content {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--column-width);
|
|
||||||
margin: var(--column-margin-top) auto 0 auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
13
examples/sveltekit/src/routes/about/+page.js
Normal file
13
examples/sveltekit/src/routes/about/+page.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { browser, dev } from '$app/env';
|
||||||
|
|
||||||
|
// we don't need any JS on this page, though we'll load
|
||||||
|
// it in dev so that we get hot module replacement...
|
||||||
|
export const hydrate = dev;
|
||||||
|
|
||||||
|
// ...but if the client-side router is already loaded
|
||||||
|
// (i.e. we came here from elsewhere in the app), use it
|
||||||
|
export const router = browser;
|
||||||
|
|
||||||
|
// since there's no dynamic data here, we can prerender
|
||||||
|
// it so that it gets served as a static asset in prod
|
||||||
|
export const prerender = true;
|
||||||
34
examples/sveltekit/src/routes/about/+page.svelte
Normal file
34
examples/sveltekit/src/routes/about/+page.svelte
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<svelte:head>
|
||||||
|
<title>About</title>
|
||||||
|
<meta name="description" content="About this app" />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<h1>About this app</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
|
||||||
|
following into your command line and following the prompts:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>npm create svelte@latest</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The page you're looking at is purely static HTML, with no client-side interactivity needed.
|
||||||
|
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
|
||||||
|
the devtools network panel and reloading.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
|
||||||
|
it with JavaScript disabled!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--column-width);
|
||||||
|
margin: var(--column-margin-top) auto 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<script context="module">
|
|
||||||
export const prerender = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Counter from '$lib/Counter.svelte';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>Home</title>
|
|
||||||
<meta name="description" content="Svelte demo app" />
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h1>
|
|
||||||
<div class="welcome">
|
|
||||||
<picture>
|
|
||||||
<source srcset="svelte-welcome.webp" type="image/webp" />
|
|
||||||
<img src="svelte-welcome.png" alt="Welcome" />
|
|
||||||
</picture>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
to your new<br />SvelteKit app
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<h2>
|
|
||||||
try editing <strong>src/routes/index.svelte</strong>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<Counter />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
section {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.welcome {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 0;
|
|
||||||
padding: 0 0 calc(100% * 495 / 2048) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.welcome img {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
top: 0;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
62
examples/sveltekit/src/routes/todos/+page.server.js
Normal file
62
examples/sveltekit/src/routes/todos/+page.server.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import { api } from './api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {{
|
||||||
|
* uid: string;
|
||||||
|
* created_at: Date;
|
||||||
|
* text: string;
|
||||||
|
* done: boolean;
|
||||||
|
* pending_delete: boolean;
|
||||||
|
* }} Todo
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {import('./$types').PageServerLoad} */
|
||||||
|
export const load = async ({ locals }) => {
|
||||||
|
// locals.userid comes from src/hooks.js
|
||||||
|
const response = await api('GET', `todos/${locals.userid}`);
|
||||||
|
|
||||||
|
if (response.status === 404) {
|
||||||
|
// user hasn't created a todo list.
|
||||||
|
// start with an empty array
|
||||||
|
return {
|
||||||
|
/** @type {Todo[]} */
|
||||||
|
todos: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
return {
|
||||||
|
/** @type {Todo[]} */
|
||||||
|
todos: await response.json()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error(response.status);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {import('./$types').Action} */
|
||||||
|
export const POST = async ({ request, locals }) => {
|
||||||
|
const form = await request.formData();
|
||||||
|
|
||||||
|
await api('POST', `todos/${locals.userid}`, {
|
||||||
|
text: form.get('text')
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {import('./$types').Action} */
|
||||||
|
export const PATCH = async ({ request, locals }) => {
|
||||||
|
const form = await request.formData();
|
||||||
|
|
||||||
|
await api('PATCH', `todos/${locals.userid}/${form.get('uid')}`, {
|
||||||
|
text: form.has('text') ? form.get('text') : undefined,
|
||||||
|
done: form.has('done') ? !!form.get('done') : undefined
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {import('./$types').Action} */
|
||||||
|
export const DELETE = async ({ request, locals }) => {
|
||||||
|
const form = await request.formData();
|
||||||
|
|
||||||
|
await api('DELETE', `todos/${locals.userid}/${form.get('uid')}`);
|
||||||
|
};
|
||||||
180
examples/sveltekit/src/routes/todos/+page.svelte
Normal file
180
examples/sveltekit/src/routes/todos/+page.svelte
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<script>
|
||||||
|
import { enhance } from '$lib/form';
|
||||||
|
import { scale } from 'svelte/transition';
|
||||||
|
import { flip } from 'svelte/animate';
|
||||||
|
|
||||||
|
/** @type {import('./$types').PageData} */
|
||||||
|
export let data;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Todos</title>
|
||||||
|
<meta name="description" content="A todo list app" />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div class="todos">
|
||||||
|
<h1>Todos</h1>
|
||||||
|
|
||||||
|
<form
|
||||||
|
class="new"
|
||||||
|
action="/todos"
|
||||||
|
method="post"
|
||||||
|
use:enhance={{
|
||||||
|
result: async ({ form }) => {
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{#each data.todos as todo (todo.uid)}
|
||||||
|
<div
|
||||||
|
class="todo"
|
||||||
|
class:done={todo.done}
|
||||||
|
transition:scale|local={{ start: 0.7 }}
|
||||||
|
animate:flip={{ duration: 200 }}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
action="/todos?_method=PATCH"
|
||||||
|
method="post"
|
||||||
|
use:enhance={{
|
||||||
|
pending: ({ data }) => {
|
||||||
|
todo.done = !!data.get('done');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input type="hidden" name="uid" value={todo.uid} />
|
||||||
|
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
|
||||||
|
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
|
||||||
|
<input type="hidden" name="uid" value={todo.uid} />
|
||||||
|
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
|
||||||
|
<button class="save" aria-label="Save todo" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form
|
||||||
|
action="/todos?_method=DELETE"
|
||||||
|
method="post"
|
||||||
|
use:enhance={{
|
||||||
|
pending: () => (todo.pending_delete = true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input type="hidden" name="uid" value={todo.uid} />
|
||||||
|
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.todos {
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--column-width);
|
||||||
|
margin: var(--column-margin-top) auto 0 auto;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus-visible {
|
||||||
|
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid #ff3e00 !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new input {
|
||||||
|
font-size: 28px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5em 1em 0.3em 1em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2rem 1fr 2rem;
|
||||||
|
grid-gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
|
||||||
|
transform: translate(-1px, -1px);
|
||||||
|
transition: filter 0.2s, transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.done {
|
||||||
|
transform: none;
|
||||||
|
opacity: 0.4;
|
||||||
|
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
|
||||||
|
}
|
||||||
|
|
||||||
|
form.text {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.5em 2em 0.5em 0.8em;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo button {
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.toggle {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-size: 1em auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.done .toggle {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete:hover,
|
||||||
|
.delete:focus {
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
opacity: 0;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo input:focus + .save,
|
||||||
|
.save:focus {
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
This module is used by the /todos endpoint to
|
This module is used by the /todos endpoint to
|
||||||
make calls to api.svelte.dev, which stores todos
|
make calls to api.svelte.dev, which stores todos
|
||||||
for each user. The leading underscore indicates that this is
|
for each user.
|
||||||
a private module, _not_ an endpoint — visiting /todos/_api
|
|
||||||
will net you a 404 response.
|
|
||||||
|
|
||||||
(The data on the todo app will expire periodically; no
|
(The data on the todo app will expire periodically; no
|
||||||
guarantees are made. Don't use it to organise your life.)
|
guarantees are made. Don't use it to organise your life.)
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import { api } from './_api';
|
|
||||||
|
|
||||||
/** @type {import('./__types').RequestHandler} */
|
|
||||||
export const get = async ({ locals }) => {
|
|
||||||
// locals.userid comes from src/hooks.js
|
|
||||||
const response = await api('get', `todos/${locals.userid}`);
|
|
||||||
|
|
||||||
if (response.status === 404) {
|
|
||||||
// user hasn't created a todo list.
|
|
||||||
// start with an empty array
|
|
||||||
return {
|
|
||||||
body: {
|
|
||||||
todos: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
|
||||||
return {
|
|
||||||
body: {
|
|
||||||
todos: await response.json()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: response.status
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @type {import('./index').RequestHandler} */
|
|
||||||
export const post = async ({ request, locals }) => {
|
|
||||||
const form = await request.formData();
|
|
||||||
|
|
||||||
await api('post', `todos/${locals.userid}`, {
|
|
||||||
text: form.get('text')
|
|
||||||
});
|
|
||||||
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the user has JavaScript disabled, the URL will change to
|
|
||||||
// include the method override unless we redirect back to /todos
|
|
||||||
const redirect = {
|
|
||||||
status: 303,
|
|
||||||
headers: {
|
|
||||||
location: '/todos'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @type {import('./index').RequestHandler} */
|
|
||||||
export const patch = async ({ request, locals }) => {
|
|
||||||
const form = await request.formData();
|
|
||||||
|
|
||||||
await api('patch', `todos/${locals.userid}/${form.get('uid')}`, {
|
|
||||||
text: form.has('text') ? form.get('text') : undefined,
|
|
||||||
done: form.has('done') ? !!form.get('done') : undefined
|
|
||||||
});
|
|
||||||
|
|
||||||
return redirect;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @type {import('./index').RequestHandler} */
|
|
||||||
export const del = async ({ request, locals }) => {
|
|
||||||
const form = await request.formData();
|
|
||||||
|
|
||||||
await api('delete', `todos/${locals.userid}/${form.get('uid')}`);
|
|
||||||
|
|
||||||
return redirect;
|
|
||||||
};
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { enhance } from '$lib/form';
|
|
||||||
import { scale } from 'svelte/transition';
|
|
||||||
import { flip } from 'svelte/animate';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {{
|
|
||||||
* uid: string;
|
|
||||||
* created_at: Date;
|
|
||||||
* text: string;
|
|
||||||
* done: boolean;
|
|
||||||
* pending_delete: boolean;
|
|
||||||
* }} Todo
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @type {Todo[]} */
|
|
||||||
export let todos;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>Todos</title>
|
|
||||||
<meta name="description" content="A todo list app" />
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<div class="todos">
|
|
||||||
<h1>Todos</h1>
|
|
||||||
|
|
||||||
<form
|
|
||||||
class="new"
|
|
||||||
action="/todos"
|
|
||||||
method="post"
|
|
||||||
use:enhance={{
|
|
||||||
result: async ({ form }) => {
|
|
||||||
form.reset();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{#each todos as todo (todo.uid)}
|
|
||||||
<div
|
|
||||||
class="todo"
|
|
||||||
class:done={todo.done}
|
|
||||||
transition:scale|local={{ start: 0.7 }}
|
|
||||||
animate:flip={{ duration: 200 }}
|
|
||||||
>
|
|
||||||
<form
|
|
||||||
action="/todos?_method=PATCH"
|
|
||||||
method="post"
|
|
||||||
use:enhance={{
|
|
||||||
pending: ({ data }) => {
|
|
||||||
todo.done = !!data.get('done');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input type="hidden" name="uid" value={todo.uid} />
|
|
||||||
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
|
|
||||||
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
|
|
||||||
<input type="hidden" name="uid" value={todo.uid} />
|
|
||||||
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
|
|
||||||
<button class="save" aria-label="Save todo" />
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<form
|
|
||||||
action="/todos?_method=DELETE"
|
|
||||||
method="post"
|
|
||||||
use:enhance={{
|
|
||||||
pending: () => (todo.pending_delete = true)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input type="hidden" name="uid" value={todo.uid} />
|
|
||||||
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.todos {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--column-width);
|
|
||||||
margin: var(--column-margin-top) auto 0 auto;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
border: 1px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus-visible {
|
|
||||||
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
border: 1px solid #ff3e00 !important;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new input {
|
|
||||||
font-size: 28px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5em 1em 0.3em 1em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border-radius: 8px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 2rem 1fr 2rem;
|
|
||||||
grid-gap: 0.5rem;
|
|
||||||
align-items: center;
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
padding: 0.5rem;
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
|
|
||||||
transform: translate(-1px, -1px);
|
|
||||||
transition: filter 0.2s, transform 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.done {
|
|
||||||
transform: none;
|
|
||||||
opacity: 0.4;
|
|
||||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
|
|
||||||
}
|
|
||||||
|
|
||||||
form.text {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo input {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0.5em 2em 0.5em 0.8em;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo button {
|
|
||||||
width: 2em;
|
|
||||||
height: 2em;
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
background-position: 50% 50%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.toggle {
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
||||||
border-radius: 50%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-size: 1em auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.done .toggle {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
|
|
||||||
opacity: 0.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete:hover,
|
|
||||||
.delete:focus {
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.save {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
opacity: 0;
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo input:focus + .save,
|
|
||||||
.save:focus {
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -8,11 +8,6 @@ const config = {
|
|||||||
// Override http methods in the Todo forms
|
// Override http methods in the Todo forms
|
||||||
methodOverride: {
|
methodOverride: {
|
||||||
allowed: ['PATCH', 'DELETE']
|
allowed: ['PATCH', 'DELETE']
|
||||||
},
|
|
||||||
vite: {
|
|
||||||
define: {
|
|
||||||
'import.meta.env.VERCEL_ANALYTICS_ID': JSON.stringify(process.env.VERCEL_ANALYTICS_ID)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
11
examples/sveltekit/vite.config.js
Normal file
11
examples/sveltekit/vite.config.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
|
||||||
|
/** @type {import('vite').UserConfig} */
|
||||||
|
const config = {
|
||||||
|
plugins: [sveltekit()],
|
||||||
|
define: {
|
||||||
|
'import.meta.env.VERCEL_ANALYTICS_ID': JSON.stringify(process.env.VERCEL_ANALYTICS_ID)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -3,12 +3,16 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"packageManager": "yarn@1.22.19",
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"nohoist": [
|
"nohoist": [
|
||||||
"**/@types/**"
|
"**/@types/**",
|
||||||
|
"**/typedoc",
|
||||||
|
"**/typedoc-plugin-markdown",
|
||||||
|
"**/typedoc-plugin-mdn-links"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -31,7 +35,7 @@
|
|||||||
"prettier": "2.6.2",
|
"prettier": "2.6.2",
|
||||||
"ts-eager": "2.0.2",
|
"ts-eager": "2.0.2",
|
||||||
"ts-jest": "28.0.5",
|
"ts-jest": "28.0.5",
|
||||||
"turbo": "1.3.2-canary.1"
|
"turbo": "1.4.7"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lerna": "lerna",
|
"lerna": "lerna",
|
||||||
@@ -40,14 +44,14 @@
|
|||||||
"publish-canary": "git checkout main && git pull && lerna version prerelease --preid canary --message \"Publish Canary\" --exact",
|
"publish-canary": "git checkout main && git pull && lerna version prerelease --preid canary --message \"Publish Canary\" --exact",
|
||||||
"publish-from-github": "./utils/publish.sh",
|
"publish-from-github": "./utils/publish.sh",
|
||||||
"changelog": "node utils/changelog.js",
|
"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",
|
"vercel-build": "yarn build && yarn run pack && cd api && node -r ts-eager/register ./_lib/script/build.ts",
|
||||||
"pre-commit": "lint-staged",
|
"pre-commit": "lint-staged",
|
||||||
"test": "jest --rootDir=\"test\" --testPathPattern=\"\\.test.js\"",
|
"test": "jest --rootDir=\"test\" --testPathPattern=\"\\.test.js\"",
|
||||||
"test-unit": "yarn test && turbo run test-unit",
|
"test-unit": "yarn test && node utils/gen.js && turbo run test-unit",
|
||||||
"test-integration-cli": "turbo run test-integration-cli",
|
"test-integration-cli": "node utils/gen.js && turbo run test-integration-cli",
|
||||||
"test-integration-once": "turbo run test-integration-once",
|
"test-integration-once": "node utils/gen.js && turbo run test-integration-once",
|
||||||
"test-integration-dev": "turbo run test-integration-dev",
|
"test-integration-dev": "node utils/gen.js && turbo run test-integration-dev",
|
||||||
"lint": "eslint . --ext .ts,.js",
|
"lint": "eslint . --ext .ts,.js",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"pack": "cd utils && node -r ts-eager/register ./pack.ts"
|
"pack": "cd utils && node -r ts-eager/register ./pack.ts"
|
||||||
@@ -99,6 +103,7 @@
|
|||||||
"selector": "MemberExpression > Identifier[name='substr']"
|
"selector": "MemberExpression > Identifier[name='substr']"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"no-dupe-keys": 2,
|
||||||
"require-atomic-updates": 0,
|
"require-atomic-updates": 0,
|
||||||
"@typescript-eslint/ban-ts-comment": 0,
|
"@typescript-eslint/ban-ts-comment": 0,
|
||||||
"@typescript-eslint/camelcase": 0,
|
"@typescript-eslint/camelcase": 0,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/build-utils",
|
"name": "@vercel/build-utils",
|
||||||
"version": "5.2.0",
|
"version": "5.5.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.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[]) {
|
export default function debug(message: string, ...additional: any[]) {
|
||||||
if (getPlatformEnv('BUILDER_DEBUG')) {
|
if (getPlatformEnv('BUILDER_DEBUG')) {
|
||||||
console.log(message, ...additional);
|
console.log(message, ...additional);
|
||||||
|
} else if (process.env.VERCEL_DEBUG_PREFIX) {
|
||||||
|
console.log(`${process.env.VERCEL_DEBUG_PREFIX}${message}`, ...additional);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ export class EdgeFunction {
|
|||||||
*/
|
*/
|
||||||
assets?: { name: string; path: string }[];
|
assets?: { name: string; path: string }[];
|
||||||
|
|
||||||
|
/** The regions where the edge function will be executed on */
|
||||||
|
regions?: string | string[];
|
||||||
|
|
||||||
constructor(params: Omit<EdgeFunction, 'type'>) {
|
constructor(params: Omit<EdgeFunction, 'type'>) {
|
||||||
this.type = 'EdgeFunction';
|
this.type = 'EdgeFunction';
|
||||||
this.name = params.name;
|
this.name = params.name;
|
||||||
@@ -46,5 +49,6 @@ export class EdgeFunction {
|
|||||||
this.files = params.files;
|
this.files = params.files;
|
||||||
this.envVarsInUse = params.envVarsInUse;
|
this.envVarsInUse = params.envVarsInUse;
|
||||||
this.assets = params.assets;
|
this.assets = params.assets;
|
||||||
|
this.regions = params.regions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,14 +61,14 @@ export function getPrettyError(obj: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new NowBuildError({
|
return new NowBuildError({
|
||||||
code: 'DEV_VALIDATE_CONFIG',
|
code: 'INVALID_VERCEL_CONFIG',
|
||||||
message: message,
|
message: message,
|
||||||
link: prop ? `${docsUrl}#project/${prop.toLowerCase()}` : docsUrl,
|
link: prop ? `${docsUrl}#project/${prop.toLowerCase()}` : docsUrl,
|
||||||
action: 'View Documentation',
|
action: 'View Documentation',
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return new NowBuildError({
|
return new NowBuildError({
|
||||||
code: 'DEV_VALIDATE_CONFIG',
|
code: 'INVALID_VERCEL_CONFIG',
|
||||||
message: `Failed to validate configuration.`,
|
message: `Failed to validate configuration.`,
|
||||||
link: docsUrl,
|
link: docsUrl,
|
||||||
action: 'View Documentation',
|
action: 'View Documentation',
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ import path from 'path';
|
|||||||
import Sema from 'async-sema';
|
import Sema from 'async-sema';
|
||||||
import { FileBase } from './types';
|
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 {
|
interface FileFsRefOptions {
|
||||||
mode?: number;
|
mode?: number;
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ interface FileRefOptions {
|
|||||||
mutable?: boolean;
|
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 {
|
class BailableError extends Error {
|
||||||
public bail: boolean;
|
public bail: boolean;
|
||||||
|
|||||||
@@ -8,17 +8,13 @@ import FileFsRef from '../file-fs-ref';
|
|||||||
|
|
||||||
export type GlobOptions = vanillaGlob_.IOptions;
|
export type GlobOptions = vanillaGlob_.IOptions;
|
||||||
|
|
||||||
interface FsFiles {
|
|
||||||
[filePath: string]: FileFsRef;
|
|
||||||
}
|
|
||||||
|
|
||||||
const vanillaGlob = promisify(vanillaGlob_);
|
const vanillaGlob = promisify(vanillaGlob_);
|
||||||
|
|
||||||
export default async function glob(
|
export default async function glob(
|
||||||
pattern: string,
|
pattern: string,
|
||||||
opts: GlobOptions | string,
|
opts: GlobOptions | string,
|
||||||
mountpoint?: string
|
mountpoint?: string
|
||||||
): Promise<FsFiles> {
|
): Promise<Record<string, FileFsRef>> {
|
||||||
let options: GlobOptions;
|
let options: GlobOptions;
|
||||||
if (typeof opts === 'string') {
|
if (typeof opts === 'string') {
|
||||||
options = { cwd: opts };
|
options = { cwd: opts };
|
||||||
@@ -36,10 +32,11 @@ export default async function glob(
|
|||||||
throw new Error(`basePath/cwd must be an absolute path (${options.cwd})`);
|
throw new Error(`basePath/cwd must be an absolute path (${options.cwd})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const results: FsFiles = {};
|
const results: Record<string, FileFsRef> = {};
|
||||||
|
const statCache: Record<string, Stats> = {};
|
||||||
|
|
||||||
options.symlinks = {};
|
options.symlinks = {};
|
||||||
options.statCache = {};
|
options.statCache = statCache;
|
||||||
options.stat = true;
|
options.stat = true;
|
||||||
options.dot = true;
|
options.dot = true;
|
||||||
|
|
||||||
@@ -47,7 +44,7 @@ export default async function glob(
|
|||||||
|
|
||||||
for (const relativePath of files) {
|
for (const relativePath of files) {
|
||||||
const fsPath = normalizePath(path.join(options.cwd, relativePath));
|
const fsPath = normalizePath(path.join(options.cwd, relativePath));
|
||||||
let stat: Stats = options.statCache[fsPath] as Stats;
|
let stat = statCache[fsPath];
|
||||||
assert(
|
assert(
|
||||||
stat,
|
stat,
|
||||||
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`
|
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`
|
||||||
|
|||||||
@@ -3,28 +3,37 @@ import { NodeVersion } from '../types';
|
|||||||
import { NowBuildError } from '../errors';
|
import { NowBuildError } from '../errors';
|
||||||
import debug from '../debug';
|
import debug from '../debug';
|
||||||
|
|
||||||
const allOptions = [
|
function getOptions() {
|
||||||
{ major: 16, range: '16.x', runtime: 'nodejs16.x' },
|
const options = [
|
||||||
{ major: 14, range: '14.x', runtime: 'nodejs14.x' },
|
{ major: 16, range: '16.x', runtime: 'nodejs16.x' },
|
||||||
{
|
{ major: 14, range: '14.x', runtime: 'nodejs14.x' },
|
||||||
major: 12,
|
{
|
||||||
range: '12.x',
|
major: 12,
|
||||||
runtime: 'nodejs12.x',
|
range: '12.x',
|
||||||
discontinueDate: new Date('2022-08-09'),
|
runtime: 'nodejs12.x',
|
||||||
},
|
discontinueDate: new Date('2022-10-03'),
|
||||||
{
|
},
|
||||||
major: 10,
|
{
|
||||||
range: '10.x',
|
major: 10,
|
||||||
runtime: 'nodejs10.x',
|
range: '10.x',
|
||||||
discontinueDate: new Date('2021-04-20'),
|
runtime: 'nodejs10.x',
|
||||||
},
|
discontinueDate: new Date('2021-04-20'),
|
||||||
{
|
},
|
||||||
major: 8,
|
{
|
||||||
range: '8.10.x',
|
major: 8,
|
||||||
runtime: 'nodejs8.10',
|
range: '8.10.x',
|
||||||
discontinueDate: new Date('2020-01-06'),
|
runtime: 'nodejs8.10',
|
||||||
},
|
discontinueDate: new Date('2020-01-06'),
|
||||||
] as const;
|
},
|
||||||
|
] 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) {
|
function getHint(isAuto = false) {
|
||||||
const { major, range } = getLatestNodeVersion();
|
const { major, range } = getLatestNodeVersion();
|
||||||
@@ -34,11 +43,11 @@ function getHint(isAuto = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getLatestNodeVersion() {
|
export function getLatestNodeVersion() {
|
||||||
return allOptions[0];
|
return getOptions()[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDiscontinuedNodeVersions(): NodeVersion[] {
|
export function getDiscontinuedNodeVersions(): NodeVersion[] {
|
||||||
return allOptions.filter(isDiscontinued);
|
return getOptions().filter(isDiscontinued);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSupportedNodeVersion(
|
export async function getSupportedNodeVersion(
|
||||||
@@ -50,7 +59,7 @@ export async function getSupportedNodeVersion(
|
|||||||
if (engineRange) {
|
if (engineRange) {
|
||||||
const found =
|
const found =
|
||||||
validRange(engineRange) &&
|
validRange(engineRange) &&
|
||||||
allOptions.some(o => {
|
getOptions().some(o => {
|
||||||
// the array is already in order so return the first
|
// the array is already in order so return the first
|
||||||
// match which will be the newest version of node
|
// match which will be the newest version of node
|
||||||
selection = o;
|
selection = o;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { NowBuildError } from '../errors';
|
|||||||
import { Meta, PackageJson, NodeVersion, Config } from '../types';
|
import { Meta, PackageJson, NodeVersion, Config } from '../types';
|
||||||
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
|
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
|
||||||
import { readConfigFile } from './read-config-file';
|
import { readConfigFile } from './read-config-file';
|
||||||
|
import { cloneEnv } from '../clone-env';
|
||||||
|
|
||||||
// Only allow one `runNpmInstall()` invocation to run concurrently
|
// Only allow one `runNpmInstall()` invocation to run concurrently
|
||||||
const runNpmInstallSema = new Sema(1);
|
const runNpmInstallSema = new Sema(1);
|
||||||
@@ -23,8 +24,7 @@ export interface ScanParentDirsResult {
|
|||||||
*/
|
*/
|
||||||
cliType: CliType;
|
cliType: CliType;
|
||||||
/**
|
/**
|
||||||
* The file path of found `package.json` file, or `undefined` if none was
|
* The file path of found `package.json` file, or `undefined` if not found.
|
||||||
* found.
|
|
||||||
*/
|
*/
|
||||||
packageJsonPath?: string;
|
packageJsonPath?: string;
|
||||||
/**
|
/**
|
||||||
@@ -33,8 +33,13 @@ export interface ScanParentDirsResult {
|
|||||||
*/
|
*/
|
||||||
packageJson?: PackageJson;
|
packageJson?: PackageJson;
|
||||||
/**
|
/**
|
||||||
* The `lockfileVersion` number from the `package-lock.json` file,
|
* The file path of the lockfile (`yarn.lock`, `package-lock.json`, or `pnpm-lock.yaml`)
|
||||||
* when present.
|
* or `undefined` if not found.
|
||||||
|
*/
|
||||||
|
lockfilePath?: string;
|
||||||
|
/**
|
||||||
|
* The `lockfileVersion` number from lockfile (`package-lock.json` or `pnpm-lock.yaml`),
|
||||||
|
* or `undefined` if not found.
|
||||||
*/
|
*/
|
||||||
lockfileVersion?: number;
|
lockfileVersion?: number;
|
||||||
}
|
}
|
||||||
@@ -178,25 +183,9 @@ export async function getNodeBinPath({
|
|||||||
}: {
|
}: {
|
||||||
cwd: string;
|
cwd: string;
|
||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
const { code, stdout, stderr } = await execAsync('npm', ['bin'], {
|
const { lockfilePath } = await scanParentDirs(cwd);
|
||||||
cwd,
|
const dir = path.dirname(lockfilePath || cwd);
|
||||||
prettyCommand: 'npm bin',
|
return path.join(dir, 'node_modules', '.bin');
|
||||||
|
|
||||||
// in some rare cases, we saw `npm bin` exit with a non-0 code, but still
|
|
||||||
// output the right bin path, so we ignore the exit code
|
|
||||||
ignoreNon0Exit: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const nodeBinPath = stdout.trim();
|
|
||||||
|
|
||||||
if (path.isAbsolute(nodeBinPath)) {
|
|
||||||
return nodeBinPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NowBuildError({
|
|
||||||
code: `BUILD_UTILS_GET_NODE_BIN_PATH`,
|
|
||||||
message: `Running \`npm bin\` failed to return a valid bin path (code=${code}, stdout=${stdout}, stderr=${stderr})`,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function chmodPlusX(fsPath: string) {
|
async function chmodPlusX(fsPath: string) {
|
||||||
@@ -229,7 +218,7 @@ export function getSpawnOptions(
|
|||||||
nodeVersion: NodeVersion
|
nodeVersion: NodeVersion
|
||||||
): SpawnOptions {
|
): SpawnOptions {
|
||||||
const opts = {
|
const opts = {
|
||||||
env: { ...process.env },
|
env: cloneEnv(process.env),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!meta.isDev) {
|
if (!meta.isDev) {
|
||||||
@@ -262,33 +251,24 @@ export async function getNodeVersion(
|
|||||||
meta: Meta = {}
|
meta: Meta = {}
|
||||||
): Promise<NodeVersion> {
|
): Promise<NodeVersion> {
|
||||||
const latest = getLatestNodeVersion();
|
const latest = getLatestNodeVersion();
|
||||||
if (meta && meta.isDev) {
|
if (meta.isDev) {
|
||||||
// Use the system-installed version of `node` in PATH for `vercel dev`
|
// Use the system-installed version of `node` in PATH for `vercel dev`
|
||||||
return { ...latest, runtime: 'nodejs' };
|
return { ...latest, runtime: 'nodejs' };
|
||||||
}
|
}
|
||||||
const { packageJson } = await scanParentDirs(destPath, true);
|
const { packageJson } = await scanParentDirs(destPath, true);
|
||||||
let { nodeVersion } = config;
|
let { nodeVersion } = config;
|
||||||
let isAuto = true;
|
let isAuto = true;
|
||||||
if (packageJson && packageJson.engines && packageJson.engines.node) {
|
if (packageJson?.engines?.node) {
|
||||||
const { node } = packageJson.engines;
|
const { node } = packageJson.engines;
|
||||||
if (
|
if (nodeVersion && validRange(node) && !intersects(nodeVersion, node)) {
|
||||||
nodeVersion &&
|
|
||||||
validRange(node) &&
|
|
||||||
!intersects(nodeVersion, node) &&
|
|
||||||
!meta.isDev
|
|
||||||
) {
|
|
||||||
console.warn(
|
console.warn(
|
||||||
`Warning: Due to "engines": { "node": "${node}" } in your \`package.json\` file, the Node.js Version defined in your Project Settings ("${nodeVersion}") will not apply. Learn More: http://vercel.link/node-version`
|
`Warning: Due to "engines": { "node": "${node}" } in your \`package.json\` file, the Node.js Version defined in your Project Settings ("${nodeVersion}") will not apply. Learn More: http://vercel.link/node-version`
|
||||||
);
|
);
|
||||||
} else if (coerce(node)?.raw === node && !meta.isDev) {
|
} else if (coerce(node)?.raw === node) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` with major.minor.patch, but only major Node.js Version can be selected. Learn More: http://vercel.link/node-version`
|
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` with major.minor.patch, but only major Node.js Version can be selected. Learn More: http://vercel.link/node-version`
|
||||||
);
|
);
|
||||||
} else if (
|
} else if (validRange(node) && intersects(`${latest.major + 1}.x`, node)) {
|
||||||
validRange(node) &&
|
|
||||||
intersects(`${latest.major + 1}.x`, node) &&
|
|
||||||
!meta.isDev
|
|
||||||
) {
|
|
||||||
console.warn(
|
console.warn(
|
||||||
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version`
|
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version`
|
||||||
);
|
);
|
||||||
@@ -319,6 +299,7 @@ export async function scanParentDirs(
|
|||||||
start: destPath,
|
start: destPath,
|
||||||
filenames: ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml'],
|
filenames: ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml'],
|
||||||
});
|
});
|
||||||
|
let lockfilePath: string | undefined;
|
||||||
let lockfileVersion: number | undefined;
|
let lockfileVersion: number | undefined;
|
||||||
let cliType: CliType = 'yarn';
|
let cliType: CliType = 'yarn';
|
||||||
|
|
||||||
@@ -335,17 +316,25 @@ export async function scanParentDirs(
|
|||||||
// Priority order is Yarn > pnpm > npm
|
// Priority order is Yarn > pnpm > npm
|
||||||
if (hasYarnLock) {
|
if (hasYarnLock) {
|
||||||
cliType = 'yarn';
|
cliType = 'yarn';
|
||||||
|
lockfilePath = yarnLockPath;
|
||||||
} else if (pnpmLockYaml) {
|
} else if (pnpmLockYaml) {
|
||||||
cliType = 'pnpm';
|
cliType = 'pnpm';
|
||||||
// just ensure that it is read as a number and not a string
|
lockfilePath = pnpmLockPath;
|
||||||
lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
|
lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
|
||||||
} else if (packageLockJson) {
|
} else if (packageLockJson) {
|
||||||
cliType = 'npm';
|
cliType = 'npm';
|
||||||
|
lockfilePath = npmLockPath;
|
||||||
lockfileVersion = packageLockJson.lockfileVersion;
|
lockfileVersion = packageLockJson.lockfileVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
const packageJsonPath = pkgJsonPath || undefined;
|
const packageJsonPath = pkgJsonPath || undefined;
|
||||||
return { cliType, packageJson, lockfileVersion, packageJsonPath };
|
return {
|
||||||
|
cliType,
|
||||||
|
packageJson,
|
||||||
|
lockfilePath,
|
||||||
|
lockfileVersion,
|
||||||
|
packageJsonPath,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function walkParentDirs({
|
export async function walkParentDirs({
|
||||||
@@ -452,7 +441,7 @@ export async function runNpmInstall(
|
|||||||
debug(`Installing to ${destPath}`);
|
debug(`Installing to ${destPath}`);
|
||||||
|
|
||||||
const opts: SpawnOptionsExtended = { cwd: destPath, ...spawnOpts };
|
const opts: SpawnOptionsExtended = { cwd: destPath, ...spawnOpts };
|
||||||
const env = opts.env ? { ...opts.env } : { ...process.env };
|
const env = cloneEnv(opts.env || process.env);
|
||||||
delete env.NODE_ENV;
|
delete env.NODE_ENV;
|
||||||
opts.env = getEnvForPackageManager({
|
opts.env = getEnvForPackageManager({
|
||||||
cliType,
|
cliType,
|
||||||
@@ -461,12 +450,30 @@ export async function runNpmInstall(
|
|||||||
env,
|
env,
|
||||||
});
|
});
|
||||||
let commandArgs: string[];
|
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') {
|
if (cliType === 'npm') {
|
||||||
opts.prettyCommand = 'npm install';
|
opts.prettyCommand = 'npm install';
|
||||||
commandArgs = args
|
commandArgs = args
|
||||||
.filter(a => a !== '--prefer-offline')
|
.filter(a => a !== '--prefer-offline')
|
||||||
.concat(['install', '--no-audit', '--unsafe-perm']);
|
.concat(['install', '--no-audit', '--unsafe-perm']);
|
||||||
|
if (
|
||||||
|
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') {
|
} else if (cliType === 'pnpm') {
|
||||||
// PNPM's install command is similar to NPM's but without the audit nonsense
|
// PNPM's install command is similar to NPM's but without the audit nonsense
|
||||||
// @see options https://pnpm.io/cli/install
|
// @see options https://pnpm.io/cli/install
|
||||||
@@ -483,7 +490,26 @@ export async function runNpmInstall(
|
|||||||
commandArgs.push('--production');
|
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]`);
|
debug(`Install complete [${Date.now() - installTime}ms]`);
|
||||||
return true;
|
return true;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -594,10 +620,7 @@ export async function runPackageJsonScript(
|
|||||||
cliType,
|
cliType,
|
||||||
lockfileVersion,
|
lockfileVersion,
|
||||||
nodeVersion: undefined,
|
nodeVersion: undefined,
|
||||||
env: {
|
env: cloneEnv(process.env, spawnOpts?.env),
|
||||||
...process.env,
|
|
||||||
...spawnOpts?.env,
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
32
packages/build-utils/src/get-prefixed-env-vars.ts
Normal file
32
packages/build-utils/src/get-prefixed-env-vars.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
type Envs = { [key: string]: string | undefined };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the framework-specific prefixed System Environment Variables.
|
||||||
|
* See https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables
|
||||||
|
* @param envPrefix - Prefix, typically from `@vercel/frameworks`
|
||||||
|
* @param envs - Environment Variables, typically from `process.env`
|
||||||
|
*/
|
||||||
|
export function getPrefixedEnvVars({
|
||||||
|
envPrefix,
|
||||||
|
envs,
|
||||||
|
}: {
|
||||||
|
envPrefix: string | undefined;
|
||||||
|
envs: Envs;
|
||||||
|
}): Envs {
|
||||||
|
const vercelSystemEnvPrefix = 'VERCEL_';
|
||||||
|
const newEnvs: Envs = {};
|
||||||
|
if (envPrefix && envs.VERCEL_URL) {
|
||||||
|
Object.keys(envs)
|
||||||
|
.filter(key => key.startsWith(vercelSystemEnvPrefix))
|
||||||
|
.forEach(key => {
|
||||||
|
const newKey = `${envPrefix}${key}`;
|
||||||
|
if (!(newKey in envs)) {
|
||||||
|
newEnvs[newKey] = envs[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Tell turbo to exclude all Vercel System Env Vars
|
||||||
|
// See https://github.com/vercel/turborepo/pull/1622
|
||||||
|
newEnvs.TURBO_CI_VENDOR_ENV_KEY = `${envPrefix}${vercelSystemEnvPrefix}`;
|
||||||
|
}
|
||||||
|
return newEnvs;
|
||||||
|
}
|
||||||
@@ -40,6 +40,8 @@ import streamToBuffer from './fs/stream-to-buffer';
|
|||||||
import debug from './debug';
|
import debug from './debug';
|
||||||
import getIgnoreFilter from './get-ignore-filter';
|
import getIgnoreFilter from './get-ignore-filter';
|
||||||
import { getPlatformEnv } from './get-platform-env';
|
import { getPlatformEnv } from './get-platform-env';
|
||||||
|
import { getPrefixedEnvVars } from './get-prefixed-env-vars';
|
||||||
|
import { cloneEnv } from './clone-env';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
FileBlob,
|
FileBlob,
|
||||||
@@ -76,12 +78,14 @@ export {
|
|||||||
getDiscontinuedNodeVersions,
|
getDiscontinuedNodeVersions,
|
||||||
getSpawnOptions,
|
getSpawnOptions,
|
||||||
getPlatformEnv,
|
getPlatformEnv,
|
||||||
|
getPrefixedEnvVars,
|
||||||
streamToBuffer,
|
streamToBuffer,
|
||||||
debug,
|
debug,
|
||||||
isSymbolicLink,
|
isSymbolicLink,
|
||||||
getLambdaOptionsFromFunction,
|
getLambdaOptionsFromFunction,
|
||||||
scanParentDirs,
|
scanParentDirs,
|
||||||
getIgnoreFilter,
|
getIgnoreFilter,
|
||||||
|
cloneEnv,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { EdgeFunction } from './edge-function';
|
export { EdgeFunction } from './edge-function';
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export interface LambdaOptionsBase {
|
|||||||
regions?: string[];
|
regions?: string[];
|
||||||
supportsMultiPayloads?: boolean;
|
supportsMultiPayloads?: boolean;
|
||||||
supportsWrapper?: boolean;
|
supportsWrapper?: boolean;
|
||||||
|
experimentalResponseStreaming?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LambdaOptionsWithFiles extends LambdaOptionsBase {
|
export interface LambdaOptionsWithFiles extends LambdaOptionsBase {
|
||||||
@@ -60,6 +61,7 @@ export class Lambda {
|
|||||||
zipBuffer?: Buffer;
|
zipBuffer?: Buffer;
|
||||||
supportsMultiPayloads?: boolean;
|
supportsMultiPayloads?: boolean;
|
||||||
supportsWrapper?: boolean;
|
supportsWrapper?: boolean;
|
||||||
|
experimentalResponseStreaming?: boolean;
|
||||||
|
|
||||||
constructor(opts: LambdaOptions) {
|
constructor(opts: LambdaOptions) {
|
||||||
const {
|
const {
|
||||||
@@ -72,6 +74,7 @@ export class Lambda {
|
|||||||
regions,
|
regions,
|
||||||
supportsMultiPayloads,
|
supportsMultiPayloads,
|
||||||
supportsWrapper,
|
supportsWrapper,
|
||||||
|
experimentalResponseStreaming,
|
||||||
} = opts;
|
} = opts;
|
||||||
if ('files' in opts) {
|
if ('files' in opts) {
|
||||||
assert(typeof opts.files === 'object', '"files" must be an object');
|
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.zipBuffer = 'zipBuffer' in opts ? opts.zipBuffer : undefined;
|
||||||
this.supportsMultiPayloads = supportsMultiPayloads;
|
this.supportsMultiPayloads = supportsMultiPayloads;
|
||||||
this.supportsWrapper = supportsWrapper;
|
this.supportsWrapper = supportsWrapper;
|
||||||
|
this.experimentalResponseStreaming = experimentalResponseStreaming;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createZip(): Promise<Buffer> {
|
async createZip(): Promise<Buffer> {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ interface PrerenderOptions {
|
|||||||
group?: number;
|
group?: number;
|
||||||
bypassToken?: string | null /* optional to be non-breaking change */;
|
bypassToken?: string | null /* optional to be non-breaking change */;
|
||||||
allowQuery?: string[];
|
allowQuery?: string[];
|
||||||
|
initialHeaders?: Record<string, string>;
|
||||||
|
initialStatus?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Prerender {
|
export class Prerender {
|
||||||
@@ -18,6 +20,8 @@ export class Prerender {
|
|||||||
public group?: number;
|
public group?: number;
|
||||||
public bypassToken: string | null;
|
public bypassToken: string | null;
|
||||||
public allowQuery?: string[];
|
public allowQuery?: string[];
|
||||||
|
public initialHeaders?: Record<string, string>;
|
||||||
|
public initialStatus?: number;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
expiration,
|
expiration,
|
||||||
@@ -26,6 +30,8 @@ export class Prerender {
|
|||||||
group,
|
group,
|
||||||
bypassToken,
|
bypassToken,
|
||||||
allowQuery,
|
allowQuery,
|
||||||
|
initialHeaders,
|
||||||
|
initialStatus,
|
||||||
}: PrerenderOptions) {
|
}: PrerenderOptions) {
|
||||||
this.type = 'Prerender';
|
this.type = 'Prerender';
|
||||||
this.expiration = expiration;
|
this.expiration = expiration;
|
||||||
@@ -64,6 +70,30 @@ export class Prerender {
|
|||||||
}
|
}
|
||||||
this.fallback = fallback;
|
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 (allowQuery !== undefined) {
|
||||||
if (!Array.isArray(allowQuery)) {
|
if (!Array.isArray(allowQuery)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export interface Config {
|
|||||||
devCommand?: string;
|
devCommand?: string;
|
||||||
framework?: string | null;
|
framework?: string | null;
|
||||||
nodeVersion?: string;
|
nodeVersion?: string;
|
||||||
|
middleware?: boolean;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,6 +342,7 @@ export interface BuilderV2 {
|
|||||||
version: 2;
|
version: 2;
|
||||||
build: BuildV2;
|
build: BuildV2;
|
||||||
prepareCache?: PrepareCache;
|
prepareCache?: PrepareCache;
|
||||||
|
shouldServe?: ShouldServe;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BuilderV3 {
|
export interface BuilderV3 {
|
||||||
|
|||||||
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,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
"name": "20-npm-7",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const skipFixtures: string[] = [
|
|||||||
'08-zero-config-middleman',
|
'08-zero-config-middleman',
|
||||||
'21-npm-workspaces',
|
'21-npm-workspaces',
|
||||||
'23-pnpm-workspaces',
|
'23-pnpm-workspaces',
|
||||||
|
'41-nx-monorepo',
|
||||||
|
'42-npm-workspace-with-nx',
|
||||||
];
|
];
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
|||||||
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'));
|
||||||
|
});
|
||||||
|
});
|
||||||
87
packages/build-utils/test/unit.get-prefixed-env-vars.test.ts
vendored
Normal file
87
packages/build-utils/test/unit.get-prefixed-env-vars.test.ts
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { getPrefixedEnvVars } from '../src';
|
||||||
|
|
||||||
|
describe('Test `getPrefixedEnvVars()`', () => {
|
||||||
|
const cases: Array<{
|
||||||
|
name: string;
|
||||||
|
args: Parameters<typeof getPrefixedEnvVars>[0];
|
||||||
|
want: ReturnType<typeof getPrefixedEnvVars>;
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
name: 'should work with NEXT_PUBLIC_',
|
||||||
|
args: {
|
||||||
|
envPrefix: 'NEXT_PUBLIC_',
|
||||||
|
envs: {
|
||||||
|
VERCEL: '1',
|
||||||
|
VERCEL_URL: 'example.vercel.sh',
|
||||||
|
USER_ENV_VAR_NOT_VERCEL: 'example.com',
|
||||||
|
FOO: 'bar',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: {
|
||||||
|
NEXT_PUBLIC_VERCEL_URL: 'example.vercel.sh',
|
||||||
|
TURBO_CI_VENDOR_ENV_KEY: 'NEXT_PUBLIC_VERCEL_',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should work with GATSBY_',
|
||||||
|
args: {
|
||||||
|
envPrefix: 'GATSBY_',
|
||||||
|
envs: {
|
||||||
|
USER_ENV_VAR_NOT_VERCEL: 'example.com',
|
||||||
|
FOO: 'bar',
|
||||||
|
VERCEL_URL: 'example.vercel.sh',
|
||||||
|
VERCEL_ENV: 'production',
|
||||||
|
VERCEL_REGION: 'iad1',
|
||||||
|
VERCEL_GIT_COMMIT_AUTHOR_LOGIN: 'rauchg',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: {
|
||||||
|
GATSBY_VERCEL_URL: 'example.vercel.sh',
|
||||||
|
GATSBY_VERCEL_ENV: 'production',
|
||||||
|
GATSBY_VERCEL_REGION: 'iad1',
|
||||||
|
GATSBY_VERCEL_GIT_COMMIT_AUTHOR_LOGIN: 'rauchg',
|
||||||
|
TURBO_CI_VENDOR_ENV_KEY: 'GATSBY_VERCEL_',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should not return anything if no system env vars detected',
|
||||||
|
args: {
|
||||||
|
envPrefix: 'GATSBY_',
|
||||||
|
envs: {
|
||||||
|
USER_ENV_VAR_NOT_VERCEL: 'example.com',
|
||||||
|
FOO: 'bar',
|
||||||
|
BLARG_VERCEL_THING: 'fake',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should not return anything if envPrefix is empty string',
|
||||||
|
args: {
|
||||||
|
envPrefix: '',
|
||||||
|
envs: {
|
||||||
|
VERCEL: '1',
|
||||||
|
VERCEL_URL: 'example.vercel.sh',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should not return anything if envPrefix is undefined',
|
||||||
|
args: {
|
||||||
|
envPrefix: undefined,
|
||||||
|
envs: {
|
||||||
|
VERCEL: '1',
|
||||||
|
VERCEL_URL: 'example.vercel.sh',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { name, args, want } of cases) {
|
||||||
|
it(name, () => {
|
||||||
|
expect(getPrefixedEnvVars(args)).toEqual(want);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
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),
|
||||||
|
});
|
||||||
|
});
|
||||||
160
packages/build-utils/test/unit.test.ts
vendored
160
packages/build-utils/test/unit.test.ts
vendored
@@ -1,7 +1,6 @@
|
|||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs, { readlink } from 'fs-extra';
|
import fs, { readlink } from 'fs-extra';
|
||||||
import retry from 'async-retry';
|
|
||||||
import { strict as assert, strictEqual } from 'assert';
|
import { strict as assert, strictEqual } from 'assert';
|
||||||
import { createZip } from '../src/lambda';
|
import { createZip } from '../src/lambda';
|
||||||
import { getSupportedNodeVersion } from '../src/fs/node-version';
|
import { getSupportedNodeVersion } from '../src/fs/node-version';
|
||||||
@@ -16,7 +15,6 @@ import {
|
|||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
scanParentDirs,
|
scanParentDirs,
|
||||||
FileBlob,
|
FileBlob,
|
||||||
Meta,
|
|
||||||
} from '../src';
|
} from '../src';
|
||||||
|
|
||||||
jest.setTimeout(10 * 1000);
|
jest.setTimeout(10 * 1000);
|
||||||
@@ -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 () => {
|
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(
|
expect(await getSupportedNodeVersion('14.x', false)).toHaveProperty(
|
||||||
'major',
|
'major',
|
||||||
14
|
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('foo', true), autoMessage);
|
||||||
await expectBuilderError(getSupportedNodeVersion('=> 10', true), autoMessage);
|
await expectBuilderError(getSupportedNodeVersion('=> 10', true), autoMessage);
|
||||||
|
|
||||||
expect(await getSupportedNodeVersion('12.x', true)).toHaveProperty(
|
|
||||||
'major',
|
|
||||||
12
|
|
||||||
);
|
|
||||||
expect(await getSupportedNodeVersion('14.x', true)).toHaveProperty(
|
expect(await getSupportedNodeVersion('14.x', true)).toHaveProperty(
|
||||||
'major',
|
'major',
|
||||||
14
|
14
|
||||||
@@ -275,24 +265,41 @@ it('should only match supported node versions, otherwise throw an error', async
|
|||||||
|
|
||||||
it('should match all semver ranges', async () => {
|
it('should match all semver ranges', async () => {
|
||||||
// See https://docs.npmjs.com/files/package.json#engines
|
// See https://docs.npmjs.com/files/package.json#engines
|
||||||
expect(await getSupportedNodeVersion('12.0.0')).toHaveProperty('major', 12);
|
expect(await getSupportedNodeVersion('14.0.0')).toHaveProperty('major', 14);
|
||||||
expect(await getSupportedNodeVersion('12.x')).toHaveProperty('major', 12);
|
expect(await getSupportedNodeVersion('14.x')).toHaveProperty('major', 14);
|
||||||
expect(await getSupportedNodeVersion('>=10')).toHaveProperty('major', 16);
|
expect(await getSupportedNodeVersion('>=10')).toHaveProperty('major', 16);
|
||||||
expect(await getSupportedNodeVersion('>=10.3.0')).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',
|
'major',
|
||||||
12
|
16
|
||||||
);
|
);
|
||||||
expect(await getSupportedNodeVersion('>=9.5.0 <=12.5.0')).toHaveProperty(
|
expect(await getSupportedNodeVersion('>=9.5.0 <=14.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(
|
|
||||||
'major',
|
'major',
|
||||||
14
|
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 () => {
|
it('should ignore node version in vercel dev getNodeVersion()', async () => {
|
||||||
@@ -394,7 +401,7 @@ it('should get latest node version', async () => {
|
|||||||
it('should throw for discontinued versions', async () => {
|
it('should throw for discontinued versions', async () => {
|
||||||
// Mock a future date so that Node 8 and 10 become discontinued
|
// Mock a future date so that Node 8 and 10 become discontinued
|
||||||
const realDateNow = Date.now.bind(global.Date);
|
const realDateNow = Date.now.bind(global.Date);
|
||||||
global.Date.now = () => new Date('2022-09-01').getTime();
|
global.Date.now = () => new Date('2022-10-15').getTime();
|
||||||
|
|
||||||
expect(getSupportedNodeVersion('8.10.x', false)).rejects.toThrow();
|
expect(getSupportedNodeVersion('8.10.x', false)).rejects.toThrow();
|
||||||
expect(getSupportedNodeVersion('8.10.x', true)).rejects.toThrow();
|
expect(getSupportedNodeVersion('8.10.x', true)).rejects.toThrow();
|
||||||
@@ -436,13 +443,45 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
|
|||||||
expect(warningMessages).toStrictEqual([
|
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 "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 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
||||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-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-08-09 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-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;
|
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', () => {
|
it('should support require by path for legacy builders', () => {
|
||||||
const index = require('@vercel/build-utils');
|
const index = require('@vercel/build-utils');
|
||||||
|
|
||||||
@@ -501,6 +540,7 @@ it('should return lockfileVersion 2 with npm7', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('npm');
|
expect(result.cliType).toEqual('npm');
|
||||||
expect(result.lockfileVersion).toEqual(2);
|
expect(result.lockfileVersion).toEqual(2);
|
||||||
|
expect(result.lockfilePath).toEqual(path.join(fixture, 'package-lock.json'));
|
||||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -509,6 +549,7 @@ it('should not return lockfileVersion with yarn', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('yarn');
|
expect(result.cliType).toEqual('yarn');
|
||||||
expect(result.lockfileVersion).toEqual(undefined);
|
expect(result.lockfileVersion).toEqual(undefined);
|
||||||
|
expect(result.lockfilePath).toEqual(path.join(fixture, 'yarn.lock'));
|
||||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -517,6 +558,7 @@ it('should return lockfileVersion 1 with older versions of npm', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('npm');
|
expect(result.cliType).toEqual('npm');
|
||||||
expect(result.lockfileVersion).toEqual(1);
|
expect(result.lockfileVersion).toEqual(1);
|
||||||
|
expect(result.lockfilePath).toEqual(path.join(fixture, 'package-lock.json'));
|
||||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -525,6 +567,9 @@ it('should detect npm Workspaces', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('npm');
|
expect(result.cliType).toEqual('npm');
|
||||||
expect(result.lockfileVersion).toEqual(2);
|
expect(result.lockfileVersion).toEqual(2);
|
||||||
|
expect(result.lockfilePath).toEqual(
|
||||||
|
path.join(fixture, '..', 'package-lock.json')
|
||||||
|
);
|
||||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -533,6 +578,7 @@ it('should detect pnpm without workspace', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('pnpm');
|
expect(result.cliType).toEqual('pnpm');
|
||||||
expect(result.lockfileVersion).toEqual(5.3);
|
expect(result.lockfileVersion).toEqual(5.3);
|
||||||
|
expect(result.lockfilePath).toEqual(path.join(fixture, 'pnpm-lock.yaml'));
|
||||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -541,6 +587,9 @@ it('should detect pnpm with workspaces', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('pnpm');
|
expect(result.cliType).toEqual('pnpm');
|
||||||
expect(result.lockfileVersion).toEqual(5.3);
|
expect(result.lockfileVersion).toEqual(5.3);
|
||||||
|
expect(result.lockfilePath).toEqual(
|
||||||
|
path.join(fixture, '..', 'pnpm-lock.yaml')
|
||||||
|
);
|
||||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -552,6 +601,7 @@ it('should detect package.json in nested backend', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('yarn');
|
expect(result.cliType).toEqual('yarn');
|
||||||
expect(result.lockfileVersion).toEqual(undefined);
|
expect(result.lockfileVersion).toEqual(undefined);
|
||||||
|
// There is no lockfile but this test will pick up vercel/vercel/yarn.lock
|
||||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -563,56 +613,20 @@ it('should detect package.json in nested frontend', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('yarn');
|
expect(result.cliType).toEqual('yarn');
|
||||||
expect(result.lockfileVersion).toEqual(undefined);
|
expect(result.lockfileVersion).toEqual(undefined);
|
||||||
|
// There is no lockfile but this test will pick up vercel/vercel/yarn.lock
|
||||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only invoke `runNpmInstall()` once per `package.json` file (serial)', async () => {
|
it('should retry npm install when peer deps invalid and npm@8 on node@16', async () => {
|
||||||
const meta: Meta = {};
|
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
||||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
if (nodeMajor !== 16) {
|
||||||
const apiDir = path.join(fixture, 'api');
|
console.log(`Skipping test on node@${nodeMajor}`);
|
||||||
const retryOpts = { maxRetryTime: 1000 };
|
return;
|
||||||
let run1, run2, run3;
|
}
|
||||||
await retry(async () => {
|
const fixture = path.join(__dirname, 'fixtures', '15-npm-8-legacy-peer-deps');
|
||||||
run1 = await runNpmInstall(apiDir, [], undefined, meta);
|
const nodeVersion = { major: nodeMajor } as any;
|
||||||
expect(run1).toEqual(true);
|
await runNpmInstall(fixture, [], {}, {}, nodeVersion);
|
||||||
expect(
|
expect(warningMessages).toStrictEqual([
|
||||||
(meta.runNpmInstallSet as Set<string>).has(
|
'Warning: Retrying "Install Command" with `--legacy-peer-deps` which may accept a potentially broken dependency and slow install time.',
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,11 +10,9 @@
|
|||||||
|
|
||||||
## Usage
|
## 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 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.
|
||||||
|
|
||||||
We make it easy for frontend teams to **develop, preview, and ship** delightful user experiences, where performance is the default.
|
|
||||||
|
|
||||||
To install the latest version of Vercel CLI, run this command:
|
To install the latest version of Vercel CLI, run this command:
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vercel",
|
"name": "vercel",
|
||||||
"version": "27.3.7",
|
"version": "28.4.13",
|
||||||
"preferGlobal": true,
|
"preferGlobal": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "The command-line interface for Vercel",
|
"description": "The command-line interface for Vercel",
|
||||||
@@ -41,16 +41,16 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/build-utils": "5.2.0",
|
"@vercel/build-utils": "5.5.6",
|
||||||
"@vercel/go": "2.0.15",
|
"@vercel/go": "2.2.14",
|
||||||
"@vercel/hydrogen": "0.0.12",
|
"@vercel/hydrogen": "0.0.27",
|
||||||
"@vercel/next": "3.1.15",
|
"@vercel/next": "3.2.7",
|
||||||
"@vercel/node": "2.5.6",
|
"@vercel/node": "2.6.0",
|
||||||
"@vercel/python": "3.1.7",
|
"@vercel/python": "3.1.23",
|
||||||
"@vercel/redwood": "1.0.16",
|
"@vercel/redwood": "1.0.32",
|
||||||
"@vercel/remix": "1.0.17",
|
"@vercel/remix": "1.0.33",
|
||||||
"@vercel/ruby": "1.3.23",
|
"@vercel/ruby": "1.3.40",
|
||||||
"@vercel/static-build": "1.0.16",
|
"@vercel/static-build": "1.0.33",
|
||||||
"update-notifier": "5.1.0"
|
"update-notifier": "5.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -85,7 +85,6 @@
|
|||||||
"@types/node-fetch": "2.5.10",
|
"@types/node-fetch": "2.5.10",
|
||||||
"@types/npm-package-arg": "6.1.0",
|
"@types/npm-package-arg": "6.1.0",
|
||||||
"@types/pluralize": "0.0.29",
|
"@types/pluralize": "0.0.29",
|
||||||
"@types/progress": "2.0.3",
|
|
||||||
"@types/psl": "1.1.0",
|
"@types/psl": "1.1.0",
|
||||||
"@types/semver": "6.0.1",
|
"@types/semver": "6.0.1",
|
||||||
"@types/tar-fs": "1.16.1",
|
"@types/tar-fs": "1.16.1",
|
||||||
@@ -96,9 +95,10 @@
|
|||||||
"@types/which": "1.3.2",
|
"@types/which": "1.3.2",
|
||||||
"@types/write-json-file": "2.2.1",
|
"@types/write-json-file": "2.2.1",
|
||||||
"@types/yauzl-promise": "2.1.0",
|
"@types/yauzl-promise": "2.1.0",
|
||||||
"@vercel/client": "12.1.10",
|
"@vercel/client": "12.2.14",
|
||||||
"@vercel/frameworks": "1.1.3",
|
"@vercel/error-utils": "1.0.1",
|
||||||
"@vercel/fs-detectors": "2.0.5",
|
"@vercel/frameworks": "1.1.9",
|
||||||
|
"@vercel/fs-detectors": "3.4.6",
|
||||||
"@vercel/fun": "1.0.4",
|
"@vercel/fun": "1.0.4",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"@zeit/source-map-support": "0.6.2",
|
"@zeit/source-map-support": "0.6.2",
|
||||||
@@ -118,7 +118,6 @@
|
|||||||
"chokidar": "3.3.1",
|
"chokidar": "3.3.1",
|
||||||
"codecov": "3.8.2",
|
"codecov": "3.8.2",
|
||||||
"cpy": "7.2.0",
|
"cpy": "7.2.0",
|
||||||
"credit-card": "3.0.1",
|
|
||||||
"date-fns": "1.29.0",
|
"date-fns": "1.29.0",
|
||||||
"debug": "3.1.0",
|
"debug": "3.1.0",
|
||||||
"dot": "1.1.3",
|
"dot": "1.1.3",
|
||||||
@@ -154,7 +153,6 @@
|
|||||||
"ora": "3.4.0",
|
"ora": "3.4.0",
|
||||||
"pcre-to-regexp": "1.0.0",
|
"pcre-to-regexp": "1.0.0",
|
||||||
"pluralize": "7.0.0",
|
"pluralize": "7.0.0",
|
||||||
"progress": "2.0.3",
|
|
||||||
"promisepipe": "3.0.0",
|
"promisepipe": "3.0.0",
|
||||||
"psl": "1.1.31",
|
"psl": "1.1.31",
|
||||||
"qr-image": "3.2.0",
|
"qr-image": "3.2.0",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const { statSync } = require('fs');
|
|||||||
const pkg = require('../package');
|
const pkg = require('../package');
|
||||||
|
|
||||||
function error(command) {
|
function error(command) {
|
||||||
console.error('> Error!', command);
|
console.error('> Error:', command);
|
||||||
}
|
}
|
||||||
|
|
||||||
function debug(str) {
|
function debug(str) {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const help = () => {
|
|||||||
)} Login token
|
)} Login token
|
||||||
-S, --scope Set a custom scope
|
-S, --scope Set a custom scope
|
||||||
-N, --next Show next page of results
|
-N, --next Show next page of results
|
||||||
|
-y, --yes Skip the confirmation prompt when removing an alias
|
||||||
|
|
||||||
${chalk.dim('Examples:')}
|
${chalk.dim('Examples:')}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export default async function rm(
|
|||||||
|
|
||||||
const removeStamp = stamp();
|
const removeStamp = stamp();
|
||||||
if (!opts['--yes'] && !(await confirmAliasRemove(client, alias))) {
|
if (!opts['--yes'] && !(await confirmAliasRemove(client, alias))) {
|
||||||
output.log('Aborted');
|
output.log('Canceled');
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ function handleSetupDomainError<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof ERRORS.UserAborted) {
|
if (error instanceof ERRORS.UserAborted) {
|
||||||
output.error(`User aborted`);
|
output.error(`User canceled.`);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,170 +0,0 @@
|
|||||||
import ansiEscapes from 'ansi-escapes';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import ccValidator from 'credit-card';
|
|
||||||
import textInput from '../../util/input/text';
|
|
||||||
import cardBrands from '../../util/billing/card-brands';
|
|
||||||
import success from '../../util/output/success';
|
|
||||||
import wait from '../../util/output/wait';
|
|
||||||
import chars from '../../util/output/chars';
|
|
||||||
import error from '../../util/output/error';
|
|
||||||
|
|
||||||
const expDateMiddleware = data => data;
|
|
||||||
|
|
||||||
export default async function ({ creditCards, clear = false, contextName }) {
|
|
||||||
const state = {
|
|
||||||
error: undefined,
|
|
||||||
cardGroupLabel: `> ${chalk.bold(
|
|
||||||
`Enter your card details for ${chalk.bold(contextName)}`
|
|
||||||
)}`,
|
|
||||||
|
|
||||||
name: {
|
|
||||||
label: 'Full Name'.padEnd(12),
|
|
||||||
placeholder: 'John Appleseed',
|
|
||||||
validateValue: data => data.trim().length > 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
cardNumber: {
|
|
||||||
label: 'Number'.padEnd(12),
|
|
||||||
mask: 'cc',
|
|
||||||
placeholder: '#### #### #### ####',
|
|
||||||
validateKeypress: (data, value) => /\d/.test(data) && value.length < 19,
|
|
||||||
validateValue: data => {
|
|
||||||
data = data.replace(/ /g, '');
|
|
||||||
const type = ccValidator.determineCardType(data);
|
|
||||||
if (!type) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return ccValidator.isValidCardNumber(data, type);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
ccv: {
|
|
||||||
label: 'CCV'.padEnd(12),
|
|
||||||
mask: 'ccv',
|
|
||||||
placeholder: '###',
|
|
||||||
validateValue: data => {
|
|
||||||
const brand = state.cardNumber.brand.toLowerCase();
|
|
||||||
return ccValidator.doesCvvMatchType(data, brand);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
expDate: {
|
|
||||||
label: 'Exp. Date'.padEnd(12),
|
|
||||||
mask: 'expDate',
|
|
||||||
placeholder: 'mm / yyyy',
|
|
||||||
middleware: expDateMiddleware,
|
|
||||||
validateValue: data => !ccValidator.isExpired(...data.split(' / ')),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
async function render() {
|
|
||||||
for (const key in state) {
|
|
||||||
if (!Object.hasOwnProperty.call(state, key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const piece = state[key];
|
|
||||||
|
|
||||||
if (typeof piece === 'string') {
|
|
||||||
console.log(piece);
|
|
||||||
} else if (typeof piece === 'object') {
|
|
||||||
let result;
|
|
||||||
|
|
||||||
try {
|
|
||||||
/* eslint-disable no-await-in-loop */
|
|
||||||
result = await textInput({
|
|
||||||
label: `- ${piece.label}`,
|
|
||||||
initialValue: piece.initialValue || piece.value,
|
|
||||||
placeholder: piece.placeholder,
|
|
||||||
mask: piece.mask,
|
|
||||||
validateKeypress: piece.validateKeypress,
|
|
||||||
validateValue: piece.validateValue,
|
|
||||||
autoComplete: piece.autoComplete,
|
|
||||||
});
|
|
||||||
|
|
||||||
piece.value = result;
|
|
||||||
|
|
||||||
if (key === 'cardNumber') {
|
|
||||||
let brand = cardBrands[ccValidator.determineCardType(result)];
|
|
||||||
piece.brand = brand;
|
|
||||||
|
|
||||||
if (brand === 'American Express') {
|
|
||||||
state.ccv.placeholder = '#'.repeat(4);
|
|
||||||
} else {
|
|
||||||
state.ccv.placeholder = '#'.repeat(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
brand = chalk.cyan(`[${brand}]`);
|
|
||||||
const masked = chalk.gray('#### '.repeat(3)) + result.split(' ')[3];
|
|
||||||
process.stdout.write(
|
|
||||||
`${chalk.cyan(chars.tick)} ${piece.label}${masked} ${brand}\n`
|
|
||||||
);
|
|
||||||
} else if (key === 'ccv') {
|
|
||||||
process.stdout.write(
|
|
||||||
`${chalk.cyan(chars.tick)} ${piece.label}${'*'.repeat(
|
|
||||||
result.length
|
|
||||||
)}\n`
|
|
||||||
);
|
|
||||||
} else if (key === 'expDate') {
|
|
||||||
let text = result.split(' / ');
|
|
||||||
text = text[0] + chalk.gray(' / ') + text[1];
|
|
||||||
process.stdout.write(
|
|
||||||
`${chalk.cyan(chars.tick)} ${piece.label}${text}\n`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
process.stdout.write(
|
|
||||||
`${chalk.cyan(chars.tick)} ${piece.label}${result}\n`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (err.message === 'USER_ABORT') {
|
|
||||||
process.exit(1);
|
|
||||||
} else {
|
|
||||||
console.error(error(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(''); // New line
|
|
||||||
const stopSpinner = wait(process.stderr, 'Saving card');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await creditCards.add({
|
|
||||||
name: state.name.value,
|
|
||||||
cardNumber: state.cardNumber.value,
|
|
||||||
ccv: state.ccv.value,
|
|
||||||
expDate: state.expDate.value,
|
|
||||||
});
|
|
||||||
|
|
||||||
stopSpinner();
|
|
||||||
|
|
||||||
if (clear) {
|
|
||||||
const linesToClear = state.error ? 15 : 14;
|
|
||||||
process.stdout.write(ansiEscapes.eraseLines(linesToClear));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
success(
|
|
||||||
`${state.cardNumber.brand || state.cardNumber.card.brand} ending in ${
|
|
||||||
res.last4 || res.card.last4
|
|
||||||
} was added to ${chalk.bold(contextName)}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
stopSpinner();
|
|
||||||
const linesToClear = state.error ? 15 : 14;
|
|
||||||
process.stdout.write(ansiEscapes.eraseLines(linesToClear));
|
|
||||||
state.error = `${chalk.red('> Error!')} ${
|
|
||||||
err.message
|
|
||||||
} Please make sure the info is correct`;
|
|
||||||
await render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await render();
|
|
||||||
} catch (err) {
|
|
||||||
console.erorr(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,353 +0,0 @@
|
|||||||
import chalk from 'chalk';
|
|
||||||
import ms from 'ms';
|
|
||||||
import plural from 'pluralize';
|
|
||||||
import { error } from '../../util/error';
|
|
||||||
import NowCreditCards from '../../util/credit-cards';
|
|
||||||
import indent from '../../util/output/indent';
|
|
||||||
import listInput from '../../util/input/list';
|
|
||||||
import success from '../../util/output/success';
|
|
||||||
import promptBool from '../../util/input/prompt-bool';
|
|
||||||
import info from '../../util/output/info';
|
|
||||||
import logo from '../../util/output/logo';
|
|
||||||
import addBilling from './add';
|
|
||||||
import exit from '../../util/exit';
|
|
||||||
import getScope from '../../util/get-scope.ts';
|
|
||||||
import { getPkgName } from '../../util/pkg-name.ts';
|
|
||||||
import getArgs from '../../util/get-args.ts';
|
|
||||||
import handleError from '../../util/handle-error.ts';
|
|
||||||
|
|
||||||
const help = () => {
|
|
||||||
console.log(`
|
|
||||||
${chalk.bold(`${logo} ${getPkgName()} billing`)} [options] <command>
|
|
||||||
|
|
||||||
${chalk.dim('Options:')}
|
|
||||||
|
|
||||||
ls Show all of your credit cards
|
|
||||||
add Add a new credit card
|
|
||||||
rm [id] Remove a credit card
|
|
||||||
set-default [id] Make a credit card your default one
|
|
||||||
|
|
||||||
${chalk.dim('Options:')}
|
|
||||||
|
|
||||||
-h, --help Output usage information
|
|
||||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
|
||||||
'FILE'
|
|
||||||
)} Path to the local ${'`vercel.json`'} file
|
|
||||||
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
|
|
||||||
'DIR'
|
|
||||||
)} Path to the global ${'`.vercel`'} directory
|
|
||||||
-d, --debug Debug mode [off]
|
|
||||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
|
||||||
'TOKEN'
|
|
||||||
)} Login token
|
|
||||||
-S, --scope Set a custom scope
|
|
||||||
|
|
||||||
${chalk.dim('Examples:')}
|
|
||||||
|
|
||||||
${chalk.gray('–')} Add a new credit card (interactively)
|
|
||||||
|
|
||||||
${chalk.cyan(`$ ${getPkgName()} billing add`)}
|
|
||||||
`);
|
|
||||||
};
|
|
||||||
|
|
||||||
let argv;
|
|
||||||
let subcommand;
|
|
||||||
|
|
||||||
export default async client => {
|
|
||||||
try {
|
|
||||||
argv = getArgs(client.argv.slice(2), {});
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
argv._ = argv._.slice(1);
|
|
||||||
|
|
||||||
subcommand = argv._[0];
|
|
||||||
|
|
||||||
if (argv['--help'] || !subcommand) {
|
|
||||||
help();
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
output,
|
|
||||||
config: { currentTeam },
|
|
||||||
} = client;
|
|
||||||
|
|
||||||
const start = new Date();
|
|
||||||
const creditCards = new NowCreditCards({
|
|
||||||
client,
|
|
||||||
currentTeam,
|
|
||||||
});
|
|
||||||
|
|
||||||
let contextName = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
({ contextName } = await getScope(client));
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
|
||||||
output.error(err.message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = argv._.slice(1);
|
|
||||||
|
|
||||||
switch (subcommand) {
|
|
||||||
case 'ls':
|
|
||||||
case 'list': {
|
|
||||||
let cards;
|
|
||||||
|
|
||||||
try {
|
|
||||||
cards = await creditCards.ls();
|
|
||||||
} catch (err) {
|
|
||||||
console.error(error(err.message));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = cards.sources
|
|
||||||
.map(source => {
|
|
||||||
const _default =
|
|
||||||
source.id === cards.defaultSource
|
|
||||||
? ` ${chalk.bold('(default)')}`
|
|
||||||
: '';
|
|
||||||
const id = `${chalk.gray('-')} ${chalk.cyan(
|
|
||||||
`ID: ${source.id}`
|
|
||||||
)}${_default}`;
|
|
||||||
const number = `${chalk.gray('#### ').repeat(3)}${
|
|
||||||
source.last4 || source.card.last4
|
|
||||||
}`;
|
|
||||||
|
|
||||||
return [
|
|
||||||
id,
|
|
||||||
indent(source.name || source.owner.name, 2),
|
|
||||||
indent(`${source.brand || source.card.brand} ${number}`, 2),
|
|
||||||
].join('\n');
|
|
||||||
})
|
|
||||||
.join('\n\n');
|
|
||||||
|
|
||||||
const elapsed = ms(new Date() - start);
|
|
||||||
console.log(
|
|
||||||
`> ${plural(
|
|
||||||
'card',
|
|
||||||
cards.sources.length,
|
|
||||||
true
|
|
||||||
)} found under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`
|
|
||||||
);
|
|
||||||
if (text) {
|
|
||||||
console.log(`\n${text}\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'set-default': {
|
|
||||||
if (args.length > 1) {
|
|
||||||
console.error(error('Invalid number of arguments'));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const start = new Date();
|
|
||||||
|
|
||||||
let cards;
|
|
||||||
try {
|
|
||||||
cards = await creditCards.ls();
|
|
||||||
} catch (err) {
|
|
||||||
console.error(error(err.message));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cards.sources.length === 0) {
|
|
||||||
console.error(error('You have no credit cards to choose from'));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cardId = args[0];
|
|
||||||
|
|
||||||
if (cardId === undefined) {
|
|
||||||
const elapsed = ms(new Date() - start);
|
|
||||||
const message = `Selecting a new default payment card for ${chalk.bold(
|
|
||||||
contextName
|
|
||||||
)} ${chalk.gray(`[${elapsed}]`)}`;
|
|
||||||
const choices = buildInquirerChoices(cards);
|
|
||||||
|
|
||||||
cardId = await listInput(client, {
|
|
||||||
message,
|
|
||||||
choices,
|
|
||||||
separator: true,
|
|
||||||
abort: 'end',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the provided cardId (in case the user
|
|
||||||
// typed `vercel billing set-default <some-id>`) is valid
|
|
||||||
if (cardId) {
|
|
||||||
const label = `Are you sure that you to set this card as the default?`;
|
|
||||||
const confirmation = await promptBool(label, {
|
|
||||||
...client,
|
|
||||||
trailing: '\n',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!confirmation) {
|
|
||||||
console.log(info('Aborted'));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const start = new Date();
|
|
||||||
await creditCards.setDefault(cardId);
|
|
||||||
|
|
||||||
const card = cards.sources.find(card => card.id === cardId);
|
|
||||||
const elapsed = ms(new Date() - start);
|
|
||||||
console.log(
|
|
||||||
success(
|
|
||||||
`${card.brand || card.card.brand} ending in ${
|
|
||||||
card.last4 || card.card.last4
|
|
||||||
} is now the default ${chalk.gray(`[${elapsed}]`)}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log('No changes made');
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'rm':
|
|
||||||
case 'remove': {
|
|
||||||
if (args.length > 1) {
|
|
||||||
console.error(error('Invalid number of arguments'));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const start = new Date();
|
|
||||||
let cards;
|
|
||||||
try {
|
|
||||||
cards = await creditCards.ls();
|
|
||||||
} catch (err) {
|
|
||||||
console.error(error(err.message));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cards.sources.length === 0) {
|
|
||||||
console.error(
|
|
||||||
error(
|
|
||||||
`You have no credit cards to choose from to delete under ${chalk.bold(
|
|
||||||
contextName
|
|
||||||
)}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cardId = args[0];
|
|
||||||
|
|
||||||
if (cardId === undefined) {
|
|
||||||
const elapsed = ms(new Date() - start);
|
|
||||||
const message = `Selecting a card to ${chalk.underline(
|
|
||||||
'remove'
|
|
||||||
)} under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`;
|
|
||||||
const choices = buildInquirerChoices(cards);
|
|
||||||
|
|
||||||
cardId = await listInput(client, {
|
|
||||||
message,
|
|
||||||
choices,
|
|
||||||
separator: true,
|
|
||||||
abort: 'start',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shoud check if the provided cardId (in case the user
|
|
||||||
// typed `vercel billing rm <some-id>`) is valid
|
|
||||||
if (cardId) {
|
|
||||||
const label = `Are you sure that you want to remove this card?`;
|
|
||||||
const confirmation = await promptBool(label, client);
|
|
||||||
if (!confirmation) {
|
|
||||||
console.log('Aborted');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const start = new Date();
|
|
||||||
await creditCards.rm(cardId);
|
|
||||||
|
|
||||||
const deletedCard = cards.sources.find(card => card.id === cardId);
|
|
||||||
const remainingCards = cards.sources.filter(card => card.id !== cardId);
|
|
||||||
|
|
||||||
let text = `${deletedCard.brand || deletedCard.card.brand} ending in ${
|
|
||||||
deletedCard.last4 || deletedCard.card.last4
|
|
||||||
} was deleted`;
|
|
||||||
// ${chalk.gray(`[${elapsed}]`)}
|
|
||||||
|
|
||||||
if (cardId === cards.defaultSource) {
|
|
||||||
if (remainingCards.length === 0) {
|
|
||||||
// The user deleted the last card in their account
|
|
||||||
text += `\n${chalk.yellow('Warning!')} You have no default card`;
|
|
||||||
} else {
|
|
||||||
// We can't guess the current default card – let's ask the API
|
|
||||||
const cards = await creditCards.ls();
|
|
||||||
const newDefaultCard = cards.sources.find(
|
|
||||||
card => card.id === cards.defaultCardId
|
|
||||||
);
|
|
||||||
|
|
||||||
text += `\n${
|
|
||||||
newDefaultCard.brand || newDefaultCard.card.brand
|
|
||||||
} ending in ${
|
|
||||||
newDefaultCard.last4 || newDefaultCard.card.last4
|
|
||||||
} in now default for ${chalk.bold(contextName)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const elapsed = ms(new Date() - start);
|
|
||||||
text += ` ${chalk.gray(`[${elapsed}]`)}`;
|
|
||||||
console.log(success(text));
|
|
||||||
} else {
|
|
||||||
console.log('No changes made');
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'add': {
|
|
||||||
await addBilling({
|
|
||||||
creditCards,
|
|
||||||
contextName,
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.error(
|
|
||||||
error('Please specify a valid subcommand: ls | add | rm | set-default')
|
|
||||||
);
|
|
||||||
help();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is required, otherwise we get those weird zlib errors
|
|
||||||
return exit(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Builds a `choices` object that can be passesd to inquirer.prompt()
|
|
||||||
function buildInquirerChoices(cards) {
|
|
||||||
return cards.sources.map(source => {
|
|
||||||
const _default =
|
|
||||||
source.id === cards.defaultSource ? ` ${chalk.bold('(default)')}` : '';
|
|
||||||
const id = `${chalk.cyan(`ID: ${source.id}`)}${_default}`;
|
|
||||||
const number = `${chalk.gray('#### ').repeat(3)}${
|
|
||||||
source.last4 || source.card.last4
|
|
||||||
}`;
|
|
||||||
const str = [
|
|
||||||
id,
|
|
||||||
indent(source.name || source.owner.name, 2),
|
|
||||||
indent(`${source.brand || source.card.brand} ${number}`, 2),
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: str, // Will be displayed by Inquirer
|
|
||||||
value: source.id, // Will be used to identify the answer
|
|
||||||
short: source.id, // Will be displayed after the users answers
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ import open from 'open';
|
|||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import execa from 'execa';
|
import execa from 'execa';
|
||||||
import plural from 'pluralize';
|
import plural from 'pluralize';
|
||||||
import inquirer from 'inquirer';
|
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import chalk, { Chalk } from 'chalk';
|
import chalk, { Chalk } from 'chalk';
|
||||||
import { URLSearchParams, parse } from 'url';
|
import { URLSearchParams, parse } from 'url';
|
||||||
@@ -116,8 +115,6 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const badDeploymentPromise = getDeployment(client, bad).catch(err => err);
|
|
||||||
|
|
||||||
good = normalizeURL(good);
|
good = normalizeURL(good);
|
||||||
parsed = parse(good);
|
parsed = parse(good);
|
||||||
if (!parsed.hostname) {
|
if (!parsed.hostname) {
|
||||||
@@ -138,8 +135,6 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const goodDeploymentPromise = getDeployment(client, good).catch(err => err);
|
|
||||||
|
|
||||||
if (!subpath) {
|
if (!subpath) {
|
||||||
subpath = await prompt(
|
subpath = await prompt(
|
||||||
client,
|
client,
|
||||||
@@ -148,14 +143,15 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
output.spinner('Retrieving deployments…');
|
output.spinner('Retrieving deployments…');
|
||||||
const [badDeployment, goodDeployment] = await Promise.all([
|
|
||||||
badDeploymentPromise,
|
// `getDeployment` cannot be parallelized because it might prompt for login
|
||||||
goodDeploymentPromise,
|
const badDeployment = await getDeployment(client, bad).catch(err => err);
|
||||||
]);
|
|
||||||
|
|
||||||
if (badDeployment) {
|
if (badDeployment) {
|
||||||
if (badDeployment instanceof Error) {
|
if (badDeployment instanceof Error) {
|
||||||
badDeployment.message += ` "${bad}"`;
|
badDeployment.message += ` when requesting bad deployment "${normalizeURL(
|
||||||
|
bad
|
||||||
|
)}"`;
|
||||||
output.prettyError(badDeployment);
|
output.prettyError(badDeployment);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -165,11 +161,14 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { projectId } = badDeployment;
|
// `getDeployment` cannot be parallelized because it might prompt for login
|
||||||
|
const goodDeployment = await getDeployment(client, good).catch(err => err);
|
||||||
|
|
||||||
if (goodDeployment) {
|
if (goodDeployment) {
|
||||||
if (goodDeployment instanceof Error) {
|
if (goodDeployment instanceof Error) {
|
||||||
goodDeployment.message += ` "${good}"`;
|
goodDeployment.message += ` when requesting good deployment "${normalizeURL(
|
||||||
|
good
|
||||||
|
)}"`;
|
||||||
output.prettyError(goodDeployment);
|
output.prettyError(goodDeployment);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -181,6 +180,8 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { projectId } = badDeployment;
|
||||||
|
|
||||||
if (projectId !== goodDeployment.projectId) {
|
if (projectId !== goodDeployment.projectId) {
|
||||||
output.error(`Good and Bad deployments must be from the same Project`);
|
output.error(`Good and Bad deployments must be from the same Project`);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -228,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
|
// If we have the "good" deployment in this chunk, then we're done
|
||||||
for (let i = 0; i < newDeployments.length; i++) {
|
for (let i = 0; i < newDeployments.length; i++) {
|
||||||
if (newDeployments[i].url === good) {
|
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;
|
next = undefined;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -318,7 +320,7 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
if (openEnabled) {
|
if (openEnabled) {
|
||||||
await open(testUrl);
|
await open(testUrl);
|
||||||
}
|
}
|
||||||
const answer = await inquirer.prompt({
|
const answer = await client.prompt({
|
||||||
type: 'expand',
|
type: 'expand',
|
||||||
name: 'action',
|
name: 'action',
|
||||||
message: 'Select an action:',
|
message: 'Select an action:',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import chalk from 'chalk';
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import { join, normalize, relative, resolve } from 'path';
|
import { join, normalize, relative, resolve } from 'path';
|
||||||
import {
|
import {
|
||||||
|
getDiscontinuedNodeVersions,
|
||||||
normalizePath,
|
normalizePath,
|
||||||
Files,
|
Files,
|
||||||
FileFsRef,
|
FileFsRef,
|
||||||
@@ -25,6 +26,7 @@ import {
|
|||||||
MergeRoutesProps,
|
MergeRoutesProps,
|
||||||
Route,
|
Route,
|
||||||
} from '@vercel/routing-utils';
|
} from '@vercel/routing-utils';
|
||||||
|
import { fileNameSymbol } from '@vercel/client';
|
||||||
import type { VercelConfig } from '@vercel/client';
|
import type { VercelConfig } from '@vercel/client';
|
||||||
|
|
||||||
import pull from './pull';
|
import pull from './pull';
|
||||||
@@ -37,6 +39,7 @@ import cliPkg from '../util/pkg';
|
|||||||
import readJSONFile from '../util/read-json-file';
|
import readJSONFile from '../util/read-json-file';
|
||||||
import { CantParseJSONFile } from '../util/errors-ts';
|
import { CantParseJSONFile } from '../util/errors-ts';
|
||||||
import {
|
import {
|
||||||
|
pickOverrides,
|
||||||
ProjectLinkAndSettings,
|
ProjectLinkAndSettings,
|
||||||
readProjectSettings,
|
readProjectSettings,
|
||||||
} from '../util/projects/project-settings';
|
} from '../util/projects/project-settings';
|
||||||
@@ -53,6 +56,7 @@ import { importBuilders } from '../util/build/import-builders';
|
|||||||
import { initCorepack, cleanupCorepack } from '../util/build/corepack';
|
import { initCorepack, cleanupCorepack } from '../util/build/corepack';
|
||||||
import { sortBuilders } from '../util/build/sort-builders';
|
import { sortBuilders } from '../util/build/sort-builders';
|
||||||
import { toEnumerableError } from '../util/error';
|
import { toEnumerableError } from '../util/error';
|
||||||
|
import { validateConfig } from '../util/validate-config';
|
||||||
|
|
||||||
type BuildResult = BuildResultV2 | BuildResultV3;
|
type BuildResult = BuildResultV2 | BuildResultV3;
|
||||||
|
|
||||||
@@ -91,7 +95,7 @@ const help = () => {
|
|||||||
--output [path] Directory where built assets should be written to
|
--output [path] Directory where built assets should be written to
|
||||||
--prod Build a production deployment
|
--prod Build a production deployment
|
||||||
-d, --debug Debug mode [off]
|
-d, --debug Debug mode [off]
|
||||||
-y, --yes Skip the confirmation prompt
|
-y, --yes Skip the confirmation prompt about pulling environment variables and project settings when not found locally
|
||||||
|
|
||||||
${chalk.dim('Examples:')}
|
${chalk.dim('Examples:')}
|
||||||
|
|
||||||
@@ -157,7 +161,7 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
client.output.print(
|
client.output.print(
|
||||||
`No Project Settings found locally. Run ${cli.getCommandName(
|
`No Project Settings found locally. Run ${cli.getCommandName(
|
||||||
'pull --yes'
|
'pull --yes'
|
||||||
)} to retreive them.`
|
)} to retrieve them.`
|
||||||
);
|
);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -171,7 +175,7 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
client.output.print(`Aborted. No Project Settings retrieved.\n`);
|
client.output.print(`Canceled. No Project Settings retrieved.\n`);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const { argv: originalArgv } = client;
|
const { argv: originalArgv } = client;
|
||||||
@@ -231,7 +235,8 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
process.env.VERCEL = '1';
|
process.env.VERCEL = '1';
|
||||||
process.env.NOW_BUILDER = '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) {
|
} catch (err: any) {
|
||||||
output.prettyError(err);
|
output.prettyError(err);
|
||||||
|
|
||||||
@@ -264,31 +269,49 @@ async function doBuild(
|
|||||||
buildsJson: BuildsManifest,
|
buildsJson: BuildsManifest,
|
||||||
cwd: string,
|
cwd: string,
|
||||||
outputDir: string
|
outputDir: string
|
||||||
): Promise<number> {
|
): Promise<void> {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
const workPath = join(cwd, project.settings.rootDirectory || '.');
|
const workPath = join(cwd, project.settings.rootDirectory || '.');
|
||||||
|
|
||||||
// Load `package.json` and `vercel.json` files
|
const [pkg, vercelConfig, nowConfig] = await Promise.all([
|
||||||
const [pkg, vercelConfig] = await Promise.all([
|
|
||||||
readJSONFile<PackageJson>(join(workPath, 'package.json')),
|
readJSONFile<PackageJson>(join(workPath, 'package.json')),
|
||||||
readJSONFile<VercelConfig>(join(workPath, 'vercel.json')).then(
|
readJSONFile<VercelConfig>(join(workPath, 'vercel.json')),
|
||||||
config => config || readJSONFile<VercelConfig>(join(workPath, 'now.json'))
|
readJSONFile<VercelConfig>(join(workPath, 'now.json')),
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (pkg instanceof CantParseJSONFile) throw pkg;
|
if (pkg instanceof CantParseJSONFile) throw pkg;
|
||||||
if (vercelConfig instanceof CantParseJSONFile) throw vercelConfig;
|
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
|
// Get a list of source files
|
||||||
const files = (await getFiles(workPath, client)).map(f =>
|
const files = (await getFiles(workPath, client)).map(f =>
|
||||||
normalizePath(relative(workPath, f))
|
normalizePath(relative(workPath, f))
|
||||||
);
|
);
|
||||||
|
|
||||||
const routesResult = getTransformedRoutes(vercelConfig || {});
|
const routesResult = getTransformedRoutes(localConfig);
|
||||||
if (routesResult.error) {
|
if (routesResult.error) {
|
||||||
throw routesResult.error;
|
throw routesResult.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vercelConfig?.builds && vercelConfig.functions) {
|
if (localConfig.builds && localConfig.functions) {
|
||||||
throw new NowBuildError({
|
throw new NowBuildError({
|
||||||
code: 'bad_request',
|
code: 'bad_request',
|
||||||
message:
|
message:
|
||||||
@@ -297,7 +320,7 @@ async function doBuild(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let builds = vercelConfig?.builds || [];
|
let builds = localConfig.builds || [];
|
||||||
let zeroConfigRoutes: Route[] = [];
|
let zeroConfigRoutes: Route[] = [];
|
||||||
let isZeroConfig = false;
|
let isZeroConfig = false;
|
||||||
|
|
||||||
@@ -312,8 +335,8 @@ async function doBuild(
|
|||||||
|
|
||||||
// Detect the Vercel Builders that will need to be invoked
|
// Detect the Vercel Builders that will need to be invoked
|
||||||
const detectedBuilders = await detectBuilders(files, pkg, {
|
const detectedBuilders = await detectBuilders(files, pkg, {
|
||||||
...vercelConfig,
|
...localConfig,
|
||||||
projectSettings: project.settings,
|
projectSettings,
|
||||||
ignoreBuildScript: true,
|
ignoreBuildScript: true,
|
||||||
featHandleMiss: true,
|
featHandleMiss: true,
|
||||||
});
|
});
|
||||||
@@ -389,13 +412,10 @@ async function doBuild(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
buildsJson.builds = Array.from(buildsJsonBuilds.values());
|
buildsJson.builds = Array.from(buildsJsonBuilds.values());
|
||||||
const buildsJsonPath = join(outputDir, 'builds.json');
|
await fs.writeJSON(join(outputDir, 'builds.json'), buildsJson, {
|
||||||
const writeBuildsJsonPromise = fs.writeJSON(buildsJsonPath, buildsJson, {
|
|
||||||
spaces: 2,
|
spaces: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
ops.push(writeBuildsJsonPromise);
|
|
||||||
|
|
||||||
// The `meta` config property is re-used for each Builder
|
// The `meta` config property is re-used for each Builder
|
||||||
// invocation so that Builders can share state between
|
// invocation so that Builders can share state between
|
||||||
// subsequent entrypoint builds.
|
// subsequent entrypoint builds.
|
||||||
@@ -425,14 +445,14 @@ async function doBuild(
|
|||||||
|
|
||||||
const buildConfig: Config = isZeroConfig
|
const buildConfig: Config = isZeroConfig
|
||||||
? {
|
? {
|
||||||
outputDirectory: project.settings.outputDirectory ?? undefined,
|
outputDirectory: projectSettings.outputDirectory ?? undefined,
|
||||||
...build.config,
|
...build.config,
|
||||||
projectSettings: project.settings,
|
projectSettings,
|
||||||
installCommand: project.settings.installCommand ?? undefined,
|
installCommand: projectSettings.installCommand ?? undefined,
|
||||||
devCommand: project.settings.devCommand ?? undefined,
|
devCommand: projectSettings.devCommand ?? undefined,
|
||||||
buildCommand: project.settings.buildCommand ?? undefined,
|
buildCommand: projectSettings.buildCommand ?? undefined,
|
||||||
framework: project.settings.framework,
|
framework: projectSettings.framework,
|
||||||
nodeVersion: project.settings.nodeVersion,
|
nodeVersion: projectSettings.nodeVersion,
|
||||||
}
|
}
|
||||||
: build.config || {};
|
: build.config || {};
|
||||||
const buildOptions: BuildOptions = {
|
const buildOptions: BuildOptions = {
|
||||||
@@ -448,6 +468,25 @@ async function doBuild(
|
|||||||
);
|
);
|
||||||
const buildResult = await builder.build(buildOptions);
|
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
|
// Store the build result to generate the final `config.json` after
|
||||||
// all builds have completed
|
// all builds have completed
|
||||||
buildResults.set(build, buildResult);
|
buildResults.set(build, buildResult);
|
||||||
@@ -460,7 +499,7 @@ async function doBuild(
|
|||||||
build,
|
build,
|
||||||
builder,
|
builder,
|
||||||
builderPkg,
|
builderPkg,
|
||||||
vercelConfig
|
localConfig
|
||||||
).then(
|
).then(
|
||||||
override => {
|
override => {
|
||||||
if (override) overrides.push(override);
|
if (override) overrides.push(override);
|
||||||
@@ -469,26 +508,11 @@ async function doBuild(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
} catch (err: any) {
|
} 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);
|
const buildJsonBuild = buildsJsonBuilds.get(build);
|
||||||
if (buildJsonBuild) {
|
if (buildJsonBuild) {
|
||||||
buildJsonBuild.error = toEnumerableError(err);
|
buildJsonBuild.error = toEnumerableError(err);
|
||||||
|
|
||||||
await fs.writeJSON(buildsJsonPath, buildsJson, {
|
|
||||||
spaces: 2,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
throw err;
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,7 +573,7 @@ async function doBuild(
|
|||||||
builds: builderRoutes,
|
builds: builderRoutes,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mergedImages = mergeImages(buildResults.values());
|
const mergedImages = mergeImages(localConfig.images, buildResults.values());
|
||||||
const mergedWildcard = mergeWildcard(buildResults.values());
|
const mergedWildcard = mergeWildcard(buildResults.values());
|
||||||
const mergedOverrides: Record<string, PathOverride> =
|
const mergedOverrides: Record<string, PathOverride> =
|
||||||
overrides.length > 0 ? Object.assign({}, ...overrides) : undefined;
|
overrides.length > 0 ? Object.assign({}, ...overrides) : undefined;
|
||||||
@@ -575,8 +599,6 @@ async function doBuild(
|
|||||||
emoji('success')
|
emoji('success')
|
||||||
)}\n`
|
)}\n`
|
||||||
);
|
);
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandBuild(files: string[], build: Builder): Builder[] {
|
function expandBuild(files: string[], build: Builder): Builder[] {
|
||||||
@@ -618,9 +640,9 @@ function expandBuild(files: string[], build: Builder): Builder[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mergeImages(
|
function mergeImages(
|
||||||
|
images: BuildResultV2Typical['images'],
|
||||||
buildResults: Iterable<BuildResult>
|
buildResults: Iterable<BuildResult>
|
||||||
): BuildResultV2Typical['images'] {
|
): BuildResultV2Typical['images'] {
|
||||||
let images: BuildResultV2Typical['images'] = undefined;
|
|
||||||
for (const result of buildResults) {
|
for (const result of buildResults) {
|
||||||
if ('images' in result && result.images) {
|
if ('images' in result && result.images) {
|
||||||
images = Object.assign({}, images, result.images);
|
images = Object.assign({}, images, result.images);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export const help = () => `
|
|||||||
|
|
||||||
${chalk.dim('Basic')}
|
${chalk.dim('Basic')}
|
||||||
|
|
||||||
billing Manages the account payment methods
|
|
||||||
deploy [path] Performs a deployment ${chalk.bold(
|
deploy [path] Performs a deployment ${chalk.bold(
|
||||||
'(default)'
|
'(default)'
|
||||||
)}
|
)}
|
||||||
@@ -73,7 +72,7 @@ export const help = () => `
|
|||||||
-S, --scope Set a custom scope
|
-S, --scope Set a custom scope
|
||||||
--regions Set default regions to enable the deployment on
|
--regions Set default regions to enable the deployment on
|
||||||
--prod Create a production deployment
|
--prod Create a production deployment
|
||||||
-c, --confirm Confirm default options and skip questions
|
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||||
|
|
||||||
${chalk.dim('Examples:')}
|
${chalk.dim('Examples:')}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user