mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 12:57:46 +00:00
Compare commits
205 Commits
@vercel/py
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d2c0fec89 | ||
|
|
0663524cd7 | ||
|
|
f283b3b106 | ||
|
|
b572ef5d71 | ||
|
|
71e233ce7f | ||
|
|
5669fad6bc | ||
|
|
c294409f5b | ||
|
|
686f78a86e | ||
|
|
c6ed021d2e | ||
|
|
f8cdc943ca | ||
|
|
d8d30b59ec | ||
|
|
a93383cf2e | ||
|
|
49dac4c229 | ||
|
|
1b211f28df | ||
|
|
c2d0887b94 | ||
|
|
fbb8bba4cf | ||
|
|
56cc87fe9d | ||
|
|
0027ffa65b | ||
|
|
0964be1710 | ||
|
|
9618ffe05f | ||
|
|
832ba3fe23 | ||
|
|
253b4fd1d2 | ||
|
|
9e5f17b3c6 | ||
|
|
39d0f8dbfc | ||
|
|
a567d047d1 | ||
|
|
fdfb3a385e | ||
|
|
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 |
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 })
|
||||||
11
.github/workflows/publish.yml
vendored
11
.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: |
|
||||||
@@ -32,8 +39,10 @@ jobs:
|
|||||||
- 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@v3
|
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!');
|
||||||
31
.github/workflows/test-integration-cli.yml
vendored
31
.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
|
|
||||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
|
||||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.13.15'
|
go-version: '1.13.15'
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
|
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
- uses: actions/checkout@v3
|
cache: 'yarn'
|
||||||
with:
|
|
||||||
fetch-depth: 100
|
|
||||||
- run: git --version
|
|
||||||
- run: git fetch origin main --depth=100
|
|
||||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
|
||||||
- run: git diff origin/main...HEAD --name-only
|
|
||||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||||
- run: yarn run build
|
- run: yarn run build
|
||||||
- run: yarn test-integration-cli
|
- run: yarn test-integration-cli
|
||||||
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 }}
|
||||||
|
|||||||
29
.github/workflows/test-unit.yml
vendored
29
.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,25 +28,17 @@ jobs:
|
|||||||
node: [14]
|
node: [14]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Conditionally set remote env
|
|
||||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
|
||||||
run: |
|
|
||||||
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
|
||||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
|
||||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.13.15'
|
go-version: '1.13.15'
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node }}
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 100
|
fetch-depth: 2
|
||||||
- run: git --version
|
- uses: actions/setup-node@v3
|
||||||
- run: git fetch origin main --depth=100
|
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
|
||||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
with:
|
||||||
- run: git diff origin/main...HEAD --name-only
|
node-version: ${{ matrix.node }}
|
||||||
|
cache: 'yarn'
|
||||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||||
- run: yarn run build
|
- run: yarn run build
|
||||||
- run: yarn run lint
|
- run: yarn run lint
|
||||||
|
|||||||
30
.github/workflows/test.yml
vendored
30
.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:
|
||||||
@@ -20,9 +27,13 @@ jobs:
|
|||||||
dplUrl: ${{ steps.waitForTarball.outputs.url }}
|
dplUrl: ${{ steps.waitForTarball.outputs.url }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- run: git --version
|
with:
|
||||||
- run: git fetch origin main
|
fetch-depth: 2
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '1.13.15'
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
|
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'
|
||||||
@@ -52,12 +63,6 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include: ${{ fromJson(needs.setup.outputs['tests']) }}
|
include: ${{ fromJson(needs.setup.outputs['tests']) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Conditionally set remote env
|
|
||||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
|
||||||
run: |
|
|
||||||
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
|
|
||||||
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
|
|
||||||
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
@@ -65,6 +70,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: '1.13.15'
|
go-version: '1.13.15'
|
||||||
- uses: actions/setup-node@v3
|
- 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"
|
||||||
|
|||||||
@@ -380,8 +380,8 @@ This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refere
|
|||||||
|
|
||||||
This is an abstract enumeration type that is implemented by one of the following possible `String` values:
|
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`
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -37,4 +35,4 @@ For details on how to use Vercel, check out our [documentation](https://vercel.c
|
|||||||
|
|
||||||
- [Code of Conduct](./.github/CODE_OF_CONDUCT.md)
|
- [Code of Conduct](./.github/CODE_OF_CONDUCT.md)
|
||||||
- [Contributing Guidelines](./.github/CONTRIBUTING.md)
|
- [Contributing Guidelines](./.github/CONTRIBUTING.md)
|
||||||
- [MIT License](./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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"unzip-stream": "0.3.0"
|
"unzip-stream": "0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "13.1.4",
|
"@types/node": "14.18.33",
|
||||||
"@types/node-fetch": "2.5.4",
|
"@types/node-fetch": "2.5.4",
|
||||||
"@vercel/node": "1.9.0",
|
"@vercel/node": "1.9.0",
|
||||||
"typescript": "3.9.6"
|
"typescript": "3.9.6"
|
||||||
|
|||||||
13
errors/deployment-error.md
Normal file
13
errors/deployment-error.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Deployment failed
|
||||||
|
|
||||||
|
## Why This Error Occurred
|
||||||
|
|
||||||
|
When deploying this project, there was not a successful deployment into the `READY` state.
|
||||||
|
|
||||||
|
## How to Fix It
|
||||||
|
|
||||||
|
This is a generic error to catch problems in the deployment. The error is likely to vary depending on the deployment and the conditions at the time.
|
||||||
|
|
||||||
|
Try looking in the logs for information about the deployment and the failure, this could be done at (vercel.com)[https://vercel.com]. You can also use the `vc logs` command to display the build logs for the deployment.
|
||||||
|
|
||||||
|
This might not be a permanent error and retrying the deployment might also resolve it.
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"astro": "^1.0.0-rc.8"
|
"astro": "^1.0.0-rc.8",
|
||||||
|
"web-vitals": "^3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
examples/astro/src/env.d.ts
vendored
8
examples/astro/src/env.d.ts
vendored
@@ -1 +1,9 @@
|
|||||||
/// <reference types="astro/client" />
|
/// <reference types="astro/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly PUBLIC_VERCEL_ANALYTICS_ID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,19 @@ const { title } = Astro.props as Props;
|
|||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
|
<script>
|
||||||
|
import { webVitals } from "../lib/vitals";
|
||||||
|
|
||||||
|
let analyticsId = import.meta.env.PUBLIC_VERCEL_ANALYTICS_ID;
|
||||||
|
|
||||||
|
if (analyticsId) {
|
||||||
|
webVitals({
|
||||||
|
path: location.pathname,
|
||||||
|
params: location.search,
|
||||||
|
analyticsId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
66
examples/astro/src/lib/vitals.js
Normal file
66
examples/astro/src/lib/vitals.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { onCLS, onFCP, onFID, onLCP, onTTFB } from "web-vitals";
|
||||||
|
|
||||||
|
const vitalsUrl = "https://vitals.vercel-analytics.com/v1/vitals";
|
||||||
|
|
||||||
|
function getConnectionSpeed() {
|
||||||
|
return "connection" in navigator &&
|
||||||
|
navigator["connection"] &&
|
||||||
|
"effectiveType" in navigator["connection"]
|
||||||
|
? // @ts-ignore
|
||||||
|
navigator["connection"]["effectiveType"]
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("web-vitals").Metric} metric
|
||||||
|
* @param {{ params: { [s: string]: any; } | ArrayLike<any>; path: string; analyticsId: string; debug: boolean; }} options
|
||||||
|
*/
|
||||||
|
export function sendToAnalytics(metric, options) {
|
||||||
|
const page = Object.entries(options.params).reduce(
|
||||||
|
(acc, [key, value]) => acc.replace(value, `[${key}]`),
|
||||||
|
options.path
|
||||||
|
);
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
dsn: options.analyticsId,
|
||||||
|
id: metric.id,
|
||||||
|
page,
|
||||||
|
href: location.href,
|
||||||
|
event_name: metric.name,
|
||||||
|
value: metric.value.toString(),
|
||||||
|
speed: getConnectionSpeed(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.debug) {
|
||||||
|
console.log("[Analytics]", metric.name, JSON.stringify(body, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = new Blob([new URLSearchParams(body).toString()], {
|
||||||
|
// This content type is necessary for `sendBeacon`
|
||||||
|
type: "application/x-www-form-urlencoded",
|
||||||
|
});
|
||||||
|
if (navigator.sendBeacon) {
|
||||||
|
navigator.sendBeacon(vitalsUrl, blob);
|
||||||
|
} else
|
||||||
|
fetch(vitalsUrl, {
|
||||||
|
body: blob,
|
||||||
|
method: "POST",
|
||||||
|
credentials: "omit",
|
||||||
|
keepalive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} options
|
||||||
|
*/
|
||||||
|
export function webVitals(options) {
|
||||||
|
try {
|
||||||
|
onFID((metric) => sendToAnalytics(metric, options));
|
||||||
|
onTTFB((metric) => sendToAnalytics(metric, options));
|
||||||
|
onLCP((metric) => sendToAnalytics(metric, options));
|
||||||
|
onCLS((metric) => sendToAnalytics(metric, options));
|
||||||
|
onFCP((metric) => sendToAnalytics(metric, options));
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[Analytics]", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
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
|
||||||
|
|||||||
2673
examples/nextjs/package-lock.json
generated
2673
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,10 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "12.1.4",
|
"eslint": "8.27.0",
|
||||||
"react": "18.0.0",
|
"eslint-config-next": "13.0.3",
|
||||||
"react-dom": "18.0.0"
|
"next": "13.0.3",
|
||||||
},
|
"react": "18.2.0",
|
||||||
"devDependencies": {
|
"react-dom": "18.2.0"
|
||||||
"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,6 +8,13 @@ This directory is a brief example of a [Remix](https://remix.run/docs) site that
|
|||||||
|
|
||||||
_Live Example: https://remix-run-template.vercel.app_
|
_Live Example: https://remix-run-template.vercel.app_
|
||||||
|
|
||||||
|
You can also deploy using the [Vercel CLI](https://vercel.com/cli):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i -g vercel
|
||||||
|
vercel
|
||||||
|
```
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
To run your Remix app locally, make sure your project's local dependencies are installed:
|
To run your Remix app locally, make sure your project's local dependencies are installed:
|
||||||
@@ -23,5 +30,3 @@ npm run dev
|
|||||||
```
|
```
|
||||||
|
|
||||||
Open up [http://localhost:3000](http://localhost:3000) and you should be ready to go!
|
Open up [http://localhost:3000](http://localhost:3000) and you should be ready to go!
|
||||||
|
|
||||||
If you're used to using the `vercel dev` command provided by [Vercel CLI](https://vercel.com/cli) instead, you can also use that, but it's not needed.
|
|
||||||
|
|||||||
@@ -1,4 +1,22 @@
|
|||||||
import { RemixBrowser } from "@remix-run/react";
|
import { RemixBrowser } from "@remix-run/react";
|
||||||
import { hydrate } from "react-dom";
|
import { startTransition, StrictMode } from "react";
|
||||||
|
import { hydrateRoot } from "react-dom/client";
|
||||||
|
|
||||||
hydrate(<RemixBrowser />, document);
|
function hydrate() {
|
||||||
|
startTransition(() => {
|
||||||
|
hydrateRoot(
|
||||||
|
document,
|
||||||
|
<StrictMode>
|
||||||
|
<RemixBrowser />
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.requestIdleCallback) {
|
||||||
|
window.requestIdleCallback(hydrate);
|
||||||
|
} else {
|
||||||
|
// Safari doesn't support requestIdleCallback
|
||||||
|
// https://caniuse.com/requestidlecallback
|
||||||
|
window.setTimeout(hydrate, 1);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ export default function handleRequest(
|
|||||||
responseHeaders: Headers,
|
responseHeaders: Headers,
|
||||||
remixContext: EntryContext
|
remixContext: EntryContext
|
||||||
) {
|
) {
|
||||||
let markup = renderToString(
|
const markup = renderToString(
|
||||||
<RemixServer context={remixContext} url={request.url} />
|
<RemixServer context={remixContext} url={request.url} />
|
||||||
);
|
);
|
||||||
|
|
||||||
responseHeaders.set("Content-Type", "text/html");
|
responseHeaders.set("Content-Type", "text/html");
|
||||||
|
|
||||||
return new Response("<!DOCTYPE html>" + markup, {
|
return new Response("<!DOCTYPE html>" + markup, {
|
||||||
status: responseStatusCode,
|
|
||||||
headers: responseHeaders,
|
headers: responseHeaders,
|
||||||
|
status: responseStatusCode,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,182 +1,34 @@
|
|||||||
import type { LinksFunction, MetaFunction } from "@remix-run/node";
|
import type { MetaFunction } from "@remix-run/node";
|
||||||
import {
|
import {
|
||||||
Link,
|
|
||||||
Links,
|
Links,
|
||||||
LiveReload,
|
LiveReload,
|
||||||
Meta,
|
Meta,
|
||||||
Outlet,
|
Outlet,
|
||||||
Scripts,
|
Scripts,
|
||||||
ScrollRestoration,
|
ScrollRestoration,
|
||||||
useCatch,
|
|
||||||
} from "@remix-run/react";
|
} from "@remix-run/react";
|
||||||
|
import { Analytics } from '@vercel/analytics/react';
|
||||||
|
|
||||||
import darkStylesUrl from "~/styles/dark.css";
|
export const meta: MetaFunction = () => ({
|
||||||
import globalStylesUrl from "~/styles/global.css";
|
|
||||||
|
|
||||||
// https://remix.run/api/conventions#links
|
|
||||||
export let links: LinksFunction = () => {
|
|
||||||
return [
|
|
||||||
{ rel: "stylesheet", href: globalStylesUrl },
|
|
||||||
{
|
|
||||||
rel: "stylesheet",
|
|
||||||
href: darkStylesUrl,
|
|
||||||
media: "(prefers-color-scheme: dark)"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://remix.run/api/conventions#meta
|
|
||||||
export let meta: MetaFunction = () => ({
|
|
||||||
charset: "utf-8",
|
charset: "utf-8",
|
||||||
|
title: "New Remix App",
|
||||||
viewport: "width=device-width,initial-scale=1",
|
viewport: "width=device-width,initial-scale=1",
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://remix.run/api/conventions#default-export
|
|
||||||
// https://remix.run/api/conventions#route-filenames
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
|
||||||
<Document>
|
|
||||||
<Layout>
|
|
||||||
<Outlet />
|
|
||||||
</Layout>
|
|
||||||
</Document>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://remix.run/api/conventions#errorboundary
|
|
||||||
export function ErrorBoundary({ error }: { error: Error }) {
|
|
||||||
console.error(error);
|
|
||||||
return (
|
|
||||||
<Document title="Error!">
|
|
||||||
<Layout>
|
|
||||||
<div>
|
|
||||||
<h1>There was an error</h1>
|
|
||||||
<p>{error.message}</p>
|
|
||||||
<hr />
|
|
||||||
<p>
|
|
||||||
Hey, developer, you should replace this with what you want your
|
|
||||||
users to see.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</Document>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://remix.run/api/conventions#catchboundary
|
|
||||||
export function CatchBoundary() {
|
|
||||||
let caught = useCatch();
|
|
||||||
|
|
||||||
let message;
|
|
||||||
switch (caught.status) {
|
|
||||||
case 401:
|
|
||||||
message = (
|
|
||||||
<p>
|
|
||||||
Oops! Looks like you tried to visit a page that you do not have access
|
|
||||||
to.
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 404:
|
|
||||||
message = (
|
|
||||||
<p>Oops! Looks like you tried to visit a page that does not exist.</p>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(caught.data || caught.statusText);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Document title={`${caught.status} ${caught.statusText}`}>
|
|
||||||
<Layout>
|
|
||||||
<h1>
|
|
||||||
{caught.status}: {caught.statusText}
|
|
||||||
</h1>
|
|
||||||
{message}
|
|
||||||
</Layout>
|
|
||||||
</Document>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Document({
|
|
||||||
children,
|
|
||||||
title
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
title?: string;
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
{title ? <title>{title}</title> : null}
|
|
||||||
<Meta />
|
<Meta />
|
||||||
<Links />
|
<Links />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{children}
|
<Outlet />
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
<Scripts />
|
<Scripts />
|
||||||
<LiveReload />
|
<LiveReload />
|
||||||
|
<Analytics />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Layout({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<div className="remix-app">
|
|
||||||
<header className="remix-app__header">
|
|
||||||
<div className="container remix-app__header-content">
|
|
||||||
<Link to="/" title="Remix" className="remix-app__header-home-link">
|
|
||||||
<RemixLogo />
|
|
||||||
</Link>
|
|
||||||
<nav aria-label="Main navigation" className="remix-app__header-nav">
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<Link to="/">Home</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://remix.run/docs">Remix Docs</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/remix-run/remix">GitHub</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div className="remix-app__main">
|
|
||||||
<div className="container remix-app__main-content">{children}</div>
|
|
||||||
</div>
|
|
||||||
<footer className="remix-app__footer">
|
|
||||||
<div className="container remix-app__footer-content">
|
|
||||||
<p>© You!</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function RemixLogo() {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 659 165"
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
||||||
aria-labelledby="remix-run-logo-title"
|
|
||||||
role="img"
|
|
||||||
width="106"
|
|
||||||
height="30"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<title id="remix-run-logo-title">Remix Logo</title>
|
|
||||||
<path d="M0 161V136H45.5416C53.1486 136 54.8003 141.638 54.8003 145V161H0Z M133.85 124.16C135.3 142.762 135.3 151.482 135.3 161H92.2283C92.2283 158.927 92.2653 157.03 92.3028 155.107C92.4195 149.128 92.5411 142.894 91.5717 130.304C90.2905 111.872 82.3473 107.776 67.7419 107.776H54.8021H0V74.24H69.7918C88.2407 74.24 97.4651 68.632 97.4651 53.784C97.4651 40.728 88.2407 32.816 69.7918 32.816H0V0H77.4788C119.245 0 140 19.712 140 51.2C140 74.752 125.395 90.112 105.665 92.672C122.32 96 132.057 105.472 133.85 124.16Z" />
|
|
||||||
<path d="M229.43 120.576C225.59 129.536 218.422 133.376 207.158 133.376C194.614 133.376 184.374 126.72 183.35 112.64H263.478V101.12C263.478 70.1437 243.254 44.0317 205.11 44.0317C169.526 44.0317 142.902 69.8877 142.902 105.984C142.902 142.336 169.014 164.352 205.622 164.352C235.83 164.352 256.822 149.76 262.71 123.648L229.43 120.576ZM183.862 92.6717C185.398 81.9197 191.286 73.7277 204.598 73.7277C216.886 73.7277 223.542 82.4317 224.054 92.6717H183.862Z" />
|
|
||||||
<path d="M385.256 66.5597C380.392 53.2477 369.896 44.0317 349.672 44.0317C332.52 44.0317 320.232 51.7117 314.088 64.2557V47.1037H272.616V161.28H314.088V105.216C314.088 88.0638 318.952 76.7997 332.52 76.7997C345.064 76.7997 348.136 84.9917 348.136 100.608V161.28H389.608V105.216C389.608 88.0638 394.216 76.7997 408.04 76.7997C420.584 76.7997 423.4 84.9917 423.4 100.608V161.28H464.872V89.5997C464.872 65.7917 455.656 44.0317 424.168 44.0317C404.968 44.0317 391.4 53.7597 385.256 66.5597Z" />
|
|
||||||
<path d="M478.436 47.104V161.28H519.908V47.104H478.436ZM478.18 36.352H520.164V0H478.18V36.352Z" />
|
|
||||||
<path d="M654.54 47.1035H611.788L592.332 74.2395L573.388 47.1035H527.564L568.78 103.168L523.98 161.28H566.732L589.516 130.304L612.3 161.28H658.124L613.068 101.376L654.54 47.1035Z" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import type { MetaFunction, LinksFunction } from "@remix-run/node";
|
|
||||||
import { Outlet } from "@remix-run/react";
|
|
||||||
|
|
||||||
import stylesUrl from "~/styles/demos/about.css";
|
|
||||||
|
|
||||||
export let meta: MetaFunction = () => {
|
|
||||||
return {
|
|
||||||
title: "About Remix"
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export let links: LinksFunction = () => {
|
|
||||||
return [{ rel: "stylesheet", href: stylesUrl }];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Index() {
|
|
||||||
return (
|
|
||||||
<div className="about">
|
|
||||||
<div className="about__intro">
|
|
||||||
<h2>About Us</h2>
|
|
||||||
<p>
|
|
||||||
Ok, so this page isn't really <em>about us</em>, but we did want to
|
|
||||||
show you a few more things Remix can do.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Did you notice that things look a little different on this page? The
|
|
||||||
CSS that we import in the route file and include in its{" "}
|
|
||||||
<code>links</code> export is only included on this route and its
|
|
||||||
children.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Wait a sec...<em>its children</em>? To understand what we mean by
|
|
||||||
this,{" "}
|
|
||||||
<a href="https://remix.run/docs/en/v1/guides/routing">
|
|
||||||
read all about nested routes in the docs
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
<hr />
|
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Link } from "@remix-run/react";
|
|
||||||
|
|
||||||
export default function AboutIndex() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
You are looking at the index route for the <code>/about</code> URL
|
|
||||||
segment, but there are nested routes as well!
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>
|
|
||||||
<Link to="whoa">Check out one of them here.</Link>
|
|
||||||
</strong>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Link } from "@remix-run/react";
|
|
||||||
|
|
||||||
export default function AboutIndex() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
Whoa, this is a nested route! We render the <code>/about</code> layout
|
|
||||||
route component, and its <code>Outlet</code> renders our route
|
|
||||||
component. 🤯
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>
|
|
||||||
<Link to="..">
|
|
||||||
Go back to the <code>/about</code> index.
|
|
||||||
</Link>
|
|
||||||
</strong>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
import type { ActionFunction } from "@remix-run/node";
|
|
||||||
import { json, redirect } from "@remix-run/node";
|
|
||||||
import { Form, useActionData } from "@remix-run/react";
|
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
|
|
||||||
export function meta() {
|
|
||||||
return { title: "Actions Demo" };
|
|
||||||
}
|
|
||||||
|
|
||||||
// When your form sends a POST, the action is called on the server.
|
|
||||||
// - https://remix.run/api/conventions#action
|
|
||||||
// - https://remix.run/guides/data-updates
|
|
||||||
export let action: ActionFunction = async ({ request }) => {
|
|
||||||
let formData = await request.formData();
|
|
||||||
let answer = formData.get("answer");
|
|
||||||
|
|
||||||
// Typical action workflows start with validating the form data that just came
|
|
||||||
// over the network. Clientside validation is fine, but you definitely need it
|
|
||||||
// server side. If there's a problem, return the the data and the component
|
|
||||||
// can render it.
|
|
||||||
if (typeof answer !== "string") {
|
|
||||||
return json("Come on, at least try!", { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (answer !== "egg") {
|
|
||||||
return json(`Sorry, ${answer} is not right.`, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, if the data is valid, you'll typically write to a database or send or
|
|
||||||
// email or log the user in, etc. It's recommended to redirect after a
|
|
||||||
// successful action, even if it's to the same place so that non-JavaScript workflows
|
|
||||||
// from the browser doesn't repost the data if the user clicks back.
|
|
||||||
return redirect("/demos/correct");
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ActionsDemo() {
|
|
||||||
// https://remix.run/api/remix#useactiondata
|
|
||||||
let actionMessage = useActionData<string>();
|
|
||||||
let answerRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
// This form works without JavaScript, but when we have JavaScript we can make
|
|
||||||
// the experience better by selecting the input on wrong answers! Go ahead, disable
|
|
||||||
// JavaScript in your browser and see what happens.
|
|
||||||
useEffect(() => {
|
|
||||||
if (actionMessage && answerRef.current) {
|
|
||||||
answerRef.current.select();
|
|
||||||
}
|
|
||||||
}, [actionMessage]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="remix__page">
|
|
||||||
<main>
|
|
||||||
<h2>Actions!</h2>
|
|
||||||
<p>
|
|
||||||
This form submission will send a post request that we handle in our
|
|
||||||
`action` export. Any route can export an action to handle data
|
|
||||||
mutations.
|
|
||||||
</p>
|
|
||||||
<Form method="post" className="remix__form">
|
|
||||||
<h3>Post an Action</h3>
|
|
||||||
<p>
|
|
||||||
<i>What is more useful when it is broken?</i>
|
|
||||||
</p>
|
|
||||||
<label>
|
|
||||||
<div>Answer:</div>
|
|
||||||
<input ref={answerRef} name="answer" type="text" />
|
|
||||||
</label>
|
|
||||||
<div>
|
|
||||||
<button>Answer!</button>
|
|
||||||
</div>
|
|
||||||
{actionMessage ? (
|
|
||||||
<p>
|
|
||||||
<b>{actionMessage}</b>
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
</Form>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<aside>
|
|
||||||
<h3>Additional Resources</h3>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Guide:{" "}
|
|
||||||
<a href="https://remix.run/guides/data-writes">Data Writes</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
API:{" "}
|
|
||||||
<a href="https://remix.run/api/conventions#action">
|
|
||||||
Route Action Export
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
API:{" "}
|
|
||||||
<a href="https://remix.run/api/remix#useactiondata">
|
|
||||||
<code>useActionData</code>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export default function NiceWork() {
|
|
||||||
return <h1>You got it right!</h1>;
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import type { MetaFunction } from "@remix-run/node";
|
|
||||||
import { json } from "@remix-run/node";
|
|
||||||
import { Link, Outlet, useCatch, useLoaderData } from "@remix-run/react";
|
|
||||||
|
|
||||||
export let meta: MetaFunction = () => ({ title: "Boundaries Demo" });
|
|
||||||
|
|
||||||
export default function Boundaries() {
|
|
||||||
return (
|
|
||||||
<div className="remix__page">
|
|
||||||
<main>
|
|
||||||
<Outlet />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<aside>
|
|
||||||
<h2>Click these Links</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<Link to=".">Start over</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link to="one">
|
|
||||||
Param: <i>one</i>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link to="two">
|
|
||||||
Param: <i>two</i>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link to="this-record-does-not-exist">This will be a 404</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link to="shh-its-a-secret">And this will be 401 Unauthorized</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link to="kaboom">This one will throw an error</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
import type { LoaderFunction, MetaFunction } from "@remix-run/node";
|
|
||||||
import { json } from "@remix-run/node";
|
|
||||||
import { Link, useCatch, useLoaderData } from "@remix-run/react";
|
|
||||||
|
|
||||||
// The `$` in route filenames becomes a pattern that's parsed from the URL and
|
|
||||||
// passed to your loaders so you can look up data.
|
|
||||||
// - https://remix.run/api/conventions#loader-params
|
|
||||||
export let loader: LoaderFunction = async ({ params }) => {
|
|
||||||
// pretend like we're using params.id to look something up in the db
|
|
||||||
|
|
||||||
if (params.id === "this-record-does-not-exist") {
|
|
||||||
// If the record doesn't exist we can't render the route normally, so
|
|
||||||
// instead we throw a 404 reponse to stop running code here and show the
|
|
||||||
// user the catch boundary.
|
|
||||||
throw new Response("Not Found", { status: 404 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// now pretend like the record exists but the user just isn't authorized to
|
|
||||||
// see it.
|
|
||||||
if (params.id === "shh-its-a-secret") {
|
|
||||||
// Again, we can't render the component if the user isn't authorized. You
|
|
||||||
// can even put data in the response that might help the user rectify the
|
|
||||||
// issue! Like emailing the webmaster for access to the page. (Oh, right,
|
|
||||||
// `json` is just a Response helper that makes it easier to send JSON
|
|
||||||
// responses).
|
|
||||||
throw json({ webmasterEmail: "hello@remix.run" }, { status: 401 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sometimes your code just blows up and you never anticipated it. Remix will
|
|
||||||
// automatically catch it and send the UI to the error boundary.
|
|
||||||
if (params.id === "kaboom") {
|
|
||||||
lol();
|
|
||||||
}
|
|
||||||
|
|
||||||
// but otherwise the record was found, user has access, so we can do whatever
|
|
||||||
// else we needed to in the loader and return the data. (This is boring, we're
|
|
||||||
// just gonna return the params.id).
|
|
||||||
return { param: params.id };
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ParamDemo() {
|
|
||||||
let data = useLoaderData();
|
|
||||||
return (
|
|
||||||
<h1>
|
|
||||||
The param is <i style={{ color: "red" }}>{data.param}</i>
|
|
||||||
</h1>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://remix.run/api/conventions#catchboundary
|
|
||||||
// https://remix.run/api/remix#usecatch
|
|
||||||
// https://remix.run/api/guides/not-found
|
|
||||||
export function CatchBoundary() {
|
|
||||||
let caught = useCatch();
|
|
||||||
|
|
||||||
let message: React.ReactNode;
|
|
||||||
switch (caught.status) {
|
|
||||||
case 401:
|
|
||||||
message = (
|
|
||||||
<p>
|
|
||||||
Looks like you tried to visit a page that you do not have access to.
|
|
||||||
Maybe ask the webmaster ({caught.data.webmasterEmail}) for access.
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
case 404:
|
|
||||||
message = (
|
|
||||||
<p>Looks like you tried to visit a page that does not exist.</p>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
message = (
|
|
||||||
<p>
|
|
||||||
There was a problem with your request!
|
|
||||||
<br />
|
|
||||||
{caught.status} {caught.statusText}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h2>Oops!</h2>
|
|
||||||
<p>{message}</p>
|
|
||||||
<p>
|
|
||||||
(Isn't it cool that the user gets to stay in context and try a different
|
|
||||||
link in the parts of the UI that didn't blow up?)
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://remix.run/api/conventions#errorboundary
|
|
||||||
// https://remix.run/api/guides/not-found
|
|
||||||
export function ErrorBoundary({ error }: { error: Error }) {
|
|
||||||
console.error(error);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h2>Error!</h2>
|
|
||||||
<p>{error.message}</p>
|
|
||||||
<p>
|
|
||||||
(Isn't it cool that the user gets to stay in context and try a different
|
|
||||||
link in the parts of the UI that didn't blow up?)
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export let meta: MetaFunction = ({ data }) => {
|
|
||||||
return {
|
|
||||||
title: data ? `Param: ${data.param}` : "Oops...",
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import type { LoaderFunction } from "@remix-run/node";
|
|
||||||
import { json } from "@remix-run/node";
|
|
||||||
import { Link, Outlet, useCatch, useLoaderData } from "@remix-run/react";
|
|
||||||
|
|
||||||
export default function Boundaries() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h2>Params</h2>
|
|
||||||
<p>
|
|
||||||
When you name a route segment with $ like{" "}
|
|
||||||
<code>routes/users/$userId.js</code>, the $ segment will be parsed from
|
|
||||||
the URL and sent to your loaders and actions by the same name.
|
|
||||||
</p>
|
|
||||||
<h2>Errors</h2>
|
|
||||||
<p>
|
|
||||||
When a route throws and error in it's action, loader, or component,
|
|
||||||
Remix automatically catches it, won't even try to render the component,
|
|
||||||
but it will render the route's ErrorBoundary instead. If the route
|
|
||||||
doesn't have one, it will bubble up to the routes above it until it hits
|
|
||||||
the root.
|
|
||||||
</p>
|
|
||||||
<p>So be as granular as you want with your error handling.</p>
|
|
||||||
<h2>Not Found</h2>
|
|
||||||
<p>
|
|
||||||
(and other{" "}
|
|
||||||
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses">
|
|
||||||
client errors
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Loaders and Actions can throw a <code>Response</code> instead of an
|
|
||||||
error and Remix will render the CatchBoundary instead of the component.
|
|
||||||
This is great when loading data from a database isn't found. As soon as
|
|
||||||
you know you can't render the component normally, throw a 404 response
|
|
||||||
and send your app into the catch boundary. Just like error boundaries,
|
|
||||||
catch boundaries bubble, too.
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,101 +1,32 @@
|
|||||||
import type { MetaFunction, LoaderFunction } from "@remix-run/node";
|
|
||||||
import { json } from "@remix-run/node";
|
|
||||||
import { Link, useLoaderData } from "@remix-run/react";
|
|
||||||
|
|
||||||
type IndexData = {
|
|
||||||
resources: Array<{ name: string; url: string }>;
|
|
||||||
demos: Array<{ name: string; to: string }>;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Loaders provide data to components and are only ever called on the server, so
|
|
||||||
// you can connect to a database or run any server side code you want right next
|
|
||||||
// to the component that renders it.
|
|
||||||
// https://remix.run/api/conventions#loader
|
|
||||||
export let loader: LoaderFunction = () => {
|
|
||||||
let data: IndexData = {
|
|
||||||
resources: [
|
|
||||||
{
|
|
||||||
name: "Remix Docs",
|
|
||||||
url: "https://remix.run/docs"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "React Router Docs",
|
|
||||||
url: "https://reactrouter.com/docs"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Remix Discord",
|
|
||||||
url: "https://discord.gg/VBePs6d"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
demos: [
|
|
||||||
{
|
|
||||||
to: "demos/actions",
|
|
||||||
name: "Actions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "demos/about",
|
|
||||||
name: "Nested Routes, CSS loading/unloading"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "demos/params",
|
|
||||||
name: "URL Params and Error Boundaries"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://remix.run/api/remix#json
|
|
||||||
return json(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://remix.run/api/conventions#meta
|
|
||||||
export let meta: MetaFunction = () => {
|
|
||||||
return {
|
|
||||||
title: "Remix Starter",
|
|
||||||
description: "Welcome to remix!"
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://remix.run/guides/routing#index-routes
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
let data = useLoaderData<IndexData>();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="remix__page">
|
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
|
||||||
<main>
|
<h1>Welcome to Remix</h1>
|
||||||
<h2>Welcome to Remix!</h2>
|
<ul>
|
||||||
<p>We're stoked that you're here. 🥳</p>
|
<li>
|
||||||
<p>
|
<a
|
||||||
Feel free to take a look around the code to see how Remix does things,
|
target="_blank"
|
||||||
it might be a bit different than what you’re used to. When you're
|
href="https://remix.run/tutorials/blog"
|
||||||
ready to dive deeper, we've got plenty of resources to get you
|
rel="noreferrer"
|
||||||
up-and-running quickly.
|
>
|
||||||
</p>
|
15m Quickstart Blog Tutorial
|
||||||
<p>
|
</a>
|
||||||
Check out all the demos in this starter, and then just delete the{" "}
|
</li>
|
||||||
<code>app/routes/demos</code> and <code>app/styles/demos</code>{" "}
|
<li>
|
||||||
folders when you're ready to turn this into your next project.
|
<a
|
||||||
</p>
|
target="_blank"
|
||||||
</main>
|
href="https://remix.run/tutorials/jokes"
|
||||||
<aside>
|
rel="noreferrer"
|
||||||
<h2>Demos In This App</h2>
|
>
|
||||||
<ul>
|
Deep Dive Jokes App Tutorial
|
||||||
{data.demos.map(demo => (
|
</a>
|
||||||
<li key={demo.to} className="remix__page__resource">
|
</li>
|
||||||
<Link to={demo.to} prefetch="intent">
|
<li>
|
||||||
{demo.name}
|
<a target="_blank" href="https://remix.run/docs" rel="noreferrer">
|
||||||
</Link>
|
Remix Docs
|
||||||
</li>
|
</a>
|
||||||
))}
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Resources</h2>
|
|
||||||
<ul>
|
|
||||||
{data.resources.map(resource => (
|
|
||||||
<li key={resource.url} className="remix__page__resource">
|
|
||||||
<a href={resource.url}>{resource.name}</a>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
:root {
|
|
||||||
--color-foreground: hsl(0, 0%, 100%);
|
|
||||||
--color-background: hsl(0, 0%, 7%);
|
|
||||||
--color-links: hsl(213, 100%, 73%);
|
|
||||||
--color-links-hover: hsl(213, 100%, 80%);
|
|
||||||
--color-border: hsl(0, 0%, 25%);
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* Whoa whoa whoa, wait a sec...why are we overriding global CSS selectors?
|
|
||||||
* Isn't that kind of scary? How do we know this won't have side effects?
|
|
||||||
*
|
|
||||||
* In Remix, CSS that is included in a route file will *only* show up on that
|
|
||||||
* route (and for nested routes, its children). When the user navigates away
|
|
||||||
* from that route the CSS files linked from those routes will be automatically
|
|
||||||
* unloaded, making your styles much easier to predict and control.
|
|
||||||
*
|
|
||||||
* Read more about styling routes in the docs:
|
|
||||||
* https://remix.run/guides/styling
|
|
||||||
*/
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--color-foreground: hsl(0, 0%, 7%);
|
|
||||||
--color-background: hsl(56, 100%, 50%);
|
|
||||||
--color-links: hsl(345, 56%, 39%);
|
|
||||||
--color-links-hover: hsl(345, 51%, 49%);
|
|
||||||
--color-border: rgb(184, 173, 20);
|
|
||||||
--font-body: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
|
||||||
Liberation Mono, Courier New, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about__intro {
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
/*
|
|
||||||
* You can just delete everything here or keep whatever you like, it's just a
|
|
||||||
* quick baseline!
|
|
||||||
*/
|
|
||||||
:root {
|
|
||||||
--color-foreground: hsl(0, 0%, 7%);
|
|
||||||
--color-background: hsl(0, 0%, 100%);
|
|
||||||
--color-links: hsl(213, 100%, 52%);
|
|
||||||
--color-links-hover: hsl(213, 100%, 43%);
|
|
||||||
--color-border: hsl(0, 0%, 82%);
|
|
||||||
--font-body: -apple-system, "Segoe UI", Helvetica Neue, Helvetica, Roboto,
|
|
||||||
Arial, sans-serif, system-ui, "Apple Color Emoji", "Segoe UI Emoji";
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
:-moz-focusring {
|
|
||||||
outline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
:focus {
|
|
||||||
outline: var(--color-links) solid 2px;
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
background-color: var(--color-background);
|
|
||||||
color: var(--color-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: var(--font-body);
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--color-links);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: var(--color-links-hover);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
display: block;
|
|
||||||
height: 1px;
|
|
||||||
border: 0;
|
|
||||||
background-color: var(--color-border);
|
|
||||||
margin-top: 2rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:where([type="text"]),
|
|
||||||
input:where([type="search"]) {
|
|
||||||
display: block;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
width: 100%;
|
|
||||||
font: inherit;
|
|
||||||
line-height: 1;
|
|
||||||
height: calc(1ch + 1.5em);
|
|
||||||
padding-right: 0.5em;
|
|
||||||
padding-left: 0.5em;
|
|
||||||
background-color: hsl(0 0% 100% / 20%);
|
|
||||||
color: var(--color-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
padding: 0;
|
|
||||||
margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap;
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
--gutter: 16px;
|
|
||||||
width: 1024px;
|
|
||||||
max-width: calc(100% - var(--gutter) * 2);
|
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 100vh;
|
|
||||||
min-height: calc(100vh - env(safe-area-inset-bottom));
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app > * {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__header {
|
|
||||||
padding-top: 1rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__header-content {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__header-home-link {
|
|
||||||
width: 106px;
|
|
||||||
height: 30px;
|
|
||||||
color: var(--color-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__header-nav ul {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__header-nav li {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__main {
|
|
||||||
flex: 1 1 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__footer {
|
|
||||||
padding-top: 1rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__footer-content {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__page {
|
|
||||||
--gap: 1rem;
|
|
||||||
--space: 2rem;
|
|
||||||
display: grid;
|
|
||||||
grid-auto-rows: min-content;
|
|
||||||
gap: var(--gap);
|
|
||||||
padding-top: var(--space);
|
|
||||||
padding-bottom: var(--space);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print, screen and (min-width: 640px) {
|
|
||||||
.remix__page {
|
|
||||||
--gap: 2rem;
|
|
||||||
grid-auto-rows: unset;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 1024px) {
|
|
||||||
.remix__page {
|
|
||||||
--gap: 4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__page > main > :first-child {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__page > main > :last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__page > aside {
|
|
||||||
margin: 0;
|
|
||||||
padding: 1.5ch 2ch;
|
|
||||||
border: solid 1px var(--color-border);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__page > aside > :first-child {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__page > aside > :last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__form > * {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
23926
examples/remix/package-lock.json
generated
Normal file
23926
examples/remix/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,21 +6,22 @@
|
|||||||
"dev": "remix dev"
|
"dev": "remix dev"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@remix-run/node": "^1.5.1",
|
"@remix-run/node": "^1.7.5",
|
||||||
"@remix-run/react": "^1.5.1",
|
"@remix-run/react": "^1.7.5",
|
||||||
"@remix-run/vercel": "^1.5.1",
|
"@remix-run/vercel": "^1.7.5",
|
||||||
"@vercel/node": "^2.0.0",
|
"@vercel/analytics": "^0.1.3",
|
||||||
"react": "^17.0.2",
|
"@vercel/node": "^2.4.4",
|
||||||
"react-dom": "^17.0.2"
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@remix-run/dev": "^1.5.1",
|
"@remix-run/dev": "^1.7.5",
|
||||||
"@remix-run/eslint-config": "^1.5.1",
|
"@remix-run/eslint-config": "^1.7.5",
|
||||||
"@remix-run/serve": "^1.5.1",
|
"@remix-run/serve": "^1.7.5",
|
||||||
"@types/react": "^17.0.45",
|
"@types/react": "^18.0.15",
|
||||||
"@types/react-dom": "^17.0.17",
|
"@types/react-dom": "^18.0.6",
|
||||||
"eslint": "^8.15.0",
|
"eslint": "^8.23.1",
|
||||||
"typescript": "^4.6.4"
|
"typescript": "^4.7.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
/**
|
/** @type {import('@remix-run/dev').AppConfig} */
|
||||||
* @type {import('@remix-run/dev').AppConfig}
|
|
||||||
*/
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
serverBuildTarget: "vercel",
|
serverBuildTarget: "vercel",
|
||||||
// When running locally in development mode, we use the built in remix
|
// When running locally in development mode, we use the built in remix
|
||||||
|
|||||||
2
examples/remix/remix.env.d.ts
vendored
2
examples/remix/remix.env.d.ts
vendored
@@ -1,2 +1,2 @@
|
|||||||
/// <reference types="@remix-run/dev" />
|
/// <reference types="@remix-run/dev" />
|
||||||
/// <reference types="@remix-run/node/globals" />
|
/// <reference types="@remix-run/node" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as build from "@remix-run/dev/server-build";
|
|
||||||
import { createRequestHandler } from "@remix-run/vercel";
|
import { createRequestHandler } from "@remix-run/vercel";
|
||||||
|
import * as build from "@remix-run/dev/server-build";
|
||||||
|
|
||||||
export default createRequestHandler({ build, mode: process.env.NODE_ENV });
|
export default createRequestHandler({ build, mode: process.env.NODE_ENV });
|
||||||
|
|||||||
@@ -2,12 +2,16 @@
|
|||||||
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["DOM", "DOM.Iterable", "ES2019"],
|
"lib": ["DOM", "DOM.Iterable", "ES2019"],
|
||||||
|
"isolatedModules": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"target": "ES2019",
|
"target": "ES2019",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["./app/*"]
|
"~/*": ["./app/*"]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,12 +7,16 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"solid-app-router": "^0.3.2",
|
"solid-start-vercel": "^0.2.0",
|
||||||
"solid-js": "^1.3.15",
|
"typescript": "^4.8.3",
|
||||||
"solid-meta": "^0.27.3",
|
"vite": "^3.1.0"
|
||||||
"solid-start": "next",
|
},
|
||||||
"solid-start-vercel": "next",
|
"dependencies": {
|
||||||
"vite": "^2.9.9"
|
"@solidjs/meta": "^0.28.2",
|
||||||
|
"@solidjs/router": "^0.5.0",
|
||||||
|
"solid-js": "^1.6.0",
|
||||||
|
"solid-start": "^0.2.0",
|
||||||
|
"undici": "^5.11.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "16.x"
|
"node": "16.x"
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { hydrate } from "solid-js/web";
|
import { mount, StartClient } from 'solid-start/entry-client';
|
||||||
import { StartClient } from "solid-start/entry-client";
|
|
||||||
|
|
||||||
hydrate(() => <StartClient />, document);
|
mount(() => <StartClient />, document);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { StartServer, createHandler, renderAsync } from "solid-start/entry-server";
|
import {
|
||||||
import { inlineServerModules } from "solid-start/server";
|
StartServer,
|
||||||
|
createHandler,
|
||||||
|
renderAsync,
|
||||||
|
} from 'solid-start/entry-server';
|
||||||
|
|
||||||
export default createHandler(
|
export default createHandler(
|
||||||
inlineServerModules,
|
renderAsync(event => <StartServer event={event} />)
|
||||||
renderAsync((context) => <StartServer context={context} />)
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,25 +1,33 @@
|
|||||||
// @refresh reload
|
// @refresh reload
|
||||||
import { Links, Meta, Routes, Scripts } from "solid-start/root";
|
import {
|
||||||
import { ErrorBoundary } from "solid-start/error-boundary";
|
Html,
|
||||||
import { Suspense } from "solid-js";
|
Head,
|
||||||
|
Body,
|
||||||
|
Meta,
|
||||||
|
Routes,
|
||||||
|
FileRoutes,
|
||||||
|
Scripts,
|
||||||
|
ErrorBoundary,
|
||||||
|
} from 'solid-start';
|
||||||
|
import { Suspense } from 'solid-js';
|
||||||
|
|
||||||
export default function Root() {
|
export default function Root() {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<Html lang="en">
|
||||||
<head>
|
<Head>
|
||||||
<meta charset="utf-8" />
|
<Meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<Meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<Meta />
|
</Head>
|
||||||
<Links />
|
<Body>
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<Routes />
|
<Routes>
|
||||||
|
<FileRoutes />
|
||||||
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
<Scripts />
|
<Scripts />
|
||||||
</body>
|
</Body>
|
||||||
</html>
|
</Html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ export default function Home() {
|
|||||||
<Counter />
|
<Counter />
|
||||||
<p>
|
<p>
|
||||||
Visit{" "}
|
Visit{" "}
|
||||||
<a href="https://solidjs.com" target="_blank">
|
<a href="https://start.solidjs.com" target="_blank">
|
||||||
solidjs.com
|
start.solidjs.com
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
to learn how to build Solid apps.
|
to learn how to build Solid apps.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"build": {
|
|
||||||
"env": {
|
|
||||||
"ENABLE_VC_BUILD": "1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import solid from "solid-start";
|
import solid from "solid-start/vite";
|
||||||
import vercel from "solid-start-vercel";
|
import vercel from "solid-start-vercel";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,21 +8,9 @@ Everything you need to build a Svelte project, powered by [`create-svelte`](http
|
|||||||
|
|
||||||
_Live Example: https://sveltekit-template.vercel.app_
|
_Live Example: https://sveltekit-template.vercel.app_
|
||||||
|
|
||||||
## Creating a project
|
|
||||||
|
|
||||||
If you're seeing this, you've probably already done this step. Congrats!
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# create a new project in the current directory
|
|
||||||
npm init svelte
|
|
||||||
|
|
||||||
# create a new project in my-app
|
|
||||||
npm init svelte my-app
|
|
||||||
```
|
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
Once you've created a project and installed dependencies with `pnpm install`, start a development server:
|
Once you've installed dependencies with `pnpm install`, start a development server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run dev
|
pnpm run dev
|
||||||
|
|||||||
@@ -10,4 +10,8 @@
|
|||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true
|
"strict": true
|
||||||
}
|
}
|
||||||
|
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
|
||||||
|
//
|
||||||
|
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||||
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "svelte-kit dev",
|
"dev": "vite dev",
|
||||||
"build": "svelte-kit build",
|
"build": "vite build",
|
||||||
"package": "svelte-kit package",
|
"package": "svelte-kit package",
|
||||||
"preview": "svelte-kit preview",
|
"preview": "vite preview",
|
||||||
"prepare": "svelte-kit sync",
|
|
||||||
"check": "svelte-check --tsconfig ./jsconfig.json",
|
"check": "svelte-check --tsconfig ./jsconfig.json",
|
||||||
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
|
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
|
||||||
"lint": "prettier --check --plugin-search-dir=. .",
|
"lint": "prettier --check .",
|
||||||
"format": "prettier --write --plugin-search-dir=. ."
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "1.0.0-next.50",
|
"@sveltejs/adapter-auto": "next",
|
||||||
"@sveltejs/kit": "1.0.0-next.347",
|
"@sveltejs/kit": "next",
|
||||||
"@types/cookie": "^0.4.1",
|
"@types/cookie": "^0.5.1",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.6.2",
|
||||||
"prettier-plugin-svelte": "^2.5.0",
|
"prettier-plugin-svelte": "^2.7.0",
|
||||||
"svelte": "^3.46.0",
|
"svelte": "^3.46.0",
|
||||||
"svelte-check": "^2.2.6",
|
"svelte-check": "^2.7.1",
|
||||||
"typescript": "~4.6.2"
|
"typescript": "^4.7.4",
|
||||||
|
"vite": "^3.0.8"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
1458
examples/sveltekit/pnpm-lock.yaml
generated
Normal file
1458
examples/sveltekit/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
7
examples/sveltekit/src/app.d.ts
vendored
7
examples/sveltekit/src/app.d.ts
vendored
@@ -1,7 +1,6 @@
|
|||||||
/// <reference types="@sveltejs/kit" />
|
|
||||||
|
|
||||||
// See https://kit.svelte.dev/docs/types#app
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
|
// and what to do when importing types
|
||||||
declare namespace App {
|
declare namespace App {
|
||||||
interface Locals {
|
interface Locals {
|
||||||
userid: string;
|
userid: string;
|
||||||
@@ -9,7 +8,7 @@ declare namespace App {
|
|||||||
|
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
|
|
||||||
// interface Session {}
|
// interface PrivateEnv {}
|
||||||
|
|
||||||
// interface Stuff {}
|
// interface PublicEnv {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,107 +1,107 @@
|
|||||||
<script>
|
<script>
|
||||||
import { spring } from 'svelte/motion';
|
import { spring } from 'svelte/motion';
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
const displayed_count = spring();
|
const displayed_count = spring();
|
||||||
$: displayed_count.set(count);
|
$: displayed_count.set(count);
|
||||||
$: offset = modulo($displayed_count, 1);
|
$: offset = modulo($displayed_count, 1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} n
|
* @param {number} n
|
||||||
* @param {number} m
|
* @param {number} m
|
||||||
*/
|
*/
|
||||||
function modulo(n, m) {
|
function modulo(n, m) {
|
||||||
// handle negative numbers
|
// handle negative numbers
|
||||||
return ((n % m) + m) % m;
|
return ((n % m) + m) % m;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="counter">
|
<div class="counter">
|
||||||
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
||||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||||
<path d="M0,0.5 L1,0.5" />
|
<path d="M0,0.5 L1,0.5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="counter-viewport">
|
<div class="counter-viewport">
|
||||||
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
||||||
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
||||||
<strong>{Math.floor($displayed_count)}</strong>
|
<strong>{Math.floor($displayed_count)}</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
||||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||||
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.counter {
|
.counter {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter button {
|
.counter button {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 0;
|
border: 0;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter button:hover {
|
.counter button:hover {
|
||||||
background-color: var(--secondary-color);
|
background-color: var(--secondary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
height: 25%;
|
height: 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
path {
|
path {
|
||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
stroke-width: 2px;
|
stroke-width: 2px;
|
||||||
stroke: var(--text-color);
|
stroke: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter-viewport {
|
.counter-viewport {
|
||||||
width: 8em;
|
width: 8em;
|
||||||
height: 4em;
|
height: 4em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter-viewport strong {
|
.counter-viewport strong {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
font-size: 4rem;
|
font-size: 4rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter-digits {
|
.counter-digits {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
top: -100%;
|
top: -100%;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ import { invalidate } from '$app/navigation';
|
|||||||
export function enhance(form, { pending, error, result } = {}) {
|
export function enhance(form, { pending, error, result } = {}) {
|
||||||
let current_token;
|
let current_token;
|
||||||
|
|
||||||
/** @param {SubmitEvent} e */
|
/** @param {SubmitEvent} event */
|
||||||
async function handle_submit(e) {
|
async function handle_submit(event) {
|
||||||
const token = (current_token = {});
|
const token = (current_token = {});
|
||||||
|
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const data = new FormData(form);
|
const data = new FormData(form);
|
||||||
|
|
||||||
@@ -63,11 +63,11 @@ export function enhance(form, { pending, error, result } = {}) {
|
|||||||
} else {
|
} else {
|
||||||
console.error(await response.text());
|
console.error(await response.text());
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
if (error && e instanceof Error) {
|
if (error && err instanceof Error) {
|
||||||
error({ data, form, error: e, response: null });
|
error({ data, form, error: err, response: null });
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,124 +1,124 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import logo from './svelte-logo.svg';
|
import logo from './svelte-logo.svg';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div class="corner">
|
<div class="corner">
|
||||||
<a href="https://kit.svelte.dev">
|
<a href="https://kit.svelte.dev">
|
||||||
<img src={logo} alt="SvelteKit" />
|
<img src={logo} alt="SvelteKit" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||||
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
||||||
</svg>
|
</svg>
|
||||||
<ul>
|
<ul>
|
||||||
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
||||||
<li class:active={$page.url.pathname === '/about'}>
|
<li class:active={$page.url.pathname === '/about'}>
|
||||||
<a sveltekit:prefetch href="/about">About</a>
|
<a sveltekit:prefetch href="/about">About</a>
|
||||||
</li>
|
</li>
|
||||||
<li class:active={$page.url.pathname === '/todos'}>
|
<li class:active={$page.url.pathname === '/todos'}>
|
||||||
<a sveltekit:prefetch href="/todos">Todos</a>
|
<a sveltekit:prefetch href="/todos">Todos</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||||
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
|
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
|
||||||
</svg>
|
</svg>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="corner">
|
<div class="corner">
|
||||||
<!-- TODO put something else here? github link? -->
|
<!-- TODO put something else here? github link? -->
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.corner {
|
.corner {
|
||||||
width: 3em;
|
width: 3em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.corner a {
|
.corner a {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.corner img {
|
.corner img {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
height: 2em;
|
height: 2em;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
--background: rgba(255, 255, 255, 0.7);
|
--background: rgba(255, 255, 255, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
path {
|
path {
|
||||||
fill: var(--background);
|
fill: var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
li.active::before {
|
li.active::before {
|
||||||
--size: 6px;
|
--size: 6px;
|
||||||
content: '';
|
content: '';
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: calc(50% - var(--size));
|
left: calc(50% - var(--size));
|
||||||
border: var(--size) solid transparent;
|
border: var(--size) solid transparent;
|
||||||
border-top: var(--size) solid var(--accent-color);
|
border-top: var(--size) solid var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
nav a {
|
nav a {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 1em;
|
padding: 0 1em;
|
||||||
color: var(--heading-color);
|
color: var(--heading-color);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: color 0.2s linear;
|
transition: color 0.2s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
58
examples/sveltekit/src/routes/+layout.svelte
Normal file
58
examples/sveltekit/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<script>
|
||||||
|
import Header from '$lib/header/Header.svelte';
|
||||||
|
import { webVitals } from '$lib/vitals';
|
||||||
|
import { browser } from '$app/env';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import '../app.css';
|
||||||
|
|
||||||
|
let analyticsId = import.meta.env.VERCEL_ANALYTICS_ID;
|
||||||
|
|
||||||
|
$: if (browser && analyticsId) {
|
||||||
|
webVitals({
|
||||||
|
path: $page.url.pathname,
|
||||||
|
params: $page.params,
|
||||||
|
analyticsId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
main {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1024px;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 480px) {
|
||||||
|
footer {
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1
examples/sveltekit/src/routes/+page.js
Normal file
1
examples/sveltekit/src/routes/+page.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const prerender = true;
|
||||||
57
examples/sveltekit/src/routes/+page.svelte
Normal file
57
examples/sveltekit/src/routes/+page.svelte
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<script>
|
||||||
|
import Counter from '$lib/Counter.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Home</title>
|
||||||
|
<meta name="description" content="Svelte demo app" />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h1>
|
||||||
|
<span class="welcome">
|
||||||
|
<picture>
|
||||||
|
<source srcset="svelte-welcome.webp" type="image/webp" />
|
||||||
|
<img src="svelte-welcome.png" alt="Welcome" />
|
||||||
|
</picture>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
to your new<br />SvelteKit app
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
try editing <strong>src/routes/index.svelte</strong>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<Counter />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
padding: 0 0 calc(100% * 495 / 2048) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome img {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Header from '$lib/header/Header.svelte';
|
|
||||||
import { webVitals } from '$lib/vitals';
|
|
||||||
import { browser } from '$app/env';
|
|
||||||
import { page } from '$app/stores';
|
|
||||||
import '../app.css';
|
|
||||||
|
|
||||||
let analyticsId = import.meta.env.VERCEL_ANALYTICS_ID;
|
|
||||||
|
|
||||||
$: if (browser && analyticsId) {
|
|
||||||
webVitals({
|
|
||||||
path: $page.url.pathname,
|
|
||||||
params: $page.params,
|
|
||||||
analyticsId
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Header />
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<slot />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
main {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 1rem;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1024px;
|
|
||||||
margin: 0 auto;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer a {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 480px) {
|
|
||||||
footer {
|
|
||||||
padding: 40px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<script context="module">
|
|
||||||
import { browser, dev } from '$app/env';
|
|
||||||
|
|
||||||
// we don't need any JS on this page, though we'll load
|
|
||||||
// it in dev so that we get hot module replacement...
|
|
||||||
export const hydrate = dev;
|
|
||||||
|
|
||||||
// ...but if the client-side router is already loaded
|
|
||||||
// (i.e. we came here from elsewhere in the app), use it
|
|
||||||
export const router = browser;
|
|
||||||
|
|
||||||
// since there's no dynamic data here, we can prerender
|
|
||||||
// it so that it gets served as a static asset in prod
|
|
||||||
export const prerender = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>About</title>
|
|
||||||
<meta name="description" content="About this app" />
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<h1>About this app</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
|
|
||||||
following into your command line and following the prompts:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<pre>npm init svelte</pre>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The page you're looking at is purely static HTML, with no client-side interactivity needed.
|
|
||||||
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
|
|
||||||
the devtools network panel and reloading.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
|
|
||||||
it with JavaScript disabled!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.content {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--column-width);
|
|
||||||
margin: var(--column-margin-top) auto 0 auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
13
examples/sveltekit/src/routes/about/+page.js
Normal file
13
examples/sveltekit/src/routes/about/+page.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { browser, dev } from '$app/env';
|
||||||
|
|
||||||
|
// we don't need any JS on this page, though we'll load
|
||||||
|
// it in dev so that we get hot module replacement...
|
||||||
|
export const hydrate = dev;
|
||||||
|
|
||||||
|
// ...but if the client-side router is already loaded
|
||||||
|
// (i.e. we came here from elsewhere in the app), use it
|
||||||
|
export const router = browser;
|
||||||
|
|
||||||
|
// since there's no dynamic data here, we can prerender
|
||||||
|
// it so that it gets served as a static asset in prod
|
||||||
|
export const prerender = true;
|
||||||
34
examples/sveltekit/src/routes/about/+page.svelte
Normal file
34
examples/sveltekit/src/routes/about/+page.svelte
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<svelte:head>
|
||||||
|
<title>About</title>
|
||||||
|
<meta name="description" content="About this app" />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<h1>About this app</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
|
||||||
|
following into your command line and following the prompts:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>npm create svelte@latest</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The page you're looking at is purely static HTML, with no client-side interactivity needed.
|
||||||
|
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
|
||||||
|
the devtools network panel and reloading.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
|
||||||
|
it with JavaScript disabled!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--column-width);
|
||||||
|
margin: var(--column-margin-top) auto 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<script context="module">
|
|
||||||
export const prerender = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Counter from '$lib/Counter.svelte';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>Home</title>
|
|
||||||
<meta name="description" content="Svelte demo app" />
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h1>
|
|
||||||
<div class="welcome">
|
|
||||||
<picture>
|
|
||||||
<source srcset="svelte-welcome.webp" type="image/webp" />
|
|
||||||
<img src="svelte-welcome.png" alt="Welcome" />
|
|
||||||
</picture>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
to your new<br />SvelteKit app
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<h2>
|
|
||||||
try editing <strong>src/routes/index.svelte</strong>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<Counter />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
section {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.welcome {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 0;
|
|
||||||
padding: 0 0 calc(100% * 495 / 2048) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.welcome img {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
top: 0;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
62
examples/sveltekit/src/routes/todos/+page.server.js
Normal file
62
examples/sveltekit/src/routes/todos/+page.server.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import { api } from './api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {{
|
||||||
|
* uid: string;
|
||||||
|
* created_at: Date;
|
||||||
|
* text: string;
|
||||||
|
* done: boolean;
|
||||||
|
* pending_delete: boolean;
|
||||||
|
* }} Todo
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {import('./$types').PageServerLoad} */
|
||||||
|
export const load = async ({ locals }) => {
|
||||||
|
// locals.userid comes from src/hooks.js
|
||||||
|
const response = await api('GET', `todos/${locals.userid}`);
|
||||||
|
|
||||||
|
if (response.status === 404) {
|
||||||
|
// user hasn't created a todo list.
|
||||||
|
// start with an empty array
|
||||||
|
return {
|
||||||
|
/** @type {Todo[]} */
|
||||||
|
todos: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
return {
|
||||||
|
/** @type {Todo[]} */
|
||||||
|
todos: await response.json()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error(response.status);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {import('./$types').Action} */
|
||||||
|
export const POST = async ({ request, locals }) => {
|
||||||
|
const form = await request.formData();
|
||||||
|
|
||||||
|
await api('POST', `todos/${locals.userid}`, {
|
||||||
|
text: form.get('text')
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {import('./$types').Action} */
|
||||||
|
export const PATCH = async ({ request, locals }) => {
|
||||||
|
const form = await request.formData();
|
||||||
|
|
||||||
|
await api('PATCH', `todos/${locals.userid}/${form.get('uid')}`, {
|
||||||
|
text: form.has('text') ? form.get('text') : undefined,
|
||||||
|
done: form.has('done') ? !!form.get('done') : undefined
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {import('./$types').Action} */
|
||||||
|
export const DELETE = async ({ request, locals }) => {
|
||||||
|
const form = await request.formData();
|
||||||
|
|
||||||
|
await api('DELETE', `todos/${locals.userid}/${form.get('uid')}`);
|
||||||
|
};
|
||||||
180
examples/sveltekit/src/routes/todos/+page.svelte
Normal file
180
examples/sveltekit/src/routes/todos/+page.svelte
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<script>
|
||||||
|
import { enhance } from '$lib/form';
|
||||||
|
import { scale } from 'svelte/transition';
|
||||||
|
import { flip } from 'svelte/animate';
|
||||||
|
|
||||||
|
/** @type {import('./$types').PageData} */
|
||||||
|
export let data;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Todos</title>
|
||||||
|
<meta name="description" content="A todo list app" />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div class="todos">
|
||||||
|
<h1>Todos</h1>
|
||||||
|
|
||||||
|
<form
|
||||||
|
class="new"
|
||||||
|
action="/todos"
|
||||||
|
method="post"
|
||||||
|
use:enhance={{
|
||||||
|
result: async ({ form }) => {
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{#each data.todos as todo (todo.uid)}
|
||||||
|
<div
|
||||||
|
class="todo"
|
||||||
|
class:done={todo.done}
|
||||||
|
transition:scale|local={{ start: 0.7 }}
|
||||||
|
animate:flip={{ duration: 200 }}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
action="/todos?_method=PATCH"
|
||||||
|
method="post"
|
||||||
|
use:enhance={{
|
||||||
|
pending: ({ data }) => {
|
||||||
|
todo.done = !!data.get('done');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input type="hidden" name="uid" value={todo.uid} />
|
||||||
|
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
|
||||||
|
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
|
||||||
|
<input type="hidden" name="uid" value={todo.uid} />
|
||||||
|
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
|
||||||
|
<button class="save" aria-label="Save todo" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form
|
||||||
|
action="/todos?_method=DELETE"
|
||||||
|
method="post"
|
||||||
|
use:enhance={{
|
||||||
|
pending: () => (todo.pending_delete = true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input type="hidden" name="uid" value={todo.uid} />
|
||||||
|
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.todos {
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--column-width);
|
||||||
|
margin: var(--column-margin-top) auto 0 auto;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus-visible {
|
||||||
|
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid #ff3e00 !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new input {
|
||||||
|
font-size: 28px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5em 1em 0.3em 1em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2rem 1fr 2rem;
|
||||||
|
grid-gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
|
||||||
|
transform: translate(-1px, -1px);
|
||||||
|
transition: filter 0.2s, transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.done {
|
||||||
|
transform: none;
|
||||||
|
opacity: 0.4;
|
||||||
|
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
|
||||||
|
}
|
||||||
|
|
||||||
|
form.text {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.5em 2em 0.5em 0.8em;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo button {
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.toggle {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-size: 1em auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.done .toggle {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete:hover,
|
||||||
|
.delete:focus {
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
opacity: 0;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo input:focus + .save,
|
||||||
|
.save:focus {
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
This module is used by the /todos endpoint to
|
This module is used by the /todos endpoint to
|
||||||
make calls to api.svelte.dev, which stores todos
|
make calls to api.svelte.dev, which stores todos
|
||||||
for each user. The leading underscore indicates that this is
|
for each user.
|
||||||
a private module, _not_ an endpoint — visiting /todos/_api
|
|
||||||
will net you a 404 response.
|
|
||||||
|
|
||||||
(The data on the todo app will expire periodically; no
|
(The data on the todo app will expire periodically; no
|
||||||
guarantees are made. Don't use it to organise your life.)
|
guarantees are made. Don't use it to organise your life.)
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import { api } from './_api';
|
|
||||||
|
|
||||||
/** @type {import('./__types').RequestHandler} */
|
|
||||||
export const get = async ({ locals }) => {
|
|
||||||
// locals.userid comes from src/hooks.js
|
|
||||||
const response = await api('get', `todos/${locals.userid}`);
|
|
||||||
|
|
||||||
if (response.status === 404) {
|
|
||||||
// user hasn't created a todo list.
|
|
||||||
// start with an empty array
|
|
||||||
return {
|
|
||||||
body: {
|
|
||||||
todos: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
|
||||||
return {
|
|
||||||
body: {
|
|
||||||
todos: await response.json()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: response.status
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @type {import('./index').RequestHandler} */
|
|
||||||
export const post = async ({ request, locals }) => {
|
|
||||||
const form = await request.formData();
|
|
||||||
|
|
||||||
await api('post', `todos/${locals.userid}`, {
|
|
||||||
text: form.get('text')
|
|
||||||
});
|
|
||||||
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the user has JavaScript disabled, the URL will change to
|
|
||||||
// include the method override unless we redirect back to /todos
|
|
||||||
const redirect = {
|
|
||||||
status: 303,
|
|
||||||
headers: {
|
|
||||||
location: '/todos'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @type {import('./index').RequestHandler} */
|
|
||||||
export const patch = async ({ request, locals }) => {
|
|
||||||
const form = await request.formData();
|
|
||||||
|
|
||||||
await api('patch', `todos/${locals.userid}/${form.get('uid')}`, {
|
|
||||||
text: form.has('text') ? form.get('text') : undefined,
|
|
||||||
done: form.has('done') ? !!form.get('done') : undefined
|
|
||||||
});
|
|
||||||
|
|
||||||
return redirect;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @type {import('./index').RequestHandler} */
|
|
||||||
export const del = async ({ request, locals }) => {
|
|
||||||
const form = await request.formData();
|
|
||||||
|
|
||||||
await api('delete', `todos/${locals.userid}/${form.get('uid')}`);
|
|
||||||
|
|
||||||
return redirect;
|
|
||||||
};
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { enhance } from '$lib/form';
|
|
||||||
import { scale } from 'svelte/transition';
|
|
||||||
import { flip } from 'svelte/animate';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {{
|
|
||||||
* uid: string;
|
|
||||||
* created_at: Date;
|
|
||||||
* text: string;
|
|
||||||
* done: boolean;
|
|
||||||
* pending_delete: boolean;
|
|
||||||
* }} Todo
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @type {Todo[]} */
|
|
||||||
export let todos;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>Todos</title>
|
|
||||||
<meta name="description" content="A todo list app" />
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<div class="todos">
|
|
||||||
<h1>Todos</h1>
|
|
||||||
|
|
||||||
<form
|
|
||||||
class="new"
|
|
||||||
action="/todos"
|
|
||||||
method="post"
|
|
||||||
use:enhance={{
|
|
||||||
result: async ({ form }) => {
|
|
||||||
form.reset();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{#each todos as todo (todo.uid)}
|
|
||||||
<div
|
|
||||||
class="todo"
|
|
||||||
class:done={todo.done}
|
|
||||||
transition:scale|local={{ start: 0.7 }}
|
|
||||||
animate:flip={{ duration: 200 }}
|
|
||||||
>
|
|
||||||
<form
|
|
||||||
action="/todos?_method=PATCH"
|
|
||||||
method="post"
|
|
||||||
use:enhance={{
|
|
||||||
pending: ({ data }) => {
|
|
||||||
todo.done = !!data.get('done');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input type="hidden" name="uid" value={todo.uid} />
|
|
||||||
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
|
|
||||||
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
|
|
||||||
<input type="hidden" name="uid" value={todo.uid} />
|
|
||||||
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
|
|
||||||
<button class="save" aria-label="Save todo" />
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<form
|
|
||||||
action="/todos?_method=DELETE"
|
|
||||||
method="post"
|
|
||||||
use:enhance={{
|
|
||||||
pending: () => (todo.pending_delete = true)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input type="hidden" name="uid" value={todo.uid} />
|
|
||||||
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.todos {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--column-width);
|
|
||||||
margin: var(--column-margin-top) auto 0 auto;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
border: 1px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus-visible {
|
|
||||||
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
border: 1px solid #ff3e00 !important;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new input {
|
|
||||||
font-size: 28px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5em 1em 0.3em 1em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border-radius: 8px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 2rem 1fr 2rem;
|
|
||||||
grid-gap: 0.5rem;
|
|
||||||
align-items: center;
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
padding: 0.5rem;
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
|
|
||||||
transform: translate(-1px, -1px);
|
|
||||||
transition: filter 0.2s, transform 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.done {
|
|
||||||
transform: none;
|
|
||||||
opacity: 0.4;
|
|
||||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
|
|
||||||
}
|
|
||||||
|
|
||||||
form.text {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo input {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0.5em 2em 0.5em 0.8em;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo button {
|
|
||||||
width: 2em;
|
|
||||||
height: 2em;
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
background-position: 50% 50%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.toggle {
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
||||||
border-radius: 50%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-size: 1em auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.done .toggle {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
|
|
||||||
opacity: 0.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete:hover,
|
|
||||||
.delete:focus {
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.save {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
opacity: 0;
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo input:focus + .save,
|
|
||||||
.save:focus {
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -8,11 +8,6 @@ const config = {
|
|||||||
// Override http methods in the Todo forms
|
// Override http methods in the Todo forms
|
||||||
methodOverride: {
|
methodOverride: {
|
||||||
allowed: ['PATCH', 'DELETE']
|
allowed: ['PATCH', 'DELETE']
|
||||||
},
|
|
||||||
vite: {
|
|
||||||
define: {
|
|
||||||
'import.meta.env.VERCEL_ANALYTICS_ID': JSON.stringify(process.env.VERCEL_ANALYTICS_ID)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
11
examples/sveltekit/vite.config.js
Normal file
11
examples/sveltekit/vite.config.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
|
||||||
|
/** @type {import('vite').UserConfig} */
|
||||||
|
const config = {
|
||||||
|
plugins: [sveltekit()],
|
||||||
|
define: {
|
||||||
|
'import.meta.env.VERCEL_ANALYTICS_ID': JSON.stringify(process.env.VERCEL_ANALYTICS_ID)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
File diff suppressed because it is too large
Load Diff
18
package.json
18
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"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/build-utils",
|
"name": "@vercel/build-utils",
|
||||||
"version": "5.3.1",
|
"version": "5.5.7",
|
||||||
"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;
|
||||||
|
|||||||
@@ -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-10-01'),
|
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,
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import debug from './debug';
|
|||||||
import getIgnoreFilter from './get-ignore-filter';
|
import getIgnoreFilter from './get-ignore-filter';
|
||||||
import { getPlatformEnv } from './get-platform-env';
|
import { getPlatformEnv } from './get-platform-env';
|
||||||
import { getPrefixedEnvVars } from './get-prefixed-env-vars';
|
import { getPrefixedEnvVars } from './get-prefixed-env-vars';
|
||||||
|
import { cloneEnv } from './clone-env';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
FileBlob,
|
FileBlob,
|
||||||
@@ -84,6 +85,7 @@ export {
|
|||||||
getLambdaOptionsFromFunction,
|
getLambdaOptionsFromFunction,
|
||||||
scanParentDirs,
|
scanParentDirs,
|
||||||
getIgnoreFilter,
|
getIgnoreFilter,
|
||||||
|
cloneEnv,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { EdgeFunction } from './edge-function';
|
export { EdgeFunction } from './edge-function';
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,13 +336,13 @@ export interface ProjectSettings {
|
|||||||
directoryListing?: boolean;
|
directoryListing?: boolean;
|
||||||
gitForkProtection?: boolean;
|
gitForkProtection?: boolean;
|
||||||
commandForIgnoringBuildStep?: string | null;
|
commandForIgnoringBuildStep?: string | null;
|
||||||
skipGitConnectDuringLink?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BuilderV2 {
|
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" }]
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user