mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-24 19:00:03 +00:00
Compare commits
40 Commits
@vercel/cl
...
@vercel/ne
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e174a06673 | ||
|
|
de034943af | ||
|
|
b3862271a5 | ||
|
|
aaceeef604 | ||
|
|
ad107ecf79 | ||
|
|
79ef5c3724 | ||
|
|
02ff265074 | ||
|
|
ae89b8b8be | ||
|
|
4ccdcde463 | ||
|
|
22d3ee160b | ||
|
|
6d97e1673e | ||
|
|
522565f6e5 | ||
|
|
07bf81ab10 | ||
|
|
35024a4e3a | ||
|
|
c1df9bca19 | ||
|
|
4c1cdd1f0f | ||
|
|
b5cdc82a1c | ||
|
|
c7851404b3 | ||
|
|
e54da8a2e5 | ||
|
|
a066bedf95 | ||
|
|
09b23e53ba | ||
|
|
b793a67588 | ||
|
|
31dd354b3a | ||
|
|
529ff3b2d7 | ||
|
|
e71d5638ee | ||
|
|
8c16e765ee | ||
|
|
a008c9c7fe | ||
|
|
62b28ad0b4 | ||
|
|
7c50f2916e | ||
|
|
a521dadafb | ||
|
|
1efb5d6c0d | ||
|
|
72df5ce8f6 | ||
|
|
e20b74687f | ||
|
|
8f1358bd15 | ||
|
|
74c0b3e1bb | ||
|
|
eb0a031aeb | ||
|
|
f327be2d1f | ||
|
|
16a5867f6b | ||
|
|
90cbd675fa | ||
|
|
9c768b98b7 |
@@ -43,5 +43,8 @@ packages/static-build/test/cache-fixtures
|
||||
# redwood
|
||||
packages/redwood/test/fixtures
|
||||
|
||||
# remix
|
||||
packages/remix/test/fixtures
|
||||
|
||||
# gatsby-plugin-vercel-analytics
|
||||
packages/gatsby-plugin-vercel-analytics
|
||||
|
||||
20
.github/CONTRIBUTING.md
vendored
20
.github/CONTRIBUTING.md
vendored
@@ -6,7 +6,7 @@ Please read our [Code of Conduct](CODE_OF_CONDUCT.md) and follow it in all your
|
||||
|
||||
## Local development
|
||||
|
||||
This project is configured in a monorepo, where one repository contains multiple npm packages. Dependencies are installed and managed with `yarn`, not `npm` CLI.
|
||||
This project is configured in a monorepo, where one repository contains multiple npm packages. Dependencies are installed and managed with `pnpm`, not `npm` CLI.
|
||||
|
||||
To get started, execute the following:
|
||||
|
||||
@@ -14,22 +14,22 @@ To get started, execute the following:
|
||||
git clone https://github.com/vercel/vercel
|
||||
cd vercel
|
||||
corepack enable
|
||||
yarn install
|
||||
yarn bootstrap
|
||||
yarn build
|
||||
yarn lint
|
||||
yarn test-unit
|
||||
pnpm install
|
||||
pnpm bootstrap
|
||||
pnpm build
|
||||
pnpm lint
|
||||
pnpm test-unit
|
||||
```
|
||||
|
||||
Make sure all the tests pass before making changes.
|
||||
|
||||
### Running Vercel CLI Changes
|
||||
|
||||
You can use `yarn dev` from the `cli` package to invoke Vercel CLI with local changes:
|
||||
You can use `pnpm dev` from the `cli` package to invoke Vercel CLI with local changes:
|
||||
|
||||
```
|
||||
cd ./packages/cli
|
||||
yarn dev <cli-commands...>
|
||||
pnpm dev <cli-commands...>
|
||||
```
|
||||
|
||||
See [CLI Local Development](../packages/cli#local-development) for more details.
|
||||
@@ -39,7 +39,7 @@ See [CLI Local Development](../packages/cli#local-development) for more details.
|
||||
Once you are done with your changes (we even suggest doing it along the way), make sure all the tests still pass by running:
|
||||
|
||||
```
|
||||
yarn test-unit
|
||||
pnpm test-unit
|
||||
```
|
||||
|
||||
from the root of the project.
|
||||
@@ -102,7 +102,7 @@ When you run this script, you'll see all the imported files. If anything file is
|
||||
Sometimes you want to test changes to a Builder against an existing project, maybe with `vercel dev` or actual deployment. You can avoid publishing every Builder change to npm by uploading the Builder as a tarball.
|
||||
|
||||
1. Change directory to the desired Builder `cd ./packages/node`
|
||||
2. Run `yarn build` to compile typescript and other build steps
|
||||
2. Run `pnpm build` to compile typescript and other build steps
|
||||
3. Run `npm pack` to create a tarball file
|
||||
4. Run `vercel *.tgz` to upload the tarball file and get a URL
|
||||
5. Edit any existing `vercel.json` project and replace `use` with the URL
|
||||
|
||||
12
.github/workflows/publish.yml
vendored
12
.github/workflows/publish.yml
vendored
@@ -47,20 +47,22 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: yarn-${{ matrix.os }}-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: yarn-${{ matrix.os }}-${{ matrix.node }}
|
||||
key: pnpm-${{ matrix.os }}-${{ matrix.node }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: pnpm-${{ matrix.os }}-${{ matrix.node }}
|
||||
- name: install pnpm@7.24.2
|
||||
run: npm i -g pnpm@7.24.2
|
||||
- name: Install
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
run: yarn install --check-files --frozen-lockfile --network-timeout 1000000
|
||||
run: pnpm install
|
||||
- name: Build
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
run: yarn build
|
||||
run: pnpm build
|
||||
env:
|
||||
GA_TRACKING_ID: ${{ secrets.GA_TRACKING_ID }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
- name: Publish
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
run: yarn publish-from-github
|
||||
run: pnpm publish-from-github
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}
|
||||
GA_TRACKING_ID: ${{ secrets.GA_TRACKING_ID }}
|
||||
|
||||
12
.github/workflows/test-integration-cli.yml
vendored
12
.github/workflows/test-integration-cli.yml
vendored
@@ -41,11 +41,13 @@ jobs:
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: yarn-${{ matrix.os }}-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: yarn-${{ matrix.os }}-${{ matrix.node }}
|
||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||
- run: yarn run build
|
||||
- run: yarn test-integration-cli
|
||||
key: pnpm-${{ matrix.os }}-${{ matrix.node }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: pnpm-${{ matrix.os }}-${{ matrix.node }}
|
||||
- name: install pnpm@7.24.2
|
||||
run: npm i -g pnpm@7.24.2
|
||||
- run: pnpm install
|
||||
- run: pnpm run build
|
||||
- run: pnpm test-integration-cli
|
||||
env:
|
||||
VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }}
|
||||
VERCEL_TEST_REGISTRATION_URL: ${{ secrets.VERCEL_TEST_REGISTRATION_URL }}
|
||||
|
||||
16
.github/workflows/test-unit.yml
vendored
16
.github/workflows/test-unit.yml
vendored
@@ -41,14 +41,16 @@ jobs:
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: yarn-${{ matrix.os }}-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: yarn-${{ matrix.os }}-${{ matrix.node }}
|
||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||
- run: yarn run build
|
||||
- run: yarn run lint
|
||||
key: pnpm-${{ matrix.os }}-${{ matrix.node }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: pnpm-${{ matrix.os }}-${{ matrix.node }}
|
||||
- name: install pnpm@7.24.2
|
||||
run: npm i -g pnpm@7.24.2
|
||||
- run: pnpm install
|
||||
- run: pnpm run build
|
||||
- run: pnpm run lint
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node == 14 # only run lint once
|
||||
- run: yarn run test-unit
|
||||
- run: yarn workspace vercel run coverage
|
||||
- run: pnpm run test-unit
|
||||
- run: pnpm -C packages/cli run coverage
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node == 14 # only run coverage once
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
17
.github/workflows/test.yml
vendored
17
.github/workflows/test.yml
vendored
@@ -39,9 +39,11 @@ jobs:
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: yarn-${{ matrix.os }}-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: yarn-${{ matrix.os }}-${{ matrix.node }}
|
||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||
key: pnpm-${{ matrix.os }}-${{ matrix.node }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: pnpm-${{ matrix.os }}-${{ matrix.node }}
|
||||
- name: install pnpm@7.24.2
|
||||
run: npm i -g pnpm@7.24.2
|
||||
- run: pnpm install
|
||||
- id: set-tests
|
||||
run: |
|
||||
TESTS_ARRAY=$(node utils/chunk-tests.js $SCRIPT_NAME)
|
||||
@@ -80,14 +82,17 @@ jobs:
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: yarn-${{ matrix.os }}-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: yarn-${{ matrix.os }}-${{ matrix.node }}
|
||||
key: pnpm-${{ matrix.os }}-${{ matrix.node }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: pnpm-${{ matrix.os }}-${{ matrix.node }}
|
||||
|
||||
- name: Install Hugo
|
||||
if: matrix.runner == 'macos-latest'
|
||||
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/cli/test/dev/fixtures/08-hugo/
|
||||
|
||||
- run: yarn install --network-timeout 1000000
|
||||
- name: install pnpm@7.24.2
|
||||
run: npm i -g pnpm@7.24.2
|
||||
|
||||
- run: pnpm install
|
||||
|
||||
- name: Build ${{matrix.packageName}} and all its dependencies
|
||||
run: node utils/gen.js && node_modules/.bin/turbo run build --cache-dir=".turbo" --scope=${{matrix.packageName}} --include-dependencies --no-deps
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn pre-commit
|
||||
pnpm pre-commit
|
||||
|
||||
5
.npmrc
Normal file
5
.npmrc
Normal file
@@ -0,0 +1,5 @@
|
||||
save-exact=true
|
||||
hoist-pattern[]=!"**/@types/**"
|
||||
hoist-pattern[]=!"**/typedoc"
|
||||
hoist-pattern[]=!"**/typedoc-plugin-markdown"
|
||||
hoist-pattern[]=!"**/typedoc-plugin-mdn-links"
|
||||
@@ -33,9 +33,9 @@ For details on how to use Vercel, check out our [documentation](https://vercel.c
|
||||
|
||||
## Contributing
|
||||
|
||||
This project uses [yarn](https://yarnpkg.com/) to install dependencies and run scripts.
|
||||
This project uses [pnpm](https://pnpm.io/) to install dependencies and run scripts.
|
||||
|
||||
You can use the `dev` script to run local changes as if you were invoking Vercel CLI. For example, `vercel deploy --cwd=/path/to/project` could be run with local changes with `yarn dev deploy --cwd=/path/to/project`.
|
||||
You can use the `dev` script to run local changes as if you were invoking Vercel CLI. For example, `vercel deploy --cwd=/path/to/project` could be run with local changes with `pnpm dev deploy --cwd=/path/to/project`.
|
||||
|
||||
See the [Contributing Guidelines](./.github/CONTRIBUTING.md) for more details.
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ function initSentry() {
|
||||
sentryInitDone = true;
|
||||
|
||||
init({
|
||||
// Cannot figure out whats going wrong here. VSCode resolves this fine. But when we build it blows up.
|
||||
// @ts-ignore
|
||||
dsn: assertEnv('SENTRY_DSN'),
|
||||
environment: process.env.NODE_ENV || 'production',
|
||||
release: `${serviceName}`,
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
"version": "0.0.0",
|
||||
"description": "API for the vercel/vercel repo",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"//TODO": "We should add this pkg to yarn workspaces"
|
||||
},
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@sentry/node": "5.11.1",
|
||||
"got": "10.2.1",
|
||||
@@ -16,9 +14,9 @@
|
||||
"unzip-stream": "0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "14.18.33",
|
||||
"@types/node": "16.18.11",
|
||||
"@types/node-fetch": "2.5.4",
|
||||
"@vercel/node": "1.9.0",
|
||||
"typescript": "3.9.6"
|
||||
"@vercel/node": "*",
|
||||
"typescript": "4.3.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["examples", "frameworks.ts"]
|
||||
"include": ["examples", "frameworks.ts", "_lib"]
|
||||
}
|
||||
|
||||
524
api/yarn.lock
524
api/yarn.lock
@@ -1,524 +0,0 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@sentry/apm@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.11.1.tgz#cc89fa4150056fbf009f92eca94fccc3980db34e"
|
||||
integrity sha512-4iZH11p/7w9IMLT9hqNY1+EqLESltiIoF6/YsbpK93sXWGEs8VQ83IuvGuKWxajvHgDmj4ND0TxIliTsYqTqFw==
|
||||
dependencies:
|
||||
"@sentry/browser" "5.11.1"
|
||||
"@sentry/hub" "5.11.1"
|
||||
"@sentry/minimal" "5.11.1"
|
||||
"@sentry/types" "5.11.0"
|
||||
"@sentry/utils" "5.11.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/browser@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.11.1.tgz#337ffcb52711b23064c847a07629e966f54a5ebb"
|
||||
integrity sha512-oqOX/otmuP92DEGRyZeBuQokXdeT9HQRxH73oqIURXXNLMP3PWJALSb4HtT4AftEt/2ROGobZLuA4TaID6My/Q==
|
||||
dependencies:
|
||||
"@sentry/core" "5.11.1"
|
||||
"@sentry/types" "5.11.0"
|
||||
"@sentry/utils" "5.11.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/core@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.11.1.tgz#9e2da485e196ae32971545c1c49ee6fe719930e2"
|
||||
integrity sha512-BpvPosVNT20Xso4gAV54Lu3KqDmD20vO63HYwbNdST5LUi8oYV4JhvOkoBraPEM2cbBwQvwVcFdeEYKk4tin9A==
|
||||
dependencies:
|
||||
"@sentry/hub" "5.11.1"
|
||||
"@sentry/minimal" "5.11.1"
|
||||
"@sentry/types" "5.11.0"
|
||||
"@sentry/utils" "5.11.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/hub@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.11.1.tgz#ddcb865563fae53852d405885c46b4c6de68a91b"
|
||||
integrity sha512-ucKprYCbGGLLjVz4hWUqHN9KH0WKUkGf5ZYfD8LUhksuobRkYVyig0ZGbshECZxW5jcDTzip4Q9Qimq/PkkXBg==
|
||||
dependencies:
|
||||
"@sentry/types" "5.11.0"
|
||||
"@sentry/utils" "5.11.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/minimal@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.11.1.tgz#0e705d01a567282d8fbbda2aed848b4974cc3cec"
|
||||
integrity sha512-HK8zs7Pgdq7DsbZQTThrhQPrJsVWzz7MaluAbQA0rTIAJ3TvHKQpsVRu17xDpjZXypqWcKCRsthDrC4LxDM1Bg==
|
||||
dependencies:
|
||||
"@sentry/hub" "5.11.1"
|
||||
"@sentry/types" "5.11.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/node@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.11.1.tgz#2a9c18cd1209cfdf7a69b9d91303413149d2c910"
|
||||
integrity sha512-FbJs0blJ36gEzE0rc2yBfA/KE+kXOLl8MUfFTcyJCBdCGF8XMETDCmgINnJ4TyBUJviwKoPw2TCk9TL2pa/A1w==
|
||||
dependencies:
|
||||
"@sentry/apm" "5.11.1"
|
||||
"@sentry/core" "5.11.1"
|
||||
"@sentry/hub" "5.11.1"
|
||||
"@sentry/types" "5.11.0"
|
||||
"@sentry/utils" "5.11.1"
|
||||
cookie "^0.3.1"
|
||||
https-proxy-agent "^4.0.0"
|
||||
lru_map "^0.3.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/types@5.11.0":
|
||||
version "5.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.11.0.tgz#40f0f3174362928e033ddd9725d55e7c5cb7c5b6"
|
||||
integrity sha512-1Uhycpmeo1ZK2GLvrtwZhTwIodJHcyIS6bn+t4IMkN9MFoo6ktbAfhvexBDW/IDtdLlCGJbfm8nIZerxy0QUpg==
|
||||
|
||||
"@sentry/utils@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.11.1.tgz#aa19fcc234cf632257b2281261651d2fac967607"
|
||||
integrity sha512-O0Zl4R2JJh8cTkQ8ZL2cDqGCmQdpA5VeXpuBbEl1v78LQPkBDISi35wH4mKmLwMsLBtTVpx2UeUHBj0KO5aLlA==
|
||||
dependencies:
|
||||
"@sentry/types" "5.11.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sindresorhus/is@^1.0.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-1.2.0.tgz#63ce3638cb85231f3704164c90a18ef816da3fb7"
|
||||
integrity sha512-mwhXGkRV5dlvQc4EgPDxDxO6WuMBVymGFd1CA+2Y+z5dG9MNspoQ+AWjl/Ld1MnpCL8AKbosZlDVohqcIwuWsw==
|
||||
|
||||
"@szmarczak/http-timer@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.0.tgz#309789ccb7842ff1e41848cf43da587f78068836"
|
||||
integrity sha512-3yoXv8OtGr/r3R5gaWWNQ3VUoQ5G3Gmo8DXX95V14ZVvE2b7Pj6Ide9uIDON8ym4D/ItyfL9ejohYUPqOyvRXw==
|
||||
dependencies:
|
||||
defer-to-connect "^1.1.1"
|
||||
|
||||
"@types/cacheable-request@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976"
|
||||
integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==
|
||||
dependencies:
|
||||
"@types/http-cache-semantics" "*"
|
||||
"@types/keyv" "*"
|
||||
"@types/node" "*"
|
||||
"@types/responselike" "*"
|
||||
|
||||
"@types/http-cache-semantics@*":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a"
|
||||
integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==
|
||||
|
||||
"@types/keyv@*":
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7"
|
||||
integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node-fetch@2.5.4":
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.4.tgz#5245b6d8841fc3a6208b82291119bc11c4e0ce44"
|
||||
integrity sha512-Oz6id++2qAOFuOlE1j0ouk1dzl3mmI1+qINPNBhi9nt/gVOz0G+13Ao6qjhdF0Ys+eOkhu6JnFmt38bR3H0POQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*", "@types/node@13.1.4":
|
||||
version "13.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.4.tgz#4cfd90175a200ee9b02bd6b1cd19bc349741607e"
|
||||
integrity sha512-Lue/mlp2egZJoHXZr4LndxDAd7i/7SQYhV0EjWfb/a4/OZ6tuVwMCVPiwkU5nsEipxEf7hmkSU7Em5VQ8P5NGA==
|
||||
|
||||
"@types/responselike@*":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29"
|
||||
integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@vercel/node@1.9.0":
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@vercel/node/-/node-1.9.0.tgz#6b64f3b9a962ddb1089276fad00f441a1f4b9cf0"
|
||||
integrity sha512-Vk/ZpuY4Cdc8oUwBi/kf8qETRaJb/KYdFddVkLuS10QwA0yJx+RQ11trhZ1KFUdc27aBr5S2k8/dDxK8sLr+IA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
ts-node "8.9.1"
|
||||
typescript "3.9.3"
|
||||
|
||||
agent-base@5:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
|
||||
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
|
||||
|
||||
arg@^4.1.0:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
||||
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
|
||||
|
||||
binary@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
|
||||
integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=
|
||||
dependencies:
|
||||
buffers "~0.1.1"
|
||||
chainsaw "~0.1.0"
|
||||
|
||||
bl@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88"
|
||||
integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==
|
||||
dependencies:
|
||||
readable-stream "^3.0.1"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||
|
||||
buffers@~0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
|
||||
integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s=
|
||||
|
||||
cacheable-lookup@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-0.2.1.tgz#f474ae2c686667d7ea08c43409ad31b2b31b26c2"
|
||||
integrity sha512-BQ8MRjxJASEq2q+w0SusPU3B054gS278K8sj58QCLMZIso5qG05+MdCdmXxuyVlfvI8h4bPsNOavVUauVCGxrg==
|
||||
dependencies:
|
||||
keyv "^3.1.0"
|
||||
|
||||
cacheable-request@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.0.tgz#12421aa084e943ec81eac8c93e56af90c624788a"
|
||||
integrity sha512-UVG4gMn3WjnAeFBBx7RFoprgOANIAkMwN5Dta6ONmfSwrCxfm0Ip7g0mIBxIRJZX9aDsoID0Ry3dU5Pr0csKKA==
|
||||
dependencies:
|
||||
clone-response "^1.0.2"
|
||||
get-stream "^5.1.0"
|
||||
http-cache-semantics "^4.0.0"
|
||||
keyv "^3.0.0"
|
||||
lowercase-keys "^2.0.0"
|
||||
normalize-url "^4.1.0"
|
||||
responselike "^2.0.0"
|
||||
|
||||
chainsaw@~0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98"
|
||||
integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=
|
||||
dependencies:
|
||||
traverse ">=0.3.0 <0.4"
|
||||
|
||||
chownr@^1.1.1:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142"
|
||||
integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==
|
||||
|
||||
clone-response@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
|
||||
integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
|
||||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
cookie@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
||||
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
|
||||
|
||||
debug@4:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
|
||||
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
decompress-response@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-5.0.0.tgz#7849396e80e3d1eba8cb2f75ef4930f76461cb0f"
|
||||
integrity sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==
|
||||
dependencies:
|
||||
mimic-response "^2.0.0"
|
||||
|
||||
defer-to-connect@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.1.tgz#88ae694b93f67b81815a2c8c769aef6574ac8f2f"
|
||||
integrity sha512-J7thop4u3mRTkYRQ+Vpfwy2G5Ehoy82I14+14W4YMDLKdWloI9gSzRbV30s/NckQGVJtPkWNcW4oMAUigTdqiQ==
|
||||
|
||||
diff@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
|
||||
duplexer3@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
||||
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
|
||||
|
||||
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||
|
||||
get-stream@^5.0.0, get-stream@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9"
|
||||
integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
got@10.2.1:
|
||||
version "10.2.1"
|
||||
resolved "https://registry.yarnpkg.com/got/-/got-10.2.1.tgz#7087485482fb31aa6e6399fd493dd04639da117b"
|
||||
integrity sha512-IQX//hGm5oLjUj743GJG30U2RzjS58ZlhQQjwQXjsyR50TTD+etVMHlMEbNxYJGWVFa0ASgDVhRkAvQPe6M9iQ==
|
||||
dependencies:
|
||||
"@sindresorhus/is" "^1.0.0"
|
||||
"@szmarczak/http-timer" "^4.0.0"
|
||||
"@types/cacheable-request" "^6.0.1"
|
||||
cacheable-lookup "^0.2.1"
|
||||
cacheable-request "^7.0.0"
|
||||
decompress-response "^5.0.0"
|
||||
duplexer3 "^0.1.4"
|
||||
get-stream "^5.0.0"
|
||||
lowercase-keys "^2.0.0"
|
||||
mimic-response "^2.0.0"
|
||||
p-cancelable "^2.0.0"
|
||||
responselike "^2.0.0"
|
||||
to-readable-stream "^2.0.0"
|
||||
type-fest "^0.8.0"
|
||||
|
||||
http-cache-semantics@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#495704773277eeef6e43f9ab2c2c7d259dda25c5"
|
||||
integrity sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew==
|
||||
|
||||
https-proxy-agent@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b"
|
||||
integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==
|
||||
dependencies:
|
||||
agent-base "5"
|
||||
debug "4"
|
||||
|
||||
inherits@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
json-buffer@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
|
||||
integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
|
||||
|
||||
keyv@^3.0.0, keyv@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
|
||||
integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==
|
||||
dependencies:
|
||||
json-buffer "3.0.0"
|
||||
|
||||
lowercase-keys@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
|
||||
integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
|
||||
|
||||
lru_map@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
|
||||
integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=
|
||||
|
||||
make-error@^1.1.1:
|
||||
version "1.3.6"
|
||||
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
|
||||
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
|
||||
|
||||
mimic-response@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
|
||||
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
|
||||
|
||||
mimic-response@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46"
|
||||
integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ==
|
||||
|
||||
minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
||||
|
||||
mkdirp@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
ms@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
node-fetch@2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
normalize-url@^4.1.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
|
||||
integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
|
||||
|
||||
once@^1.3.1, once@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
p-cancelable@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e"
|
||||
integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==
|
||||
|
||||
parse-github-url@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/parse-github-url/-/parse-github-url-1.0.2.tgz#242d3b65cbcdda14bb50439e3242acf6971db395"
|
||||
integrity sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==
|
||||
|
||||
pump@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
|
||||
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
|
||||
dependencies:
|
||||
end-of-stream "^1.1.0"
|
||||
once "^1.3.1"
|
||||
|
||||
readable-stream@^3.0.1, readable-stream@^3.1.1:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc"
|
||||
integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
responselike@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723"
|
||||
integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==
|
||||
dependencies:
|
||||
lowercase-keys "^2.0.0"
|
||||
|
||||
safe-buffer@~5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
||||
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
|
||||
|
||||
source-map-support@^0.5.17:
|
||||
version "0.5.19"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
|
||||
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
source-map@^0.6.0:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
tar-fs@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad"
|
||||
integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==
|
||||
dependencies:
|
||||
chownr "^1.1.1"
|
||||
mkdirp "^0.5.1"
|
||||
pump "^3.0.0"
|
||||
tar-stream "^2.0.0"
|
||||
|
||||
tar-stream@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3"
|
||||
integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==
|
||||
dependencies:
|
||||
bl "^3.0.0"
|
||||
end-of-stream "^1.4.1"
|
||||
fs-constants "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
to-readable-stream@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-2.1.0.tgz#82880316121bea662cdc226adb30addb50cb06e8"
|
||||
integrity sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w==
|
||||
|
||||
"traverse@>=0.3.0 <0.4":
|
||||
version "0.3.9"
|
||||
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
|
||||
integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=
|
||||
|
||||
ts-node@8.9.1:
|
||||
version "8.9.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.9.1.tgz#2f857f46c47e91dcd28a14e052482eb14cfd65a5"
|
||||
integrity sha512-yrq6ODsxEFTLz0R3BX2myf0WBCSQh9A+py8PBo1dCzWIOcvisbyH6akNKqDHMgXePF2kir5mm5JXJTH3OUJYOQ==
|
||||
dependencies:
|
||||
arg "^4.1.0"
|
||||
diff "^4.0.1"
|
||||
make-error "^1.1.1"
|
||||
source-map-support "^0.5.17"
|
||||
yn "3.1.1"
|
||||
|
||||
tslib@^1.9.3:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||
|
||||
type-fest@^0.8.0:
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||
|
||||
typescript@3.9.3:
|
||||
version "3.9.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a"
|
||||
integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==
|
||||
|
||||
typescript@3.9.6:
|
||||
version "3.9.6"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a"
|
||||
integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==
|
||||
|
||||
unzip-stream@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/unzip-stream/-/unzip-stream-0.3.0.tgz#c30c054cd6b0d64b13a23cd3ece911eb0b2b52d8"
|
||||
integrity sha512-NG1h/MdGIX3HzyqMjyj1laBCmlPYhcO4xEy7gEqqzGiSLw7XqDQCnY4nYSn5XSaH8mQ6TFkaujrO8d/PIZN85A==
|
||||
dependencies:
|
||||
binary "^0.3.0"
|
||||
mkdirp "^0.5.1"
|
||||
|
||||
util-deprecate@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
yn@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
||||
@@ -8,6 +8,8 @@ First, run the development server:
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
8
examples/nextjs/jsconfig.json
Normal file
8
examples/nextjs/jsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
1218
examples/nextjs/package-lock.json
generated
1218
examples/nextjs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,10 +9,10 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@next/font": "13.1.1",
|
||||
"eslint": "8.30.0",
|
||||
"eslint-config-next": "13.1.1",
|
||||
"next": "13.1.1",
|
||||
"@next/font": "13.1.2",
|
||||
"eslint": "8.31.0",
|
||||
"eslint-config-next": "13.1.2",
|
||||
"next": "13.1.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import '../styles/globals.css'
|
||||
import '@/styles/globals.css'
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Head from 'next/head'
|
||||
import Image from 'next/image'
|
||||
import { Inter } from '@next/font/google'
|
||||
import styles from '../styles/Home.module.css'
|
||||
import styles from '@/styles/Home.module.css'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"@remix-run/react": "^1.7.6",
|
||||
"@remix-run/vercel": "^1.7.6",
|
||||
"@vercel/analytics": "^0.1.5",
|
||||
"@vercel/node": "^2.6.3",
|
||||
"@vercel/node": "^2.7.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
@@ -20,7 +20,7 @@
|
||||
"@remix-run/serve": "^1.7.6",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"eslint": "^8.27.0",
|
||||
"eslint": "^8.28.0",
|
||||
"typescript": "^4.9.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
1867
examples/remix/pnpm-lock.yaml
generated
1867
examples/remix/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
"@sveltejs/kit": "next",
|
||||
"@sveltejs/kit": "1.0.0-next.589",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-plugin-svelte": "^2.7.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"npmClient": "pnpm",
|
||||
"useWorkspaces": true,
|
||||
"packages": ["packages/*"],
|
||||
"command": {
|
||||
|
||||
30
package.json
30
package.json
@@ -3,30 +3,26 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"packageManager": "yarn@1.22.19",
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"nohoist": [
|
||||
"**/@types/**",
|
||||
"**/typedoc",
|
||||
"**/typedoc-plugin-markdown",
|
||||
"**/typedoc-plugin-mdn-links"
|
||||
]
|
||||
},
|
||||
"packageManager": "pnpm@7.24.2",
|
||||
"dependencies": {
|
||||
"lerna": "3.16.4"
|
||||
"lerna": "5.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "14.18.33",
|
||||
"@typescript-eslint/eslint-plugin": "5.21.0",
|
||||
"@typescript-eslint/parser": "5.21.0",
|
||||
"@vercel/build-utils": "*",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@vercel/next": "*",
|
||||
"async-retry": "1.2.3",
|
||||
"buffer-replace": "1.0.0",
|
||||
"create-svelte": "2.0.1",
|
||||
"dot": "1.1.3",
|
||||
"eslint": "8.14.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-jest": "26.1.5",
|
||||
"execa": "3.2.0",
|
||||
"fs-extra": "11.1.0",
|
||||
"husky": "7.0.4",
|
||||
"jest": "28.0.2",
|
||||
"json5": "2.1.1",
|
||||
@@ -34,22 +30,24 @@
|
||||
"node-fetch": "2.6.7",
|
||||
"npm-package-arg": "6.1.0",
|
||||
"prettier": "2.6.2",
|
||||
"source-map-support": "0.5.12",
|
||||
"ts-eager": "2.0.2",
|
||||
"ts-jest": "28.0.5",
|
||||
"turbo": "1.7.0-canary.9"
|
||||
},
|
||||
"scripts": {
|
||||
"lerna": "lerna",
|
||||
"version": "pnpm install && git add pnpm-lock.yaml",
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"publish-stable": "echo 'Run `yarn changelog` for instructions'",
|
||||
"publish-stable": "echo 'Run `pnpm changelog` for instructions'",
|
||||
"publish-canary": "git checkout main && git pull && lerna version prerelease --preid canary --message \"Publish Canary\" --exact",
|
||||
"publish-from-github": "./utils/publish.sh",
|
||||
"changelog": "node utils/changelog.js",
|
||||
"build": "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": "pnpm build && pnpm run pack && cd api && node -r ts-eager/register ./_lib/script/build.ts",
|
||||
"pre-commit": "lint-staged",
|
||||
"test": "jest --rootDir=\"test\" --testPathPattern=\"\\.test.js\"",
|
||||
"test-unit": "yarn test && node utils/gen.js && turbo run test-unit",
|
||||
"test-unit": "pnpm test && node utils/gen.js && turbo run test-unit",
|
||||
"test-integration-cli": "node utils/gen.js && turbo run test-integration-cli",
|
||||
"test-integration-once": "node utils/gen.js && turbo run test-integration-once",
|
||||
"test-integration-dev": "node utils/gen.js && turbo run test-integration-dev",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "5.7.5",
|
||||
"version": "5.9.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
@@ -13,8 +13,8 @@
|
||||
"scripts": {
|
||||
"build": "node build",
|
||||
"test": "jest --env node --verbose --runInBand --bail",
|
||||
"test-unit": "yarn test test/unit.*test.*",
|
||||
"test-integration-once": "yarn test test/integration.test.ts"
|
||||
"test-unit": "pnpm test test/unit.*test.*",
|
||||
"test-integration-once": "pnpm test test/integration.test.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iarna/toml": "2.2.3",
|
||||
@@ -25,8 +25,10 @@
|
||||
"@types/glob": "7.2.0",
|
||||
"@types/jest": "27.4.1",
|
||||
"@types/js-yaml": "3.12.1",
|
||||
"@types/minimatch": "^5.1.2",
|
||||
"@types/ms": "0.7.31",
|
||||
"@types/multistream": "2.1.1",
|
||||
"@types/node": "14.18.33",
|
||||
"@types/node-fetch": "^2.1.6",
|
||||
"@types/semver": "6.0.0",
|
||||
"@types/yazl": "2.4.2",
|
||||
@@ -36,8 +38,10 @@
|
||||
"async-sema": "2.1.4",
|
||||
"cross-spawn": "6.0.5",
|
||||
"end-of-stream": "1.4.1",
|
||||
"execa": "3.2.0",
|
||||
"fs-extra": "10.0.0",
|
||||
"glob": "8.0.3",
|
||||
"ignore": "4.0.6",
|
||||
"into-stream": "5.0.0",
|
||||
"js-yaml": "3.13.1",
|
||||
"minimatch": "3.0.4",
|
||||
|
||||
@@ -2,7 +2,7 @@ import path from 'path';
|
||||
import debug from '../debug';
|
||||
import FileFsRef from '../file-fs-ref';
|
||||
import { File, Files, Meta } from '../types';
|
||||
import { remove, mkdirp, readlink, symlink } from 'fs-extra';
|
||||
import { remove, mkdirp, readlink, symlink, chmod } from 'fs-extra';
|
||||
import streamToBuffer from './stream-to-buffer';
|
||||
|
||||
export interface DownloadedFiles {
|
||||
@@ -51,6 +51,12 @@ export async function downloadFile(
|
||||
): Promise<FileFsRef> {
|
||||
const { mode } = file;
|
||||
|
||||
if (isDirectory(mode)) {
|
||||
await mkdirp(fsPath);
|
||||
await chmod(fsPath, mode);
|
||||
return FileFsRef.fromFsPath({ mode, fsPath });
|
||||
}
|
||||
|
||||
// If the source is a symlink, try to create it instead of copying the file.
|
||||
// Note: creating symlinks on Windows requires admin priviliges or symlinks
|
||||
// enabled in the group policy. We may want to improve the error message.
|
||||
|
||||
@@ -6,7 +6,9 @@ import { lstat, Stats } from 'fs-extra';
|
||||
import { normalizePath } from './normalize-path';
|
||||
import FileFsRef from '../file-fs-ref';
|
||||
|
||||
export type GlobOptions = vanillaGlob_.IOptions;
|
||||
export interface GlobOptions extends vanillaGlob_.IOptions {
|
||||
includeDirectories?: boolean;
|
||||
}
|
||||
|
||||
const vanillaGlob = promisify(vanillaGlob_);
|
||||
|
||||
@@ -15,12 +17,7 @@ export default async function glob(
|
||||
opts: GlobOptions | string,
|
||||
mountpoint?: string
|
||||
): Promise<Record<string, FileFsRef>> {
|
||||
let options: GlobOptions;
|
||||
if (typeof opts === 'string') {
|
||||
options = { cwd: opts };
|
||||
} else {
|
||||
options = opts;
|
||||
}
|
||||
const options = typeof opts === 'string' ? { cwd: opts } : opts;
|
||||
|
||||
if (!options.cwd) {
|
||||
throw new Error(
|
||||
@@ -34,13 +31,18 @@ export default async function glob(
|
||||
|
||||
const results: Record<string, FileFsRef> = {};
|
||||
const statCache: Record<string, Stats> = {};
|
||||
const symlinks: Record<string, boolean | undefined> = {};
|
||||
|
||||
options.symlinks = {};
|
||||
options.statCache = statCache;
|
||||
options.stat = true;
|
||||
options.dot = true;
|
||||
const files = await vanillaGlob(pattern, {
|
||||
...options,
|
||||
symlinks,
|
||||
statCache,
|
||||
stat: true,
|
||||
dot: true,
|
||||
});
|
||||
|
||||
const files = await vanillaGlob(pattern, options);
|
||||
const dirs = new Set<string>();
|
||||
const dirsWithEntries = new Set<string>();
|
||||
|
||||
for (const relativePath of files) {
|
||||
const fsPath = normalizePath(path.join(options.cwd, relativePath));
|
||||
@@ -49,12 +51,20 @@ export default async function glob(
|
||||
stat,
|
||||
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`
|
||||
);
|
||||
const isSymlink = options.symlinks![fsPath];
|
||||
if (isSymlink || stat.isFile()) {
|
||||
const isSymlink = symlinks[fsPath];
|
||||
if (isSymlink || stat.isFile() || stat.isDirectory()) {
|
||||
if (isSymlink) {
|
||||
stat = await lstat(fsPath);
|
||||
}
|
||||
|
||||
// Some bookkeeping to track which directories already have entries within
|
||||
const dirname = path.dirname(relativePath);
|
||||
dirsWithEntries.add(dirname);
|
||||
if (stat.isDirectory()) {
|
||||
dirs.add(relativePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
let finalPath = relativePath;
|
||||
if (mountpoint) {
|
||||
finalPath = path.join(mountpoint, finalPath);
|
||||
@@ -64,5 +74,22 @@ export default async function glob(
|
||||
}
|
||||
}
|
||||
|
||||
// Add empty directory entries
|
||||
if (options.includeDirectories) {
|
||||
for (const relativePath of dirs) {
|
||||
if (dirsWithEntries.has(relativePath)) continue;
|
||||
|
||||
let finalPath = relativePath;
|
||||
if (mountpoint) {
|
||||
finalPath = path.join(mountpoint, finalPath);
|
||||
}
|
||||
|
||||
const fsPath = normalizePath(path.join(options.cwd, relativePath));
|
||||
const stat = statCache[fsPath];
|
||||
|
||||
results[finalPath] = new FileFsRef({ mode: stat.mode, fsPath });
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -137,6 +137,9 @@ export function execAsync(
|
||||
child.on('close', (code, signal) => {
|
||||
if (code === 0 || opts.ignoreNon0Exit) {
|
||||
return resolve({
|
||||
// ignoring the next line due to do some Node.js type issue when we removed hoisting of dependencies in https://github.com/vercel/vercel/pull/9198
|
||||
// should eventually be fixed when this method is remove by https://github.com/vercel/vercel/pull/9200 or we update to Node 16
|
||||
// @ts-ignore
|
||||
code,
|
||||
stdout: Buffer.concat(stdoutList).toString(),
|
||||
stderr: Buffer.concat(stderrList).toString(),
|
||||
|
||||
@@ -8,6 +8,7 @@ import download, {
|
||||
downloadFile,
|
||||
DownloadedFiles,
|
||||
isSymbolicLink,
|
||||
isDirectory,
|
||||
} from './fs/download';
|
||||
import getWriteableDirectory from './fs/get-writable-directory';
|
||||
import glob, { GlobOptions } from './fs/glob';
|
||||
@@ -82,6 +83,7 @@ export {
|
||||
streamToBuffer,
|
||||
debug,
|
||||
isSymbolicLink,
|
||||
isDirectory,
|
||||
getLambdaOptionsFromFunction,
|
||||
scanParentDirs,
|
||||
getIgnoreFilter,
|
||||
|
||||
3
packages/build-utils/test/fixtures/15-npm-8-legacy-peer-deps/package-lock.json
generated
vendored
3
packages/build-utils/test/fixtures/15-npm-8-legacy-peer-deps/package-lock.json
generated
vendored
@@ -7,6 +7,9 @@
|
||||
"dependencies": {
|
||||
"react": "16.8.0",
|
||||
"swr": "1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "16.x"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
|
||||
195
packages/build-utils/test/unit.download.test.ts
vendored
Normal file
195
packages/build-utils/test/unit.download.test.ts
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
import path from 'path';
|
||||
import fs, { readlink } from 'fs-extra';
|
||||
import { strict as assert, strictEqual } from 'assert';
|
||||
import { download, glob, FileBlob } from '../src';
|
||||
|
||||
describe('download()', () => {
|
||||
let warningMessages: string[];
|
||||
const originalConsoleWarn = console.warn;
|
||||
beforeEach(() => {
|
||||
warningMessages = [];
|
||||
console.warn = m => {
|
||||
warningMessages.push(m);
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
console.warn = originalConsoleWarn;
|
||||
});
|
||||
|
||||
it('should re-create FileFsRef symlinks properly', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping test on windows');
|
||||
return;
|
||||
}
|
||||
const files = await glob('**', path.join(__dirname, 'symlinks'));
|
||||
assert.equal(Object.keys(files).length, 4);
|
||||
|
||||
const outDir = path.join(__dirname, 'symlinks-out');
|
||||
await fs.remove(outDir);
|
||||
|
||||
const files2 = await download(files, outDir);
|
||||
assert.equal(Object.keys(files2).length, 4);
|
||||
|
||||
const [linkStat, linkDirStat, aStat] = await Promise.all([
|
||||
fs.lstat(path.join(outDir, 'link.txt')),
|
||||
fs.lstat(path.join(outDir, 'link-dir')),
|
||||
fs.lstat(path.join(outDir, 'a.txt')),
|
||||
]);
|
||||
assert(linkStat.isSymbolicLink());
|
||||
assert(linkDirStat.isSymbolicLink());
|
||||
assert(aStat.isFile());
|
||||
|
||||
const [linkDirContents, linkTextContents] = await Promise.all([
|
||||
readlink(path.join(outDir, 'link-dir')),
|
||||
readlink(path.join(outDir, 'link.txt')),
|
||||
]);
|
||||
|
||||
strictEqual(linkDirContents, 'dir');
|
||||
strictEqual(linkTextContents, './a.txt');
|
||||
});
|
||||
|
||||
it('should re-create FileBlob symlinks properly', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping test on windows');
|
||||
return;
|
||||
}
|
||||
|
||||
const files = {
|
||||
'a.txt': new FileBlob({
|
||||
mode: 33188,
|
||||
contentType: undefined,
|
||||
data: 'a text',
|
||||
}),
|
||||
'dir/b.txt': new FileBlob({
|
||||
mode: 33188,
|
||||
contentType: undefined,
|
||||
data: 'b text',
|
||||
}),
|
||||
'link-dir': new FileBlob({
|
||||
mode: 41453,
|
||||
contentType: undefined,
|
||||
data: 'dir',
|
||||
}),
|
||||
'link.txt': new FileBlob({
|
||||
mode: 41453,
|
||||
contentType: undefined,
|
||||
data: 'a.txt',
|
||||
}),
|
||||
};
|
||||
|
||||
strictEqual(Object.keys(files).length, 4);
|
||||
|
||||
const outDir = path.join(__dirname, 'symlinks-out');
|
||||
await fs.remove(outDir);
|
||||
|
||||
const files2 = await download(files, outDir);
|
||||
strictEqual(Object.keys(files2).length, 4);
|
||||
|
||||
const [linkStat, linkDirStat, aStat, dirStat] = await Promise.all([
|
||||
fs.lstat(path.join(outDir, 'link.txt')),
|
||||
fs.lstat(path.join(outDir, 'link-dir')),
|
||||
fs.lstat(path.join(outDir, 'a.txt')),
|
||||
fs.lstat(path.join(outDir, 'dir')),
|
||||
]);
|
||||
|
||||
assert(linkStat.isSymbolicLink());
|
||||
assert(linkDirStat.isSymbolicLink());
|
||||
assert(aStat.isFile());
|
||||
assert(dirStat.isDirectory());
|
||||
|
||||
const [linkDirContents, linkTextContents] = await Promise.all([
|
||||
readlink(path.join(outDir, 'link-dir')),
|
||||
readlink(path.join(outDir, 'link.txt')),
|
||||
]);
|
||||
|
||||
strictEqual(linkDirContents, 'dir');
|
||||
strictEqual(linkTextContents, 'a.txt');
|
||||
});
|
||||
|
||||
it('should download symlinks even with incorrect file', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping test on windows');
|
||||
return;
|
||||
}
|
||||
const files = {
|
||||
'dir/file.txt': new FileBlob({
|
||||
mode: 33188,
|
||||
contentType: undefined,
|
||||
data: 'file text',
|
||||
}),
|
||||
linkdir: new FileBlob({
|
||||
mode: 41453,
|
||||
contentType: undefined,
|
||||
data: 'dir',
|
||||
}),
|
||||
'linkdir/file.txt': new FileBlob({
|
||||
mode: 33188,
|
||||
contentType: undefined,
|
||||
data: 'this file should be discarded',
|
||||
}),
|
||||
};
|
||||
|
||||
const outDir = path.join(__dirname, 'symlinks-out');
|
||||
await fs.remove(outDir);
|
||||
await fs.mkdirp(outDir);
|
||||
|
||||
await download(files, outDir);
|
||||
|
||||
const [dir, file, linkdir] = await Promise.all([
|
||||
fs.lstat(path.join(outDir, 'dir')),
|
||||
fs.lstat(path.join(outDir, 'dir/file.txt')),
|
||||
fs.lstat(path.join(outDir, 'linkdir')),
|
||||
]);
|
||||
expect(dir.isFile()).toBe(false);
|
||||
expect(dir.isSymbolicLink()).toBe(false);
|
||||
|
||||
expect(file.isFile()).toBe(true);
|
||||
expect(file.isSymbolicLink()).toBe(false);
|
||||
|
||||
expect(linkdir.isSymbolicLink()).toBe(true);
|
||||
|
||||
expect(warningMessages).toEqual([
|
||||
'Warning: file "linkdir/file.txt" is within a symlinked directory "linkdir" and will be ignored',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create empty directory entries', async () => {
|
||||
const outDir = path.join(__dirname, 'symlinks-out');
|
||||
await fs.remove(outDir);
|
||||
const files = {
|
||||
'empty-dir': new FileBlob({
|
||||
mode: 16877, // drwxr-xr-x
|
||||
contentType: undefined,
|
||||
data: '',
|
||||
}),
|
||||
dir: new FileBlob({
|
||||
mode: 16877,
|
||||
contentType: undefined,
|
||||
data: '',
|
||||
}),
|
||||
'dir/subdir': new FileBlob({
|
||||
mode: 16877,
|
||||
contentType: undefined,
|
||||
data: '',
|
||||
}),
|
||||
'another/subdir': new FileBlob({
|
||||
mode: 16895, // drwxrwxrwx
|
||||
contentType: undefined,
|
||||
data: '',
|
||||
}),
|
||||
};
|
||||
|
||||
await download(files, outDir);
|
||||
|
||||
for (const [p, f] of Object.entries(files)) {
|
||||
const stat = await fs.lstat(path.join(outDir, p));
|
||||
expect(stat.isDirectory()).toEqual(true);
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
// Don't test Windows since it doesn't support the same permissions
|
||||
expect(stat.mode).toEqual(f.mode);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
65
packages/build-utils/test/unit.glob.test.ts
vendored
Normal file
65
packages/build-utils/test/unit.glob.test.ts
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { tmpdir } from 'os';
|
||||
import { glob, isDirectory } from '../src';
|
||||
|
||||
describe('glob()', () => {
|
||||
it('should not return entries for empty directories by default', async () => {
|
||||
const dir = await fs.mkdtemp(join(tmpdir(), 'build-utils-test'));
|
||||
try {
|
||||
await Promise.all([
|
||||
fs.writeFile(join(dir, 'root.txt'), 'file at the root'),
|
||||
fs.mkdirp(join(dir, 'empty-dir')),
|
||||
fs
|
||||
.mkdirp(join(dir, 'dir-with-file'))
|
||||
.then(() =>
|
||||
fs.writeFile(join(dir, 'dir-with-file/data.json'), '{"a":"b"}')
|
||||
),
|
||||
fs.mkdirp(join(dir, 'another/subdir')),
|
||||
]);
|
||||
const files = await glob('**', dir);
|
||||
const fileNames = Object.keys(files).sort();
|
||||
expect(fileNames).toHaveLength(2);
|
||||
expect(fileNames).toEqual(['dir-with-file/data.json', 'root.txt']);
|
||||
expect(isDirectory(files['dir-with-file/data.json'].mode)).toEqual(false);
|
||||
expect(isDirectory(files['root.txt'].mode)).toEqual(false);
|
||||
expect(files['dir-with-file']).toBeUndefined();
|
||||
expect(files['another/subdir']).toBeUndefined();
|
||||
expect(files['empty-dir']).toBeUndefined();
|
||||
} finally {
|
||||
await fs.remove(dir);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return entries for empty directories with `includeDirectories: true`', async () => {
|
||||
const dir = await fs.mkdtemp(join(tmpdir(), 'build-utils-test'));
|
||||
try {
|
||||
await Promise.all([
|
||||
fs.writeFile(join(dir, 'root.txt'), 'file at the root'),
|
||||
fs.mkdirp(join(dir, 'empty-dir')),
|
||||
fs
|
||||
.mkdirp(join(dir, 'dir-with-file'))
|
||||
.then(() =>
|
||||
fs.writeFile(join(dir, 'dir-with-file/data.json'), '{"a":"b"}')
|
||||
),
|
||||
fs.mkdirp(join(dir, 'another/subdir')),
|
||||
]);
|
||||
const files = await glob('**', { cwd: dir, includeDirectories: true });
|
||||
const fileNames = Object.keys(files).sort();
|
||||
expect(fileNames).toHaveLength(4);
|
||||
expect(fileNames).toEqual([
|
||||
'another/subdir',
|
||||
'dir-with-file/data.json',
|
||||
'empty-dir',
|
||||
'root.txt',
|
||||
]);
|
||||
expect(isDirectory(files['another/subdir'].mode)).toEqual(true);
|
||||
expect(isDirectory(files['empty-dir'].mode)).toEqual(true);
|
||||
expect(isDirectory(files['dir-with-file/data.json'].mode)).toEqual(false);
|
||||
expect(isDirectory(files['root.txt'].mode)).toEqual(false);
|
||||
expect(files['dir-with-file']).toBeUndefined();
|
||||
} finally {
|
||||
await fs.remove(dir);
|
||||
}
|
||||
});
|
||||
});
|
||||
178
packages/build-utils/test/unit.test.ts
vendored
178
packages/build-utils/test/unit.test.ts
vendored
@@ -1,18 +1,15 @@
|
||||
import ms from 'ms';
|
||||
import path from 'path';
|
||||
import fs, { readlink } from 'fs-extra';
|
||||
import { strict as assert, strictEqual } from 'assert';
|
||||
import fs from 'fs-extra';
|
||||
import { strict as assert } from 'assert';
|
||||
import { getSupportedNodeVersion } from '../src/fs/node-version';
|
||||
import download from '../src/fs/download';
|
||||
import {
|
||||
glob,
|
||||
getNodeVersion,
|
||||
getLatestNodeVersion,
|
||||
getDiscontinuedNodeVersions,
|
||||
runNpmInstall,
|
||||
runPackageJsonScript,
|
||||
scanParentDirs,
|
||||
FileBlob,
|
||||
Prerender,
|
||||
} from '../src';
|
||||
|
||||
@@ -49,143 +46,6 @@ afterEach(() => {
|
||||
console.warn = originalConsoleWarn;
|
||||
});
|
||||
|
||||
it('should re-create FileFsRef symlinks properly', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping test on windows');
|
||||
return;
|
||||
}
|
||||
const files = await glob('**', path.join(__dirname, 'symlinks'));
|
||||
assert.equal(Object.keys(files).length, 4);
|
||||
|
||||
const outDir = path.join(__dirname, 'symlinks-out');
|
||||
await fs.remove(outDir);
|
||||
|
||||
const files2 = await download(files, outDir);
|
||||
assert.equal(Object.keys(files2).length, 4);
|
||||
|
||||
const [linkStat, linkDirStat, aStat] = await Promise.all([
|
||||
fs.lstat(path.join(outDir, 'link.txt')),
|
||||
fs.lstat(path.join(outDir, 'link-dir')),
|
||||
fs.lstat(path.join(outDir, 'a.txt')),
|
||||
]);
|
||||
assert(linkStat.isSymbolicLink());
|
||||
assert(linkDirStat.isSymbolicLink());
|
||||
assert(aStat.isFile());
|
||||
|
||||
const [linkDirContents, linkTextContents] = await Promise.all([
|
||||
readlink(path.join(outDir, 'link-dir')),
|
||||
readlink(path.join(outDir, 'link.txt')),
|
||||
]);
|
||||
|
||||
strictEqual(linkDirContents, 'dir');
|
||||
strictEqual(linkTextContents, './a.txt');
|
||||
});
|
||||
|
||||
it('should re-create FileBlob symlinks properly', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping test on windows');
|
||||
return;
|
||||
}
|
||||
|
||||
const files = {
|
||||
'a.txt': new FileBlob({
|
||||
mode: 33188,
|
||||
contentType: undefined,
|
||||
data: 'a text',
|
||||
}),
|
||||
'dir/b.txt': new FileBlob({
|
||||
mode: 33188,
|
||||
contentType: undefined,
|
||||
data: 'b text',
|
||||
}),
|
||||
'link-dir': new FileBlob({
|
||||
mode: 41453,
|
||||
contentType: undefined,
|
||||
data: 'dir',
|
||||
}),
|
||||
'link.txt': new FileBlob({
|
||||
mode: 41453,
|
||||
contentType: undefined,
|
||||
data: 'a.txt',
|
||||
}),
|
||||
};
|
||||
|
||||
strictEqual(Object.keys(files).length, 4);
|
||||
|
||||
const outDir = path.join(__dirname, 'symlinks-out');
|
||||
await fs.remove(outDir);
|
||||
|
||||
const files2 = await download(files, outDir);
|
||||
strictEqual(Object.keys(files2).length, 4);
|
||||
|
||||
const [linkStat, linkDirStat, aStat, dirStat] = await Promise.all([
|
||||
fs.lstat(path.join(outDir, 'link.txt')),
|
||||
fs.lstat(path.join(outDir, 'link-dir')),
|
||||
fs.lstat(path.join(outDir, 'a.txt')),
|
||||
fs.lstat(path.join(outDir, 'dir')),
|
||||
]);
|
||||
|
||||
assert(linkStat.isSymbolicLink());
|
||||
assert(linkDirStat.isSymbolicLink());
|
||||
assert(aStat.isFile());
|
||||
assert(dirStat.isDirectory());
|
||||
|
||||
const [linkDirContents, linkTextContents] = await Promise.all([
|
||||
readlink(path.join(outDir, 'link-dir')),
|
||||
readlink(path.join(outDir, 'link.txt')),
|
||||
]);
|
||||
|
||||
strictEqual(linkDirContents, 'dir');
|
||||
strictEqual(linkTextContents, 'a.txt');
|
||||
});
|
||||
|
||||
it('should download symlinks even with incorrect file', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping test on windows');
|
||||
return;
|
||||
}
|
||||
const files = {
|
||||
'dir/file.txt': new FileBlob({
|
||||
mode: 33188,
|
||||
contentType: undefined,
|
||||
data: 'file text',
|
||||
}),
|
||||
linkdir: new FileBlob({
|
||||
mode: 41453,
|
||||
contentType: undefined,
|
||||
data: 'dir',
|
||||
}),
|
||||
'linkdir/file.txt': new FileBlob({
|
||||
mode: 33188,
|
||||
contentType: undefined,
|
||||
data: 'this file should be discarded',
|
||||
}),
|
||||
};
|
||||
|
||||
const outDir = path.join(__dirname, 'symlinks-out');
|
||||
await fs.remove(outDir);
|
||||
await fs.mkdirp(outDir);
|
||||
|
||||
await download(files, outDir);
|
||||
|
||||
const [dir, file, linkdir] = await Promise.all([
|
||||
fs.lstat(path.join(outDir, 'dir')),
|
||||
fs.lstat(path.join(outDir, 'dir/file.txt')),
|
||||
fs.lstat(path.join(outDir, 'linkdir')),
|
||||
]);
|
||||
expect(dir.isFile()).toBe(false);
|
||||
expect(dir.isSymbolicLink()).toBe(false);
|
||||
|
||||
expect(file.isFile()).toBe(true);
|
||||
expect(file.isSymbolicLink()).toBe(false);
|
||||
|
||||
expect(linkdir.isSymbolicLink()).toBe(true);
|
||||
|
||||
expect(warningMessages).toEqual([
|
||||
'Warning: file "linkdir/file.txt" is within a symlinked directory "linkdir" and will be ignored',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should only match supported node versions, otherwise throw an error', async () => {
|
||||
expect(await getSupportedNodeVersion('14.x', false)).toHaveProperty(
|
||||
'major',
|
||||
@@ -444,21 +304,21 @@ it('should support initialHeaders and initialStatus correctly', async () => {
|
||||
});
|
||||
|
||||
it('should support require by path for legacy builders', () => {
|
||||
const index = require('@vercel/build-utils');
|
||||
const index = require('../');
|
||||
|
||||
const download2 = require('@vercel/build-utils/fs/download.js');
|
||||
const getWriteableDirectory2 = require('@vercel/build-utils/fs/get-writable-directory.js');
|
||||
const glob2 = require('@vercel/build-utils/fs/glob.js');
|
||||
const rename2 = require('@vercel/build-utils/fs/rename.js');
|
||||
const download2 = require('../fs/download.js');
|
||||
const getWriteableDirectory2 = require('../fs/get-writable-directory.js');
|
||||
const glob2 = require('../fs/glob.js');
|
||||
const rename2 = require('../fs/rename.js');
|
||||
const {
|
||||
runNpmInstall: runNpmInstall2,
|
||||
} = require('@vercel/build-utils/fs/run-user-scripts.js');
|
||||
const streamToBuffer2 = require('@vercel/build-utils/fs/stream-to-buffer.js');
|
||||
} = require('../fs/run-user-scripts.js');
|
||||
const streamToBuffer2 = require('../fs/stream-to-buffer.js');
|
||||
|
||||
const FileBlob2 = require('@vercel/build-utils/file-blob.js');
|
||||
const FileFsRef2 = require('@vercel/build-utils/file-fs-ref.js');
|
||||
const FileRef2 = require('@vercel/build-utils/file-ref.js');
|
||||
const { Lambda: Lambda2 } = require('@vercel/build-utils/lambda.js');
|
||||
const FileBlob2 = require('../file-blob.js');
|
||||
const FileFsRef2 = require('../file-fs-ref.js');
|
||||
const FileRef2 = require('../file-ref.js');
|
||||
const { Lambda: Lambda2 } = require('../lambda.js');
|
||||
|
||||
expect(download2).toBe(index.download);
|
||||
expect(getWriteableDirectory2).toBe(index.getWriteableDirectory);
|
||||
@@ -560,9 +420,9 @@ it('should detect package.json in nested backend', async () => {
|
||||
'../../node/test/fixtures/18.1-nested-packagejson/backend'
|
||||
);
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('yarn');
|
||||
expect(result.lockfileVersion).toEqual(undefined);
|
||||
// There is no lockfile but this test will pick up vercel/vercel/yarn.lock
|
||||
expect(result.cliType).toEqual('pnpm');
|
||||
// There is no lockfile but this test will pick up vercel/vercel/pnpm-lock.yaml
|
||||
expect(result.lockfileVersion).toEqual(5.4);
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
@@ -572,9 +432,9 @@ it('should detect package.json in nested frontend', async () => {
|
||||
'../../node/test/fixtures/18.1-nested-packagejson/frontend'
|
||||
);
|
||||
const result = await scanParentDirs(fixture);
|
||||
expect(result.cliType).toEqual('yarn');
|
||||
expect(result.lockfileVersion).toEqual(undefined);
|
||||
// There is no lockfile but this test will pick up vercel/vercel/yarn.lock
|
||||
expect(result.cliType).toEqual('pnpm');
|
||||
// There is no lockfile but this test will pick up vercel/vercel/pnpm-lock.yaml
|
||||
expect(result.lockfileVersion).toEqual(5.4);
|
||||
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||
});
|
||||
|
||||
|
||||
14
packages/build-utils/test/unit.walk.test.ts
vendored
14
packages/build-utils/test/unit.walk.test.ts
vendored
@@ -2,7 +2,7 @@ import { walkParentDirs } from '../src';
|
||||
import { strict } from 'assert';
|
||||
import { join } from 'path';
|
||||
import { promises } from 'fs';
|
||||
const { deepEqual, notDeepEqual, fail } = strict;
|
||||
const { notDeepEqual, fail } = strict;
|
||||
const { readFile } = promises;
|
||||
const fixture = (name: string) => join(__dirname, 'walk', name);
|
||||
const filename = 'file.txt';
|
||||
@@ -10,7 +10,7 @@ const filename = 'file.txt';
|
||||
async function assertContent(target: string | null, contents: string) {
|
||||
notDeepEqual(target, null);
|
||||
const actual = await readFile(target!, 'utf8');
|
||||
deepEqual(actual.trim(), contents.trim());
|
||||
strict.deepEqual(actual.trim(), contents.trim());
|
||||
}
|
||||
|
||||
describe('Test `walkParentDirs`', () => {
|
||||
@@ -21,7 +21,7 @@ describe('Test `walkParentDirs`', () => {
|
||||
await walkParentDirs({ base, start, filename });
|
||||
fail('Expected error');
|
||||
} catch (error) {
|
||||
deepEqual(
|
||||
strict.deepEqual(
|
||||
(error as Error).message,
|
||||
'Expected "base" to be absolute path'
|
||||
);
|
||||
@@ -35,7 +35,7 @@ describe('Test `walkParentDirs`', () => {
|
||||
await walkParentDirs({ base, start, filename });
|
||||
fail('Expected error');
|
||||
} catch (error) {
|
||||
deepEqual(
|
||||
strict.deepEqual(
|
||||
(error as Error).message,
|
||||
'Expected "start" to be absolute path'
|
||||
);
|
||||
@@ -67,21 +67,21 @@ describe('Test `walkParentDirs`', () => {
|
||||
const base = fixture('not-found');
|
||||
const start = base;
|
||||
const target = await walkParentDirs({ base, start, filename });
|
||||
deepEqual(target, null);
|
||||
strict.deepEqual(target, null);
|
||||
});
|
||||
|
||||
it('should not find nested two', async () => {
|
||||
const base = fixture('not-found');
|
||||
const start = join(base, 'two');
|
||||
const target = await walkParentDirs({ base, start, filename });
|
||||
deepEqual(target, null);
|
||||
strict.deepEqual(target, null);
|
||||
});
|
||||
|
||||
it('should not find nested three', async () => {
|
||||
const base = fixture('not-found');
|
||||
const start = join(base, 'two', 'three');
|
||||
const target = await walkParentDirs({ base, start, filename });
|
||||
deepEqual(target, null);
|
||||
strict.deepEqual(target, null);
|
||||
});
|
||||
|
||||
it('should find only one', async () => {
|
||||
|
||||
@@ -41,8 +41,8 @@ To develop Vercel CLI, first check out the source code, install dependencies, an
|
||||
```bash
|
||||
git clone https://github.com/vercel/vercel.git
|
||||
cd vercel
|
||||
yarn
|
||||
yarn build
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
At this point you can make modifications to the CLI source code and test them out locally. The CLI source code is located in the `packages/cli` directory.
|
||||
@@ -51,15 +51,15 @@ At this point you can make modifications to the CLI source code and test them ou
|
||||
cd packages/cli
|
||||
```
|
||||
|
||||
### `yarn dev <cli-commands...>`
|
||||
### `pnpm dev <cli-commands...>`
|
||||
|
||||
From within the `packages/cli` directory, you can use the "dev" script to quickly execute Vercel CLI from its TypeScript source code directly (without having to manually compile first). For example:
|
||||
|
||||
```bash
|
||||
yarn dev deploy
|
||||
yarn dev whoami
|
||||
yarn dev login
|
||||
yarn dev switch --debug
|
||||
pnpm dev deploy
|
||||
pnpm dev whoami
|
||||
pnpm dev login
|
||||
pnpm dev switch --debug
|
||||
```
|
||||
|
||||
When you are satisfied with your changes, make a commit and create a pull request!
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "28.11.1",
|
||||
"version": "28.12.8",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -13,9 +13,9 @@
|
||||
"scripts": {
|
||||
"preinstall": "node ./scripts/preinstall.js",
|
||||
"test": "jest --env node --verbose --bail",
|
||||
"test-unit": "yarn test test/unit/",
|
||||
"test-unit": "pnpm test test/unit/",
|
||||
"test-integration-cli": "rimraf test/fixtures/integration && ava test/integration.js --serial --fail-fast --verbose",
|
||||
"test-integration-dev": "yarn test test/dev/",
|
||||
"test-integration-dev": "pnpm test test/dev/",
|
||||
"coverage": "codecov",
|
||||
"build": "ts-node ./scripts/build.ts",
|
||||
"dev": "ts-node ./src/index.ts"
|
||||
@@ -41,16 +41,16 @@
|
||||
"node": ">= 14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "5.7.5",
|
||||
"@vercel/go": "2.2.24",
|
||||
"@vercel/hydrogen": "0.0.38",
|
||||
"@vercel/next": "3.3.9",
|
||||
"@vercel/node": "2.8.6",
|
||||
"@vercel/python": "3.1.34",
|
||||
"@vercel/redwood": "1.0.45",
|
||||
"@vercel/remix": "1.1.7",
|
||||
"@vercel/ruby": "1.3.50",
|
||||
"@vercel/static-build": "1.1.1"
|
||||
"@vercel/build-utils": "5.9.0",
|
||||
"@vercel/go": "2.2.30",
|
||||
"@vercel/hydrogen": "0.0.44",
|
||||
"@vercel/next": "3.3.17",
|
||||
"@vercel/node": "2.8.14",
|
||||
"@vercel/python": "3.1.40",
|
||||
"@vercel/redwood": "1.0.51",
|
||||
"@vercel/remix": "1.2.7",
|
||||
"@vercel/ruby": "1.3.56",
|
||||
"@vercel/static-build": "1.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@alex_neo/jest-expect-message": "1.0.5",
|
||||
@@ -83,6 +83,7 @@
|
||||
"@types/npm-package-arg": "6.1.0",
|
||||
"@types/pluralize": "0.0.29",
|
||||
"@types/psl": "1.1.0",
|
||||
"@types/qs": "6.9.7",
|
||||
"@types/semver": "6.0.1",
|
||||
"@types/tar-fs": "1.16.1",
|
||||
"@types/text-table": "0.2.0",
|
||||
@@ -92,13 +93,13 @@
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@types/yauzl-promise": "2.1.0",
|
||||
"@vercel/client": "12.2.26",
|
||||
"@vercel/error-utils": "1.0.3",
|
||||
"@vercel/frameworks": "1.1.18",
|
||||
"@vercel/fs-detectors": "3.6.2",
|
||||
"@vercel/client": "12.3.2",
|
||||
"@vercel/error-utils": "1.0.8",
|
||||
"@vercel/frameworks": "1.2.4",
|
||||
"@vercel/fs-detectors": "3.7.5",
|
||||
"@vercel/fun": "1.0.4",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@vercel/routing-utils": "2.1.3",
|
||||
"@vercel/routing-utils": "2.1.8",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
"ajv": "6.12.2",
|
||||
"alpha-sort": "2.0.1",
|
||||
@@ -138,6 +139,7 @@
|
||||
"is-port-reachable": "3.1.0",
|
||||
"is-url": "1.2.2",
|
||||
"jaro-winkler": "0.2.8",
|
||||
"jest-matcher-utils": "29.3.1",
|
||||
"json5": "2.2.1",
|
||||
"jsonlines": "0.1.1",
|
||||
"line-async-iterator": "3.0.0",
|
||||
|
||||
@@ -47,12 +47,12 @@ async function main() {
|
||||
const dependencies = Object.keys(pkg?.dependencies ?? {});
|
||||
// Do the initial `ncc` build
|
||||
console.log('Dependencies:', dependencies);
|
||||
const externs = [];
|
||||
const externs: Array<string> = [];
|
||||
for (const dep of dependencies) {
|
||||
externs.push('--external', dep);
|
||||
}
|
||||
const args = ['ncc', 'build', 'src/index.ts', ...externs];
|
||||
await execa('yarn', args, { stdio: 'inherit', cwd: dirRoot });
|
||||
await execa('pnpm', args, { stdio: 'inherit', cwd: dirRoot });
|
||||
|
||||
// `ncc` has some issues with `@vercel/fun`'s runtime files:
|
||||
// - Executable bits on the `bootstrap` files appear to be lost:
|
||||
@@ -66,10 +66,7 @@ async function main() {
|
||||
// get compiled into the final ncc bundle file, however, we want them to be
|
||||
// present in the npm package because the contents of those files are involved
|
||||
// with `fun`'s cache invalidation mechanism and they need to be shasum'd.
|
||||
const runtimes = join(
|
||||
dirRoot,
|
||||
'../../node_modules/@vercel/fun/dist/src/runtimes'
|
||||
);
|
||||
const runtimes = join(dirRoot, 'node_modules/@vercel/fun/dist/src/runtimes');
|
||||
await cpy('**/*', join(distRoot, 'runtimes'), {
|
||||
parents: true,
|
||||
cwd: runtimes,
|
||||
|
||||
@@ -17,7 +17,11 @@ import {
|
||||
BuildResultV3,
|
||||
NowBuildError,
|
||||
} from '@vercel/build-utils';
|
||||
import { detectBuilders } from '@vercel/fs-detectors';
|
||||
import {
|
||||
detectBuilders,
|
||||
detectFrameworkRecord,
|
||||
LocalFileSystemDetector,
|
||||
} from '@vercel/fs-detectors';
|
||||
import minimatch from 'minimatch';
|
||||
import {
|
||||
appendRoutesToPhase,
|
||||
@@ -59,6 +63,9 @@ import { toEnumerableError } from '../util/error';
|
||||
import { validateConfig } from '../util/validate-config';
|
||||
|
||||
import { setMonorepoDefaultSettings } from '../util/build/monorepo';
|
||||
import frameworks from '@vercel/frameworks';
|
||||
import { detectFrameworkVersion } from '@vercel/fs-detectors';
|
||||
import semver from 'semver';
|
||||
|
||||
type BuildResult = BuildResultV2 | BuildResultV3;
|
||||
|
||||
@@ -69,6 +76,20 @@ interface SerializedBuilder extends Builder {
|
||||
apiVersion: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Output API `config.json` file interface.
|
||||
*/
|
||||
interface BuildOutputConfig {
|
||||
version?: 3;
|
||||
wildcard?: BuildResultV2Typical['wildcard'];
|
||||
images?: BuildResultV2Typical['images'];
|
||||
routes?: BuildResultV2Typical['routes'];
|
||||
overrides?: Record<string, PathOverride>;
|
||||
framework?: {
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Contents of the `builds.json` file.
|
||||
*/
|
||||
@@ -434,7 +455,7 @@ async function doBuild(
|
||||
// Execute Builders for detected entrypoints
|
||||
// TODO: parallelize builds (except for frontend)
|
||||
const sortedBuilders = sortBuilders(builds);
|
||||
const buildResults: Map<Builder, BuildResult> = new Map();
|
||||
const buildResults: Map<Builder, BuildResult | BuildOutputConfig> = new Map();
|
||||
const overrides: PathOverride[] = [];
|
||||
const repoRootPath = cwd;
|
||||
const corepackShimDir = await initCorepack({ repoRootPath });
|
||||
@@ -538,8 +559,7 @@ async function doBuild(
|
||||
|
||||
// Merge existing `config.json` file into the one that will be produced
|
||||
const configPath = join(outputDir, 'config.json');
|
||||
// TODO: properly type
|
||||
const existingConfig = await readJSONFile<any>(configPath);
|
||||
const existingConfig = await readJSONFile<BuildOutputConfig>(configPath);
|
||||
if (existingConfig instanceof CantParseJSONFile) {
|
||||
throw existingConfig;
|
||||
}
|
||||
@@ -585,15 +605,17 @@ async function doBuild(
|
||||
const mergedOverrides: Record<string, PathOverride> =
|
||||
overrides.length > 0 ? Object.assign({}, ...overrides) : undefined;
|
||||
|
||||
const framework = await getFramework(cwd, buildResults);
|
||||
|
||||
// Write out the final `config.json` file based on the
|
||||
// user configuration and Builder build results
|
||||
// TODO: properly type
|
||||
const config = {
|
||||
const config: BuildOutputConfig = {
|
||||
version: 3,
|
||||
routes: mergedRoutes,
|
||||
images: mergedImages,
|
||||
wildcard: mergedWildcard,
|
||||
overrides: mergedOverrides,
|
||||
framework,
|
||||
};
|
||||
await fs.writeJSON(join(outputDir, 'config.json'), config, { spaces: 2 });
|
||||
|
||||
@@ -608,6 +630,50 @@ async function doBuild(
|
||||
);
|
||||
}
|
||||
|
||||
async function getFramework(
|
||||
cwd: string,
|
||||
buildResults: Map<Builder, BuildResult | BuildOutputConfig>
|
||||
): Promise<{ version: string } | undefined> {
|
||||
const detectedFramework = await detectFrameworkRecord({
|
||||
fs: new LocalFileSystemDetector(cwd),
|
||||
frameworkList: frameworks,
|
||||
});
|
||||
|
||||
if (!detectedFramework) {
|
||||
return;
|
||||
}
|
||||
|
||||
// determine framework version from build result
|
||||
if (detectedFramework.useRuntime) {
|
||||
for (const [build, buildResult] of buildResults.entries()) {
|
||||
if (
|
||||
'framework' in buildResult &&
|
||||
build.use === detectedFramework.useRuntime.use
|
||||
) {
|
||||
return buildResult.framework;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// determine framework version from listed package.json version
|
||||
if (detectedFramework.detectedVersion) {
|
||||
// check for a valid, explicit version, not a range
|
||||
if (semver.valid(detectedFramework.detectedVersion)) {
|
||||
return {
|
||||
version: detectedFramework.detectedVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// determine framework version with runtime lookup
|
||||
const frameworkVersion = detectFrameworkVersion(detectedFramework);
|
||||
if (frameworkVersion) {
|
||||
return {
|
||||
version: frameworkVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function expandBuild(files: string[], build: Builder): Builder[] {
|
||||
if (!build.use) {
|
||||
throw new NowBuildError({
|
||||
@@ -648,7 +714,7 @@ function expandBuild(files: string[], build: Builder): Builder[] {
|
||||
|
||||
function mergeImages(
|
||||
images: BuildResultV2Typical['images'],
|
||||
buildResults: Iterable<BuildResult>
|
||||
buildResults: Iterable<BuildResult | BuildOutputConfig>
|
||||
): BuildResultV2Typical['images'] {
|
||||
for (const result of buildResults) {
|
||||
if ('images' in result && result.images) {
|
||||
@@ -659,7 +725,7 @@ function mergeImages(
|
||||
}
|
||||
|
||||
function mergeWildcard(
|
||||
buildResults: Iterable<BuildResult>
|
||||
buildResults: Iterable<BuildResult | BuildOutputConfig>
|
||||
): BuildResultV2Typical['wildcard'] {
|
||||
let wildcard: BuildResultV2Typical['wildcard'] = undefined;
|
||||
for (const result of buildResults) {
|
||||
|
||||
@@ -34,7 +34,7 @@ const help = () => {
|
||||
)} Connect your Vercel Project to your Git repository defined in your local .git config
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} git connect`)}
|
||||
|
||||
|
||||
${chalk.gray(
|
||||
'–'
|
||||
)} Connect your Vercel Project to a Git repository using the remote URL
|
||||
@@ -96,6 +96,7 @@ export default async function main(client: Client) {
|
||||
}
|
||||
|
||||
const { org, project } = linkedProject;
|
||||
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
|
||||
|
||||
switch (subcommand) {
|
||||
case 'connect':
|
||||
|
||||
@@ -42,6 +42,8 @@ class WorkerOutput {
|
||||
}
|
||||
|
||||
print(type, args) {
|
||||
// note: `args` may contain an `Error` that will be toString()'d and thus
|
||||
// no stack trace
|
||||
const str = format(
|
||||
...args.map(s => (typeof s === 'string' ? s : inspect(s)))
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Client from '../client';
|
||||
import { stringify } from 'qs';
|
||||
import { Org } from '../../types';
|
||||
import chalk from 'chalk';
|
||||
import link from '../output/link';
|
||||
@@ -19,9 +18,7 @@ export async function disconnectGitProvider(
|
||||
org: Org,
|
||||
projectId: string
|
||||
) {
|
||||
const fetchUrl = `/v9/projects/${projectId}/link?${stringify({
|
||||
teamId: org.type === 'team' ? org.id : undefined,
|
||||
})}`;
|
||||
const fetchUrl = `/v9/projects/${projectId}/link`;
|
||||
return client.fetch(fetchUrl, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
@@ -37,9 +34,7 @@ export async function connectGitProvider(
|
||||
type: string,
|
||||
repo: string
|
||||
) {
|
||||
const fetchUrl = `/v9/projects/${projectId}/link?${stringify({
|
||||
teamId: org.type === 'team' ? org.id : undefined,
|
||||
})}`;
|
||||
const fetchUrl = `/v9/projects/${projectId}/link`;
|
||||
try {
|
||||
return await client.fetch(fetchUrl, {
|
||||
method: 'POST',
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
README.md
|
||||
yarn.lock
|
||||
@@ -1,16 +1,5 @@
|
||||
import { join } from 'path';
|
||||
import ms from 'ms';
|
||||
import fs, { mkdirp } from 'fs-extra';
|
||||
|
||||
const {
|
||||
exec,
|
||||
fetch,
|
||||
fixture,
|
||||
sleep,
|
||||
testFixture,
|
||||
testFixtureStdio,
|
||||
validateResponseHeaders,
|
||||
} = require('./utils.js');
|
||||
import { isIP } from 'net';
|
||||
const { exec, fixture, testFixture, testFixtureStdio } = require('./utils.js');
|
||||
|
||||
test('[vercel dev] validate redirects', async () => {
|
||||
const directory = fixture('invalid-redirects');
|
||||
@@ -124,260 +113,112 @@ test(
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cleanUrls serve correct content',
|
||||
testFixtureStdio('test-clean-urls', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/about', 'About Page');
|
||||
await testPath(200, '/sub', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/index.html', 'Redirecting to / (308)', {
|
||||
Location: '/',
|
||||
});
|
||||
await testPath(308, '/about.html', 'Redirecting to /about (308)', {
|
||||
Location: '/about',
|
||||
});
|
||||
await testPath(308, '/sub/index.html', 'Redirecting to /sub (308)', {
|
||||
Location: '/sub',
|
||||
'[vercel dev] Use `@vercel/python` with Flask requirements.txt',
|
||||
testFixtureStdio('python-flask', async (testPath: any) => {
|
||||
const name = 'Alice';
|
||||
const year = new Date().getFullYear();
|
||||
await testPath(200, `/api/user?name=${name}`, new RegExp(`Hello ${name}`));
|
||||
await testPath(200, `/api/date`, new RegExp(`Current date is ${year}`));
|
||||
await testPath(200, `/api/date.py`, new RegExp(`Current date is ${year}`));
|
||||
await testPath(200, `/api/headers`, (body: any, res: any) => {
|
||||
// @ts-ignore
|
||||
const { host } = new URL(res.url);
|
||||
expect(body).toBe(host);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Use custom runtime from the "functions" property',
|
||||
testFixtureStdio('custom-runtime', async (testPath: any) => {
|
||||
await testPath(200, `/api/user`, /Hello, from Bash!/m);
|
||||
await testPath(200, `/api/user.sh`, /Hello, from Bash!/m);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Should work with nested `tsconfig.json` files',
|
||||
testFixtureStdio('nested-tsconfig', async (testPath: any) => {
|
||||
await testPath(200, `/`, /Nested tsconfig.json test page/);
|
||||
await testPath(200, `/api`, 'Nested `tsconfig.json` API endpoint');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Should force `tsc` option "module: commonjs" for `startDevServer()`',
|
||||
testFixtureStdio('force-module-commonjs', async (testPath: any) => {
|
||||
await testPath(200, `/`, /Force "module: commonjs" test page/);
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html',
|
||||
'Redirecting to /sub/another (308)',
|
||||
{ Location: '/sub/another' }
|
||||
200,
|
||||
`/api`,
|
||||
'Force "module: commonjs" JavaScript with ES Modules API endpoint'
|
||||
);
|
||||
await testPath(
|
||||
200,
|
||||
`/api/ts`,
|
||||
'Force "module: commonjs" TypeScript API endpoint'
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cleanUrls serve correct content when using `outputDirectory`',
|
||||
testFixtureStdio(
|
||||
'test-clean-urls-with-output-directory',
|
||||
async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/about', 'About Page');
|
||||
await testPath(200, '/sub', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/index.html', 'Redirecting to / (308)', {
|
||||
Location: '/',
|
||||
});
|
||||
await testPath(308, '/about.html', 'Redirecting to /about (308)', {
|
||||
Location: '/about',
|
||||
});
|
||||
await testPath(308, '/sub/index.html', 'Redirecting to /sub (308)', {
|
||||
Location: '/sub',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html',
|
||||
'Redirecting to /sub/another (308)',
|
||||
{ Location: '/sub/another' }
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] should serve custom 404 when `cleanUrls: true`',
|
||||
testFixtureStdio('test-clean-urls-custom-404', async (testPath: any) => {
|
||||
await testPath(200, '/', 'This is the home page');
|
||||
await testPath(200, '/about', 'The about page');
|
||||
await testPath(200, '/contact/me', 'Contact Me Subdirectory');
|
||||
await testPath(404, '/nothing', 'Custom 404 Page');
|
||||
await testPath(404, '/nothing/', 'Custom 404 Page');
|
||||
'[vercel dev] should prioritize index.html over other file named index.*',
|
||||
testFixtureStdio('index-html-priority', async (testPath: any) => {
|
||||
await testPath(200, '/', 'This is index.html');
|
||||
await testPath(200, '/index.css', 'This is index.css');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cleanUrls and trailingSlash serve correct content',
|
||||
testFixtureStdio('test-clean-urls-trailing-slash', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/about/', 'About Page');
|
||||
await testPath(200, '/sub/', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another/', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
//TODO: fix this test so that location is `/` instead of `//`
|
||||
//await testPath(308, '/index.html', 'Redirecting to / (308)', { Location: '/' });
|
||||
await testPath(308, '/about.html', 'Redirecting to /about/ (308)', {
|
||||
Location: '/about/',
|
||||
});
|
||||
await testPath(308, '/sub/index.html', 'Redirecting to /sub/ (308)', {
|
||||
Location: '/sub/',
|
||||
});
|
||||
'[vercel dev] Should support `*.go` API serverless functions',
|
||||
testFixtureStdio('go', async (testPath: any) => {
|
||||
await testPath(200, `/api`, 'This is the index page');
|
||||
await testPath(200, `/api/index`, 'This is the index page');
|
||||
await testPath(200, `/api/index.go`, 'This is the index page');
|
||||
await testPath(200, `/api/another`, 'This is another page');
|
||||
await testPath(200, '/api/another.go', 'This is another page');
|
||||
await testPath(200, `/api/foo`, 'Req Path: /api/foo');
|
||||
await testPath(200, `/api/bar`, 'Req Path: /api/bar');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Should set the `ts-node` "target" to match Node.js version',
|
||||
testFixtureStdio('node-ts-node-target', async (testPath: any) => {
|
||||
await testPath(200, `/api/subclass`, '{"ok":true}');
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html',
|
||||
'Redirecting to /sub/another/ (308)',
|
||||
{
|
||||
Location: '/sub/another/',
|
||||
}
|
||||
200,
|
||||
`/api/array`,
|
||||
'{"months":[1,2,3,4,5,6,7,8,9,10,11,12]}'
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cors headers work with OPTIONS',
|
||||
testFixtureStdio('test-cors-routes', async (testPath: any) => {
|
||||
const headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers':
|
||||
'Content-Type, Authorization, Accept, Content-Length, Origin, User-Agent',
|
||||
'Access-Control-Allow-Methods':
|
||||
'GET, POST, OPTIONS, HEAD, PATCH, PUT, DELETE',
|
||||
};
|
||||
await testPath(200, '/', 'status api', headers, { method: 'GET' });
|
||||
await testPath(200, '/', 'status api', headers, { method: 'POST' });
|
||||
await testPath(200, '/api/status.js', 'status api', headers, {
|
||||
method: 'GET',
|
||||
});
|
||||
await testPath(200, '/api/status.js', 'status api', headers, {
|
||||
method: 'POST',
|
||||
});
|
||||
await testPath(204, '/', '', headers, { method: 'OPTIONS' });
|
||||
await testPath(204, '/api/status.js', '', headers, { method: 'OPTIONS' });
|
||||
})
|
||||
);
|
||||
await testPath(200, `/api/dump`, (body: any, res: any, isDev: any) => {
|
||||
// @ts-ignore
|
||||
const { host } = new URL(res.url);
|
||||
const { env, headers } = JSON.parse(body);
|
||||
|
||||
test(
|
||||
'[vercel dev] test trailingSlash true serve correct content',
|
||||
testFixtureStdio('test-trailing-slash', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/index.html', 'Index Page');
|
||||
await testPath(200, '/about.html', 'About Page');
|
||||
await testPath(200, '/sub/', 'Sub Index Page');
|
||||
await testPath(200, '/sub/index.html', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another.html', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/about.html/', 'Redirecting to /about.html (308)', {
|
||||
Location: '/about.html',
|
||||
});
|
||||
await testPath(308, '/style.css/', 'Redirecting to /style.css (308)', {
|
||||
Location: '/style.css',
|
||||
});
|
||||
await testPath(308, '/sub', 'Redirecting to /sub/ (308)', {
|
||||
Location: '/sub/',
|
||||
});
|
||||
})
|
||||
);
|
||||
// Test that the API endpoint receives the Vercel proxy request headers
|
||||
expect(headers['x-forwarded-host']).toBe(host);
|
||||
expect(headers['x-vercel-deployment-url']).toBe(host);
|
||||
expect(isIP(headers['x-real-ip'])).toBeTruthy();
|
||||
expect(isIP(headers['x-forwarded-for'])).toBeTruthy();
|
||||
expect(isIP(headers['x-vercel-forwarded-for'])).toBeTruthy();
|
||||
|
||||
test(
|
||||
'[vercel dev] should serve custom 404 when `trailingSlash: true`',
|
||||
testFixtureStdio('test-trailing-slash-custom-404', async (testPath: any) => {
|
||||
await testPath(200, '/', 'This is the home page');
|
||||
await testPath(200, '/about.html', 'The about page');
|
||||
await testPath(200, '/contact/', 'Contact Subdirectory');
|
||||
await testPath(404, '/nothing/', 'Custom 404 Page');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test trailingSlash false serve correct content',
|
||||
testFixtureStdio('test-trailing-slash-false', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/index.html', 'Index Page');
|
||||
await testPath(200, '/about.html', 'About Page');
|
||||
await testPath(200, '/sub', 'Sub Index Page');
|
||||
await testPath(200, '/sub/index.html', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another.html', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/about.html/', 'Redirecting to /about.html (308)', {
|
||||
Location: '/about.html',
|
||||
});
|
||||
await testPath(308, '/sub/', 'Redirecting to /sub (308)', {
|
||||
Location: '/sub',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html/',
|
||||
'Redirecting to /sub/another.html (308)',
|
||||
{
|
||||
Location: '/sub/another.html',
|
||||
// Test that the API endpoint has the Vercel platform env vars defined.
|
||||
expect(env.NOW_REGION).toMatch(/^[a-z]{3}\d$/);
|
||||
if (isDev) {
|
||||
// Only dev is tested because in production these are opt-in.
|
||||
expect(env.VERCEL_URL).toBe(host);
|
||||
expect(env.VERCEL_REGION).toBe('dev1');
|
||||
}
|
||||
);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] throw when invalid builder routes detected',
|
||||
testFixtureStdio(
|
||||
'invalid-builder-routes',
|
||||
async (testPath: any) => {
|
||||
await testPath(
|
||||
500,
|
||||
'/',
|
||||
/Route at index 0 has invalid `src` regular expression/m
|
||||
);
|
||||
},
|
||||
{ skipDeploy: true }
|
||||
)
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] support legacy `@now` scope runtimes',
|
||||
testFixtureStdio('legacy-now-runtime', async (testPath: any) => {
|
||||
await testPath(200, '/', /A simple deployment with the Vercel API!/m);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] 00-list-directory',
|
||||
testFixtureStdio(
|
||||
'00-list-directory',
|
||||
async (testPath: any) => {
|
||||
await testPath(200, '/', /Files within/m);
|
||||
await testPath(200, '/', /test[0-3]\.txt/m);
|
||||
await testPath(200, '/', /\.well-known/m);
|
||||
await testPath(200, '/.well-known/keybase.txt', 'proof goes here');
|
||||
},
|
||||
{ projectSettings: { directoryListing: true } }
|
||||
)
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] 01-node',
|
||||
testFixtureStdio('01-node', async (testPath: any) => {
|
||||
await testPath(200, '/', /A simple deployment with the Vercel API!/m);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] add a `api/fn.ts` when `api` does not exist at startup`',
|
||||
testFixtureStdio('no-api', async (_testPath: any, port: any) => {
|
||||
const directory = fixture('no-api');
|
||||
const apiDir = join(directory, 'api');
|
||||
|
||||
try {
|
||||
{
|
||||
const response = await fetch(`http://localhost:${port}/api/new-file`);
|
||||
validateResponseHeaders(response);
|
||||
expect(response.status).toBe(404);
|
||||
}
|
||||
|
||||
const fileContents = `
|
||||
export const config = {
|
||||
runtime: 'edge'
|
||||
}
|
||||
|
||||
export default async function edge(request, event) {
|
||||
return new Response('from new file');
|
||||
}
|
||||
`;
|
||||
|
||||
await mkdirp(apiDir);
|
||||
await fs.writeFile(join(apiDir, 'new-file.js'), fileContents);
|
||||
|
||||
// Wait until file events have been processed
|
||||
await sleep(ms('1s'));
|
||||
|
||||
{
|
||||
const response = await fetch(`http://localhost:${port}/api/new-file`);
|
||||
validateResponseHeaders(response);
|
||||
const body = await response.text();
|
||||
expect(body.trim()).toBe('from new file');
|
||||
}
|
||||
} finally {
|
||||
await fs.remove(apiDir);
|
||||
}
|
||||
'[vercel dev] Do not fail if `src` is missing',
|
||||
testFixtureStdio('missing-src-property', async (testPath: any) => {
|
||||
await testPath(200, '/', /hello:index.txt/m);
|
||||
await testPath(404, '/i-do-not-exist');
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,196 +1,15 @@
|
||||
import ms from 'ms';
|
||||
import fs from 'fs-extra';
|
||||
import { isIP } from 'net';
|
||||
import { join } from 'path';
|
||||
import { Response } from 'node-fetch';
|
||||
|
||||
const {
|
||||
fetch,
|
||||
sleep,
|
||||
fixture,
|
||||
testFixture,
|
||||
testFixtureStdio,
|
||||
validateResponseHeaders,
|
||||
} = require('./utils.js');
|
||||
|
||||
test(
|
||||
'[vercel dev] temporary directory listing',
|
||||
testFixtureStdio(
|
||||
'temporary-directory-listing',
|
||||
async (_testPath: any, port: any) => {
|
||||
const directory = fixture('temporary-directory-listing');
|
||||
await fs.unlink(join(directory, 'index.txt')).catch(() => null);
|
||||
|
||||
await sleep(ms('20s'));
|
||||
|
||||
const firstResponse = await fetch(`http://localhost:${port}`);
|
||||
validateResponseHeaders(firstResponse);
|
||||
const body = await firstResponse.text();
|
||||
console.log(body);
|
||||
expect(firstResponse.status).toBe(404);
|
||||
|
||||
await fs.writeFile(join(directory, 'index.txt'), 'hello');
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
validateResponseHeaders(response);
|
||||
|
||||
if (response.status === 200) {
|
||||
const body = await response.text();
|
||||
expect(body).toBe('hello');
|
||||
}
|
||||
|
||||
await sleep(ms('1s'));
|
||||
}
|
||||
},
|
||||
{ skipDeploy: true }
|
||||
)
|
||||
);
|
||||
|
||||
test('[vercel dev] add a `package.json` to trigger `@vercel/static-build`', async () => {
|
||||
const directory = fixture('trigger-static-build');
|
||||
|
||||
await fs.unlink(join(directory, 'package.json')).catch(() => null);
|
||||
|
||||
await fs.unlink(join(directory, 'public', 'index.txt')).catch(() => null);
|
||||
|
||||
await fs.rmdir(join(directory, 'public')).catch(() => null);
|
||||
|
||||
const tester = testFixtureStdio(
|
||||
'trigger-static-build',
|
||||
async (_testPath: any, port: any) => {
|
||||
{
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
validateResponseHeaders(response);
|
||||
const body = await response.text();
|
||||
expect(body.trim()).toBe('hello:index.txt');
|
||||
}
|
||||
|
||||
const rnd = Math.random().toString();
|
||||
const pkg = {
|
||||
private: true,
|
||||
scripts: { build: `mkdir -p public && echo ${rnd} > public/index.txt` },
|
||||
};
|
||||
|
||||
await fs.writeFile(join(directory, 'package.json'), JSON.stringify(pkg));
|
||||
|
||||
// Wait until file events have been processed
|
||||
await sleep(ms('2s'));
|
||||
|
||||
{
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
validateResponseHeaders(response);
|
||||
const body = await response.text();
|
||||
expect(body.trim()).toBe(rnd);
|
||||
}
|
||||
},
|
||||
{ skipDeploy: true }
|
||||
);
|
||||
|
||||
await tester();
|
||||
});
|
||||
|
||||
test('[vercel dev] no build matches warning', async () => {
|
||||
const directory = fixture('no-build-matches');
|
||||
const { dev } = await testFixture(directory, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
try {
|
||||
// start `vercel dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
dev.stderr.setEncoding('utf8');
|
||||
await new Promise<void>(resolve => {
|
||||
dev.stderr.on('data', (str: string) => {
|
||||
if (str.includes('did not match any source files')) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
await dev.kill();
|
||||
}
|
||||
});
|
||||
|
||||
test(
|
||||
'[vercel dev] do not recursivly check the path',
|
||||
testFixtureStdio('handle-filesystem-missing', async (testPath: any) => {
|
||||
await testPath(200, '/', /hello/m);
|
||||
await testPath(404, '/favicon.txt');
|
||||
})
|
||||
);
|
||||
|
||||
test('[vercel dev] render warning for empty cwd dir', async () => {
|
||||
const directory = fixture('empty');
|
||||
const { dev, port } = await testFixture(directory, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
try {
|
||||
dev.unref();
|
||||
|
||||
// Monitor `stderr` for the warning
|
||||
dev.stderr.setEncoding('utf8');
|
||||
const msg = 'There are no files inside your deployment.';
|
||||
await new Promise<void>(resolve => {
|
||||
dev.stderr.on('data', (str: string) => {
|
||||
if (str.includes(msg)) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Issue a request to ensure a 404 response
|
||||
await sleep(ms('3s'));
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
validateResponseHeaders(response);
|
||||
expect(response.status).toBe(404);
|
||||
} finally {
|
||||
await dev.kill();
|
||||
}
|
||||
});
|
||||
|
||||
test('[vercel dev] do not rebuild for changes in the output directory', async () => {
|
||||
const directory = fixture('output-is-source');
|
||||
|
||||
const { dev, port } = await testFixture(directory, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
try {
|
||||
dev.unref();
|
||||
|
||||
let stderr: any = [];
|
||||
const start = Date.now();
|
||||
|
||||
dev.stderr.on('data', (str: any) => stderr.push(str));
|
||||
|
||||
while (stderr.join('').includes('Ready') === false) {
|
||||
await sleep(ms('3s'));
|
||||
|
||||
if (Date.now() - start > ms('30s')) {
|
||||
console.log('stderr:', stderr.join(''));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const resp1 = await fetch(`http://localhost:${port}`);
|
||||
const text1 = await resp1.text();
|
||||
expect(text1.trim()).toBe('hello first');
|
||||
|
||||
await fs.writeFile(join(directory, 'public', 'index.html'), 'hello second');
|
||||
|
||||
await sleep(ms('3s'));
|
||||
|
||||
const resp2 = await fetch(`http://localhost:${port}`);
|
||||
const text2 = await resp2.text();
|
||||
expect(text2.trim()).toBe('hello second');
|
||||
} finally {
|
||||
await dev.kill();
|
||||
}
|
||||
});
|
||||
|
||||
test(
|
||||
'[vercel dev] 25-nextjs-src-dir',
|
||||
testFixtureStdio('25-nextjs-src-dir', async (testPath: any) => {
|
||||
@@ -324,117 +143,6 @@ test(
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Use `@vercel/python` with Flask requirements.txt',
|
||||
testFixtureStdio('python-flask', async (testPath: any) => {
|
||||
const name = 'Alice';
|
||||
const year = new Date().getFullYear();
|
||||
await testPath(200, `/api/user?name=${name}`, new RegExp(`Hello ${name}`));
|
||||
await testPath(200, `/api/date`, new RegExp(`Current date is ${year}`));
|
||||
await testPath(200, `/api/date.py`, new RegExp(`Current date is ${year}`));
|
||||
await testPath(200, `/api/headers`, (body: any, res: any) => {
|
||||
// @ts-ignore
|
||||
const { host } = new URL(res.url);
|
||||
expect(body).toBe(host);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Use custom runtime from the "functions" property',
|
||||
testFixtureStdio('custom-runtime', async (testPath: any) => {
|
||||
await testPath(200, `/api/user`, /Hello, from Bash!/m);
|
||||
await testPath(200, `/api/user.sh`, /Hello, from Bash!/m);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Should work with nested `tsconfig.json` files',
|
||||
testFixtureStdio('nested-tsconfig', async (testPath: any) => {
|
||||
await testPath(200, `/`, /Nested tsconfig.json test page/);
|
||||
await testPath(200, `/api`, 'Nested `tsconfig.json` API endpoint');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Should force `tsc` option "module: commonjs" for `startDevServer()`',
|
||||
testFixtureStdio('force-module-commonjs', async (testPath: any) => {
|
||||
await testPath(200, `/`, /Force "module: commonjs" test page/);
|
||||
await testPath(
|
||||
200,
|
||||
`/api`,
|
||||
'Force "module: commonjs" JavaScript with ES Modules API endpoint'
|
||||
);
|
||||
await testPath(
|
||||
200,
|
||||
`/api/ts`,
|
||||
'Force "module: commonjs" TypeScript API endpoint'
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] should prioritize index.html over other file named index.*',
|
||||
testFixtureStdio('index-html-priority', async (testPath: any) => {
|
||||
await testPath(200, '/', 'This is index.html');
|
||||
await testPath(200, '/index.css', 'This is index.css');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Should support `*.go` API serverless functions',
|
||||
testFixtureStdio('go', async (testPath: any) => {
|
||||
await testPath(200, `/api`, 'This is the index page');
|
||||
await testPath(200, `/api/index`, 'This is the index page');
|
||||
await testPath(200, `/api/index.go`, 'This is the index page');
|
||||
await testPath(200, `/api/another`, 'This is another page');
|
||||
await testPath(200, '/api/another.go', 'This is another page');
|
||||
await testPath(200, `/api/foo`, 'Req Path: /api/foo');
|
||||
await testPath(200, `/api/bar`, 'Req Path: /api/bar');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Should set the `ts-node` "target" to match Node.js version',
|
||||
testFixtureStdio('node-ts-node-target', async (testPath: any) => {
|
||||
await testPath(200, `/api/subclass`, '{"ok":true}');
|
||||
await testPath(
|
||||
200,
|
||||
`/api/array`,
|
||||
'{"months":[1,2,3,4,5,6,7,8,9,10,11,12]}'
|
||||
);
|
||||
|
||||
await testPath(200, `/api/dump`, (body: any, res: any, isDev: any) => {
|
||||
// @ts-ignore
|
||||
const { host } = new URL(res.url);
|
||||
const { env, headers } = JSON.parse(body);
|
||||
|
||||
// Test that the API endpoint receives the Vercel proxy request headers
|
||||
expect(headers['x-forwarded-host']).toBe(host);
|
||||
expect(headers['x-vercel-deployment-url']).toBe(host);
|
||||
expect(isIP(headers['x-real-ip'])).toBeTruthy();
|
||||
expect(isIP(headers['x-forwarded-for'])).toBeTruthy();
|
||||
expect(isIP(headers['x-vercel-forwarded-for'])).toBeTruthy();
|
||||
|
||||
// Test that the API endpoint has the Vercel platform env vars defined.
|
||||
expect(env.NOW_REGION).toMatch(/^[a-z]{3}\d$/);
|
||||
if (isDev) {
|
||||
// Only dev is tested because in production these are opt-in.
|
||||
expect(env.VERCEL_URL).toBe(host);
|
||||
expect(env.VERCEL_REGION).toBe('dev1');
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Do not fail if `src` is missing',
|
||||
testFixtureStdio('missing-src-property', async (testPath: any) => {
|
||||
await testPath(200, '/', /hello:index.txt/m);
|
||||
await testPath(404, '/i-do-not-exist');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Middleware that returns a 200 response',
|
||||
testFixtureStdio('middleware-response', async (testPath: any) => {
|
||||
|
||||
449
packages/cli/test/dev/integration-5.test.ts
Normal file
449
packages/cli/test/dev/integration-5.test.ts
Normal file
@@ -0,0 +1,449 @@
|
||||
import { join } from 'path';
|
||||
import ms from 'ms';
|
||||
import fs, { mkdirp } from 'fs-extra';
|
||||
|
||||
const {
|
||||
fetch,
|
||||
fixture,
|
||||
sleep,
|
||||
testFixture,
|
||||
testFixtureStdio,
|
||||
validateResponseHeaders,
|
||||
} = require('./utils.js');
|
||||
|
||||
test(
|
||||
'[vercel dev] temporary directory listing',
|
||||
testFixtureStdio(
|
||||
'temporary-directory-listing',
|
||||
async (_testPath: any, port: any) => {
|
||||
const directory = fixture('temporary-directory-listing');
|
||||
await fs.unlink(join(directory, 'index.txt')).catch(() => null);
|
||||
|
||||
await sleep(ms('20s'));
|
||||
|
||||
const firstResponse = await fetch(`http://localhost:${port}`);
|
||||
validateResponseHeaders(firstResponse);
|
||||
const body = await firstResponse.text();
|
||||
console.log(body);
|
||||
expect(firstResponse.status).toBe(404);
|
||||
|
||||
await fs.writeFile(join(directory, 'index.txt'), 'hello');
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
validateResponseHeaders(response);
|
||||
|
||||
if (response.status === 200) {
|
||||
const body = await response.text();
|
||||
expect(body).toBe('hello');
|
||||
}
|
||||
|
||||
await sleep(ms('1s'));
|
||||
}
|
||||
},
|
||||
{ skipDeploy: true }
|
||||
)
|
||||
);
|
||||
|
||||
test('[vercel dev] add a `package.json` to trigger `@vercel/static-build`', async () => {
|
||||
const directory = fixture('trigger-static-build');
|
||||
|
||||
await fs.unlink(join(directory, 'package.json')).catch(() => null);
|
||||
|
||||
await fs.unlink(join(directory, 'public', 'index.txt')).catch(() => null);
|
||||
|
||||
await fs.rmdir(join(directory, 'public')).catch(() => null);
|
||||
|
||||
const tester = testFixtureStdio(
|
||||
'trigger-static-build',
|
||||
async (_testPath: any, port: any) => {
|
||||
{
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
validateResponseHeaders(response);
|
||||
const body = await response.text();
|
||||
expect(body.trim()).toBe('hello:index.txt');
|
||||
}
|
||||
|
||||
const rnd = Math.random().toString();
|
||||
const pkg = {
|
||||
private: true,
|
||||
scripts: { build: `mkdir -p public && echo ${rnd} > public/index.txt` },
|
||||
};
|
||||
|
||||
await fs.writeFile(join(directory, 'package.json'), JSON.stringify(pkg));
|
||||
|
||||
// Wait until file events have been processed
|
||||
await sleep(ms('2s'));
|
||||
|
||||
{
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
validateResponseHeaders(response);
|
||||
const body = await response.text();
|
||||
expect(body.trim()).toBe(rnd);
|
||||
}
|
||||
},
|
||||
{ skipDeploy: true }
|
||||
);
|
||||
|
||||
await tester();
|
||||
});
|
||||
|
||||
test('[vercel dev] no build matches warning', async () => {
|
||||
const directory = fixture('no-build-matches');
|
||||
const { dev } = await testFixture(directory, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
try {
|
||||
// start `vercel dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
dev.stderr.setEncoding('utf8');
|
||||
await new Promise<void>(resolve => {
|
||||
dev.stderr.on('data', (str: string) => {
|
||||
if (str.includes('did not match any source files')) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
await dev.kill();
|
||||
}
|
||||
});
|
||||
|
||||
test(
|
||||
'[vercel dev] do not recursivly check the path',
|
||||
testFixtureStdio('handle-filesystem-missing', async (testPath: any) => {
|
||||
await testPath(200, '/', /hello/m);
|
||||
await testPath(404, '/favicon.txt');
|
||||
})
|
||||
);
|
||||
|
||||
test('[vercel dev] render warning for empty cwd dir', async () => {
|
||||
const directory = fixture('empty');
|
||||
const { dev, port } = await testFixture(directory, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
try {
|
||||
dev.unref();
|
||||
|
||||
// Monitor `stderr` for the warning
|
||||
dev.stderr.setEncoding('utf8');
|
||||
const msg = 'There are no files inside your deployment.';
|
||||
await new Promise<void>(resolve => {
|
||||
dev.stderr.on('data', (str: string) => {
|
||||
if (str.includes(msg)) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Issue a request to ensure a 404 response
|
||||
await sleep(ms('3s'));
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
validateResponseHeaders(response);
|
||||
expect(response.status).toBe(404);
|
||||
} finally {
|
||||
await dev.kill();
|
||||
}
|
||||
});
|
||||
|
||||
test('[vercel dev] do not rebuild for changes in the output directory', async () => {
|
||||
const directory = fixture('output-is-source');
|
||||
|
||||
const { dev, port } = await testFixture(directory, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
try {
|
||||
dev.unref();
|
||||
|
||||
let stderr: any = [];
|
||||
const start = Date.now();
|
||||
|
||||
dev.stderr.on('data', (str: any) => stderr.push(str));
|
||||
|
||||
while (stderr.join('').includes('Ready') === false) {
|
||||
await sleep(ms('3s'));
|
||||
|
||||
if (Date.now() - start > ms('30s')) {
|
||||
console.log('stderr:', stderr.join(''));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const resp1 = await fetch(`http://localhost:${port}`);
|
||||
const text1 = await resp1.text();
|
||||
expect(text1.trim()).toBe('hello first');
|
||||
|
||||
await fs.writeFile(join(directory, 'public', 'index.html'), 'hello second');
|
||||
|
||||
await sleep(ms('3s'));
|
||||
|
||||
const resp2 = await fetch(`http://localhost:${port}`);
|
||||
const text2 = await resp2.text();
|
||||
expect(text2.trim()).toBe('hello second');
|
||||
} finally {
|
||||
await dev.kill();
|
||||
}
|
||||
});
|
||||
|
||||
test(
|
||||
'[vercel dev] test cleanUrls serve correct content',
|
||||
testFixtureStdio('test-clean-urls', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/about', 'About Page');
|
||||
await testPath(200, '/sub', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/index.html', 'Redirecting to / (308)', {
|
||||
Location: '/',
|
||||
});
|
||||
await testPath(308, '/about.html', 'Redirecting to /about (308)', {
|
||||
Location: '/about',
|
||||
});
|
||||
await testPath(308, '/sub/index.html', 'Redirecting to /sub (308)', {
|
||||
Location: '/sub',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html',
|
||||
'Redirecting to /sub/another (308)',
|
||||
{ Location: '/sub/another' }
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cleanUrls serve correct content when using `outputDirectory`',
|
||||
testFixtureStdio(
|
||||
'test-clean-urls-with-output-directory',
|
||||
async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/about', 'About Page');
|
||||
await testPath(200, '/sub', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/index.html', 'Redirecting to / (308)', {
|
||||
Location: '/',
|
||||
});
|
||||
await testPath(308, '/about.html', 'Redirecting to /about (308)', {
|
||||
Location: '/about',
|
||||
});
|
||||
await testPath(308, '/sub/index.html', 'Redirecting to /sub (308)', {
|
||||
Location: '/sub',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html',
|
||||
'Redirecting to /sub/another (308)',
|
||||
{ Location: '/sub/another' }
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] should serve custom 404 when `cleanUrls: true`',
|
||||
testFixtureStdio('test-clean-urls-custom-404', async (testPath: any) => {
|
||||
await testPath(200, '/', 'This is the home page');
|
||||
await testPath(200, '/about', 'The about page');
|
||||
await testPath(200, '/contact/me', 'Contact Me Subdirectory');
|
||||
await testPath(404, '/nothing', 'Custom 404 Page');
|
||||
await testPath(404, '/nothing/', 'Custom 404 Page');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cleanUrls and trailingSlash serve correct content',
|
||||
testFixtureStdio('test-clean-urls-trailing-slash', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/about/', 'About Page');
|
||||
await testPath(200, '/sub/', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another/', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
//TODO: fix this test so that location is `/` instead of `//`
|
||||
//await testPath(308, '/index.html', 'Redirecting to / (308)', { Location: '/' });
|
||||
await testPath(308, '/about.html', 'Redirecting to /about/ (308)', {
|
||||
Location: '/about/',
|
||||
});
|
||||
await testPath(308, '/sub/index.html', 'Redirecting to /sub/ (308)', {
|
||||
Location: '/sub/',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html',
|
||||
'Redirecting to /sub/another/ (308)',
|
||||
{
|
||||
Location: '/sub/another/',
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cors headers work with OPTIONS',
|
||||
testFixtureStdio('test-cors-routes', async (testPath: any) => {
|
||||
const headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers':
|
||||
'Content-Type, Authorization, Accept, Content-Length, Origin, User-Agent',
|
||||
'Access-Control-Allow-Methods':
|
||||
'GET, POST, OPTIONS, HEAD, PATCH, PUT, DELETE',
|
||||
};
|
||||
await testPath(200, '/', 'status api', headers, { method: 'GET' });
|
||||
await testPath(200, '/', 'status api', headers, { method: 'POST' });
|
||||
await testPath(200, '/api/status.js', 'status api', headers, {
|
||||
method: 'GET',
|
||||
});
|
||||
await testPath(200, '/api/status.js', 'status api', headers, {
|
||||
method: 'POST',
|
||||
});
|
||||
await testPath(204, '/', '', headers, { method: 'OPTIONS' });
|
||||
await testPath(204, '/api/status.js', '', headers, { method: 'OPTIONS' });
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test trailingSlash true serve correct content',
|
||||
testFixtureStdio('test-trailing-slash', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/index.html', 'Index Page');
|
||||
await testPath(200, '/about.html', 'About Page');
|
||||
await testPath(200, '/sub/', 'Sub Index Page');
|
||||
await testPath(200, '/sub/index.html', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another.html', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/about.html/', 'Redirecting to /about.html (308)', {
|
||||
Location: '/about.html',
|
||||
});
|
||||
await testPath(308, '/style.css/', 'Redirecting to /style.css (308)', {
|
||||
Location: '/style.css',
|
||||
});
|
||||
await testPath(308, '/sub', 'Redirecting to /sub/ (308)', {
|
||||
Location: '/sub/',
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] should serve custom 404 when `trailingSlash: true`',
|
||||
testFixtureStdio('test-trailing-slash-custom-404', async (testPath: any) => {
|
||||
await testPath(200, '/', 'This is the home page');
|
||||
await testPath(200, '/about.html', 'The about page');
|
||||
await testPath(200, '/contact/', 'Contact Subdirectory');
|
||||
await testPath(404, '/nothing/', 'Custom 404 Page');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test trailingSlash false serve correct content',
|
||||
testFixtureStdio('test-trailing-slash-false', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/index.html', 'Index Page');
|
||||
await testPath(200, '/about.html', 'About Page');
|
||||
await testPath(200, '/sub', 'Sub Index Page');
|
||||
await testPath(200, '/sub/index.html', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another.html', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/about.html/', 'Redirecting to /about.html (308)', {
|
||||
Location: '/about.html',
|
||||
});
|
||||
await testPath(308, '/sub/', 'Redirecting to /sub (308)', {
|
||||
Location: '/sub',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html/',
|
||||
'Redirecting to /sub/another.html (308)',
|
||||
{
|
||||
Location: '/sub/another.html',
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] throw when invalid builder routes detected',
|
||||
testFixtureStdio(
|
||||
'invalid-builder-routes',
|
||||
async (testPath: any) => {
|
||||
await testPath(
|
||||
500,
|
||||
'/',
|
||||
/Route at index 0 has invalid `src` regular expression/m
|
||||
);
|
||||
},
|
||||
{ skipDeploy: true }
|
||||
)
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] support legacy `@now` scope runtimes',
|
||||
testFixtureStdio('legacy-now-runtime', async (testPath: any) => {
|
||||
await testPath(200, '/', /A simple deployment with the Vercel API!/m);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] 00-list-directory',
|
||||
testFixtureStdio(
|
||||
'00-list-directory',
|
||||
async (testPath: any) => {
|
||||
await testPath(200, '/', /Files within/m);
|
||||
await testPath(200, '/', /test[0-3]\.txt/m);
|
||||
await testPath(200, '/', /\.well-known/m);
|
||||
await testPath(200, '/.well-known/keybase.txt', 'proof goes here');
|
||||
},
|
||||
{ projectSettings: { directoryListing: true } }
|
||||
)
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] 01-node',
|
||||
testFixtureStdio('01-node', async (testPath: any) => {
|
||||
await testPath(200, '/', /A simple deployment with the Vercel API!/m);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] add a `api/fn.ts` when `api` does not exist at startup`',
|
||||
testFixtureStdio('no-api', async (_testPath: any, port: any) => {
|
||||
const directory = fixture('no-api');
|
||||
const apiDir = join(directory, 'api');
|
||||
|
||||
try {
|
||||
{
|
||||
const response = await fetch(`http://localhost:${port}/api/new-file`);
|
||||
validateResponseHeaders(response);
|
||||
expect(response.status).toBe(404);
|
||||
}
|
||||
|
||||
const fileContents = `
|
||||
export const config = {
|
||||
runtime: 'edge'
|
||||
}
|
||||
|
||||
export default async function edge(request, event) {
|
||||
return new Response('from new file');
|
||||
}
|
||||
`;
|
||||
|
||||
await mkdirp(apiDir);
|
||||
await fs.writeFile(join(apiDir, 'new-file.js'), fileContents);
|
||||
|
||||
// Wait until file events have been processed
|
||||
await sleep(ms('1s'));
|
||||
|
||||
{
|
||||
const response = await fetch(`http://localhost:${port}/api/new-file`);
|
||||
validateResponseHeaders(response);
|
||||
const body = await response.text();
|
||||
expect(body.trim()).toBe('from new file');
|
||||
}
|
||||
} finally {
|
||||
await fs.remove(apiDir);
|
||||
}
|
||||
})
|
||||
);
|
||||
4
packages/cli/test/fixtures/unit/commands/build/minimal-nextjs/node_modules/next/package.json
generated
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/build/minimal-nextjs/node_modules/next/package.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "next",
|
||||
"version": "13.0.4"
|
||||
}
|
||||
5
packages/cli/test/fixtures/unit/commands/build/minimal-nextjs/package.json
vendored
Normal file
5
packages/cli/test/fixtures/unit/commands/build/minimal-nextjs/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"next": "13.0.4"
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@ import ms from 'ms';
|
||||
import fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { getWriteableDirectory } from '@vercel/build-utils';
|
||||
import build from '../../../src/commands/build';
|
||||
import { client } from '../../mocks/client';
|
||||
import { defaultProject, useProject } from '../../mocks/project';
|
||||
import { useTeams } from '../../mocks/team';
|
||||
import { useUser } from '../../mocks/user';
|
||||
import { setupFixture } from '../../helpers/setup-fixture';
|
||||
import build from '../../../../src/commands/build';
|
||||
import { client } from '../../../mocks/client';
|
||||
import { defaultProject, useProject } from '../../../mocks/project';
|
||||
import { useTeams } from '../../../mocks/team';
|
||||
import { useUser } from '../../../mocks/user';
|
||||
import { setupFixture } from '../../../helpers/setup-fixture';
|
||||
import JSON5 from 'json5';
|
||||
// TODO (@Ethan-Arrowood) - After shipping support for turbo and nx, revisit rush support
|
||||
// import execa from 'execa';
|
||||
@@ -15,7 +15,7 @@ import JSON5 from 'json5';
|
||||
jest.setTimeout(ms('1 minute'));
|
||||
|
||||
const fixture = (name: string) =>
|
||||
join(__dirname, '../../fixtures/unit/commands/build', name);
|
||||
join(__dirname, '../../../fixtures/unit/commands/build', name);
|
||||
|
||||
describe('build', () => {
|
||||
const originalCwd = process.cwd();
|
||||
@@ -6,6 +6,8 @@ import { join } from 'path';
|
||||
|
||||
tmp.setGracefulCleanup();
|
||||
|
||||
jest.setTimeout(25000);
|
||||
|
||||
const cacheDir = tmp.tmpNameSync({
|
||||
prefix: 'test-vercel-cli-get-latest-version-',
|
||||
});
|
||||
@@ -131,8 +133,9 @@ describe('get latest version', () => {
|
||||
});
|
||||
|
||||
async function waitForCacheFile() {
|
||||
for (let i = 0; i < 40; i++) {
|
||||
await sleep(100);
|
||||
const seconds = 20;
|
||||
for (let i = 0; i < seconds * 4; i++) {
|
||||
await sleep(250);
|
||||
if (await fs.pathExists(cacheFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ Firstly, install the package:
|
||||
|
||||
```bash
|
||||
npm install @vercel/client
|
||||
# or
|
||||
yarn add @vercel/client
|
||||
```
|
||||
|
||||
Next, load it:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/client",
|
||||
"version": "12.2.26",
|
||||
"version": "12.3.2",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://vercel.com",
|
||||
@@ -15,15 +15,15 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test-integration-once": "yarn test tests/create-deployment.test.ts tests/create-legacy-deployment.test.ts tests/paths.test.ts",
|
||||
"test-integration-once": "pnpm test tests/create-deployment.test.ts tests/create-legacy-deployment.test.ts tests/paths.test.ts",
|
||||
"test": "jest --env node --verbose --runInBand --bail",
|
||||
"test-unit": "yarn test tests/unit.*test.*"
|
||||
"test-unit": "pnpm test tests/unit.*test.*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/async-retry": "1.4.1",
|
||||
"@types/async-retry": "1.4.5",
|
||||
"@types/fs-extra": "7.0.0",
|
||||
"@types/jest": "27.4.1",
|
||||
"@types/minimatch": "3.0.5",
|
||||
@@ -43,8 +43,8 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "5.7.5",
|
||||
"@vercel/routing-utils": "2.1.3",
|
||||
"@vercel/build-utils": "5.9.0",
|
||||
"@vercel/routing-utils": "2.1.8",
|
||||
"@zeit/fetch": "5.2.0",
|
||||
"async-retry": "1.2.3",
|
||||
"async-sema": "3.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import sleep from 'sleep-promise';
|
||||
import ms from 'ms';
|
||||
import { fetch, getApiDeploymentsUrl } from './utils';
|
||||
import { getPollingDelay } from './utils/get-polling-delay';
|
||||
import {
|
||||
isDone,
|
||||
isReady,
|
||||
@@ -47,6 +47,7 @@ export async function* checkDeploymentStatus(
|
||||
// Build polling
|
||||
debug('Waiting for builds and the deployment to complete...');
|
||||
const finishedEvents = new Set();
|
||||
const startTime = Date.now();
|
||||
|
||||
while (true) {
|
||||
// Deployment polling
|
||||
@@ -155,6 +156,8 @@ export async function* checkDeploymentStatus(
|
||||
};
|
||||
}
|
||||
|
||||
await sleep(ms('1.5s'));
|
||||
const elapsed = Date.now() - startTime;
|
||||
const duration = getPollingDelay(elapsed);
|
||||
await sleep(duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function buildCreateDeployment() {
|
||||
debug(`Provided 'path' is a single file`);
|
||||
}
|
||||
|
||||
let { fileList } = await buildFileTree(path, clientOptions, debug);
|
||||
const { fileList } = await buildFileTree(path, clientOptions, debug);
|
||||
|
||||
// This is a useful warning because it prevents people
|
||||
// from getting confused about a deployment that renders 404.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DeploymentFile } from './utils/hashes';
|
||||
import { FilesMap } from './utils/hashes';
|
||||
import { generateQueryString } from './utils/query-string';
|
||||
import { isReady, isAliasAssigned } from './utils/ready-state';
|
||||
import { checkDeploymentStatus } from './check-deployment-status';
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from './types';
|
||||
|
||||
async function* postDeployment(
|
||||
files: Map<string, DeploymentFile>,
|
||||
files: FilesMap,
|
||||
clientOptions: VercelClientOptions,
|
||||
deploymentOptions: DeploymentOptions
|
||||
): AsyncIterableIterator<{
|
||||
@@ -90,7 +90,7 @@ async function* postDeployment(
|
||||
}
|
||||
|
||||
function getDefaultName(
|
||||
files: Map<string, DeploymentFile>,
|
||||
files: FilesMap,
|
||||
clientOptions: VercelClientOptions
|
||||
): string {
|
||||
const debug = createDebug(clientOptions.debug);
|
||||
@@ -109,7 +109,7 @@ function getDefaultName(
|
||||
}
|
||||
|
||||
export async function* deploy(
|
||||
files: Map<string, DeploymentFile>,
|
||||
files: FilesMap,
|
||||
clientOptions: VercelClientOptions,
|
||||
deploymentOptions: DeploymentOptions
|
||||
): AsyncIterableIterator<{ type: string; payload: any }> {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { EventEmitter } from 'events';
|
||||
import retry from 'async-retry';
|
||||
import { Sema } from 'async-sema';
|
||||
|
||||
import { DeploymentFile } from './utils/hashes';
|
||||
import { DeploymentFile, FilesMap } from './utils/hashes';
|
||||
import { fetch, API_FILES, createDebug } from './utils';
|
||||
import { DeploymentError } from './errors';
|
||||
import { deploy } from './deploy';
|
||||
@@ -29,7 +29,7 @@ const isClientNetworkError = (err: Error) => {
|
||||
};
|
||||
|
||||
export async function* upload(
|
||||
files: Map<string, DeploymentFile>,
|
||||
files: FilesMap,
|
||||
clientOptions: VercelClientOptions,
|
||||
deploymentOptions: DeploymentOptions
|
||||
): AsyncIterableIterator<any> {
|
||||
@@ -98,6 +98,10 @@ export async function* upload(
|
||||
await semaphore.acquire();
|
||||
|
||||
const { data } = file;
|
||||
if (typeof data === 'undefined') {
|
||||
// Directories don't need to be uploaded
|
||||
return;
|
||||
}
|
||||
|
||||
uploadProgress.bytesUploaded = 0;
|
||||
|
||||
|
||||
14
packages/client/src/utils/get-polling-delay.ts
Normal file
14
packages/client/src/utils/get-polling-delay.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import ms from 'ms';
|
||||
|
||||
export function getPollingDelay(elapsed: number): number {
|
||||
if (elapsed <= ms('15s')) {
|
||||
return ms('1s');
|
||||
}
|
||||
if (elapsed <= ms('1m')) {
|
||||
return ms('5s');
|
||||
}
|
||||
if (elapsed <= ms('5m')) {
|
||||
return ms('15s');
|
||||
}
|
||||
return ms('30s');
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import { Sema } from 'async-sema';
|
||||
|
||||
export interface DeploymentFile {
|
||||
names: string[];
|
||||
data: Buffer;
|
||||
data?: Buffer;
|
||||
mode: number;
|
||||
}
|
||||
|
||||
export type FilesMap = Map<string | undefined, DeploymentFile>;
|
||||
|
||||
/**
|
||||
* Computes a hash for the given buf.
|
||||
*
|
||||
@@ -23,14 +25,12 @@ export function hash(buf: Buffer): string {
|
||||
* @param map with hashed files
|
||||
* @return {object}
|
||||
*/
|
||||
export const mapToObject = (
|
||||
map: Map<string, DeploymentFile>
|
||||
): { [key: string]: DeploymentFile } => {
|
||||
export const mapToObject = (map: FilesMap): Record<string, DeploymentFile> => {
|
||||
const obj: { [key: string]: DeploymentFile } = {};
|
||||
for (const [key, value] of map) {
|
||||
if (typeof key === 'undefined') continue;
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
@@ -43,8 +43,8 @@ export const mapToObject = (
|
||||
*/
|
||||
export async function hashes(
|
||||
files: string[],
|
||||
map = new Map<string, DeploymentFile>()
|
||||
): Promise<Map<string, DeploymentFile>> {
|
||||
map = new Map<string | undefined, DeploymentFile>()
|
||||
): Promise<FilesMap> {
|
||||
const semaphore = new Sema(100);
|
||||
|
||||
await Promise.all(
|
||||
@@ -54,15 +54,21 @@ export async function hashes(
|
||||
const stat = await fs.lstat(name);
|
||||
const mode = stat.mode;
|
||||
|
||||
let data: Buffer | null = null;
|
||||
if (stat.isSymbolicLink()) {
|
||||
const link = await fs.readlink(name);
|
||||
data = Buffer.from(link, 'utf8');
|
||||
} else {
|
||||
data = await fs.readFile(name);
|
||||
let data: Buffer | undefined;
|
||||
const isDirectory = stat.isDirectory();
|
||||
|
||||
let h: string | undefined;
|
||||
|
||||
if (!isDirectory) {
|
||||
if (stat.isSymbolicLink()) {
|
||||
const link = await fs.readlink(name);
|
||||
data = Buffer.from(link, 'utf8');
|
||||
} else {
|
||||
data = await fs.readFile(name);
|
||||
}
|
||||
h = hash(data);
|
||||
}
|
||||
|
||||
const h = hash(data);
|
||||
const entry = map.get(h);
|
||||
|
||||
if (entry) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DeploymentFile } from './hashes';
|
||||
import { FilesMap } from './hashes';
|
||||
import { FetchOptions } from '@zeit/fetch';
|
||||
import { nodeFetch, zeitFetch } from './fetch';
|
||||
import { join, sep, relative } from 'path';
|
||||
@@ -256,15 +256,15 @@ export const fetch = async (
|
||||
|
||||
export interface PreparedFile {
|
||||
file: string;
|
||||
sha: string;
|
||||
size: number;
|
||||
sha?: string;
|
||||
size?: number;
|
||||
mode: number;
|
||||
}
|
||||
|
||||
const isWin = process.platform.includes('win');
|
||||
|
||||
export const prepareFiles = (
|
||||
files: Map<string, DeploymentFile>,
|
||||
files: FilesMap,
|
||||
clientOptions: VercelClientOptions
|
||||
): PreparedFile[] => {
|
||||
const preparedFiles: PreparedFile[] = [];
|
||||
@@ -286,9 +286,9 @@ export const prepareFiles = (
|
||||
|
||||
preparedFiles.push({
|
||||
file: isWin ? fileName.replace(/\\/g, '/') : fileName,
|
||||
size: file.data.byteLength || file.data.length,
|
||||
size: file.data?.byteLength || file.data?.length,
|
||||
mode: file.mode,
|
||||
sha,
|
||||
sha: sha || undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,10 @@ export default function readdir(
|
||||
if (stats.isDirectory()) {
|
||||
readdir(filePath, ignores)
|
||||
.then(function (res) {
|
||||
if (res.length === 0) {
|
||||
// Empty directories get returned
|
||||
list.push(filePath);
|
||||
}
|
||||
list = list.concat(res);
|
||||
pending -= 1;
|
||||
if (!pending) {
|
||||
|
||||
41
packages/client/tests/unit.get-polling-delay.test.ts
Normal file
41
packages/client/tests/unit.get-polling-delay.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { getPollingDelay } from '../src/utils/get-polling-delay';
|
||||
|
||||
describe('getPollingDelay()', () => {
|
||||
it('should return 1 second', async () => {
|
||||
expect(getPollingDelay(0)).toBe(1000);
|
||||
expect(getPollingDelay(1000)).toBe(1000);
|
||||
expect(getPollingDelay(3000)).toBe(1000);
|
||||
expect(getPollingDelay(5000)).toBe(1000);
|
||||
expect(getPollingDelay(8000)).toBe(1000);
|
||||
expect(getPollingDelay(9000)).toBe(1000);
|
||||
expect(getPollingDelay(10000)).toBe(1000);
|
||||
expect(getPollingDelay(13000)).toBe(1000);
|
||||
expect(getPollingDelay(15000)).toBe(1000);
|
||||
});
|
||||
|
||||
it('should return 5 second', async () => {
|
||||
expect(getPollingDelay(15001)).toBe(5000);
|
||||
expect(getPollingDelay(16000)).toBe(5000);
|
||||
expect(getPollingDelay(23000)).toBe(5000);
|
||||
expect(getPollingDelay(36000)).toBe(5000);
|
||||
expect(getPollingDelay(59000)).toBe(5000);
|
||||
expect(getPollingDelay(60000)).toBe(5000);
|
||||
});
|
||||
|
||||
it('should return 15 second', async () => {
|
||||
expect(getPollingDelay(60001)).toBe(15000);
|
||||
expect(getPollingDelay(80000)).toBe(15000);
|
||||
expect(getPollingDelay(100000)).toBe(15000);
|
||||
expect(getPollingDelay(200000)).toBe(15000);
|
||||
expect(getPollingDelay(250000)).toBe(15000);
|
||||
expect(getPollingDelay(300000)).toBe(15000);
|
||||
});
|
||||
|
||||
it('should return 30 second', async () => {
|
||||
expect(getPollingDelay(300001)).toBe(30000);
|
||||
expect(getPollingDelay(400000)).toBe(30000);
|
||||
expect(getPollingDelay(1400000)).toBe(30000);
|
||||
expect(getPollingDelay(9400000)).toBe(30000);
|
||||
expect(getPollingDelay(99400000)).toBe(30000);
|
||||
});
|
||||
});
|
||||
@@ -24,7 +24,11 @@ describe('buildFileTree()', () => {
|
||||
noop
|
||||
);
|
||||
|
||||
const expectedFileList = toAbsolutePaths(cwd, ['.nowignore', 'index.txt']);
|
||||
const expectedFileList = toAbsolutePaths(cwd, [
|
||||
'.nowignore',
|
||||
'folder',
|
||||
'index.txt',
|
||||
]);
|
||||
expect(normalizeWindowsPaths(expectedFileList).sort()).toEqual(
|
||||
normalizeWindowsPaths(fileList).sort()
|
||||
);
|
||||
@@ -41,9 +45,14 @@ describe('buildFileTree()', () => {
|
||||
|
||||
it('should include symlinked files and directories', async () => {
|
||||
const cwd = fixture('symlinks');
|
||||
|
||||
// Also add an empty directory to make sure it's included
|
||||
await fs.mkdirp(join(cwd, 'empty'));
|
||||
|
||||
const { fileList } = await buildFileTree(cwd, { isDirectory: true }, noop);
|
||||
|
||||
const expectedFileList = toAbsolutePaths(cwd, [
|
||||
'empty',
|
||||
'folder-link',
|
||||
'folder/text.txt',
|
||||
'index.txt',
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"strict": true,
|
||||
"target": "ES2020"
|
||||
"target": "ES2020",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ City of the original client IP as calculated by Vercel Proxy.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:4](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L4)
|
||||
[packages/edge/src/edge-headers.ts:4](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L4)
|
||||
|
||||
---
|
||||
|
||||
@@ -48,7 +48,7 @@ Country of the original client IP as calculated by Vercel Proxy.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:8](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L8)
|
||||
[packages/edge/src/edge-headers.ts:8](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L8)
|
||||
|
||||
---
|
||||
|
||||
@@ -60,7 +60,7 @@ Client IP as calcualted by Vercel Proxy.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:12](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L12)
|
||||
[packages/edge/src/edge-headers.ts:12](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L12)
|
||||
|
||||
---
|
||||
|
||||
@@ -72,7 +72,7 @@ Latitude of the original client IP as calculated by Vercel Proxy.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:16](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L16)
|
||||
[packages/edge/src/edge-headers.ts:16](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L16)
|
||||
|
||||
---
|
||||
|
||||
@@ -84,7 +84,7 @@ Longitude of the original client IP as calculated by Vercel Proxy.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:20](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L20)
|
||||
[packages/edge/src/edge-headers.ts:20](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L20)
|
||||
|
||||
---
|
||||
|
||||
@@ -98,7 +98,7 @@ See [docs](https://vercel.com/docs/concepts/edge-network/headers#x-vercel-ip-cou
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:26](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L26)
|
||||
[packages/edge/src/edge-headers.ts:26](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L26)
|
||||
|
||||
---
|
||||
|
||||
@@ -110,7 +110,7 @@ The request ID for each request generated by Vercel Proxy.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:30](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L30)
|
||||
[packages/edge/src/edge-headers.ts:30](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L30)
|
||||
|
||||
## Functions
|
||||
|
||||
@@ -140,7 +140,7 @@ Returns the location information for the incoming request.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:106](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L106)
|
||||
[packages/edge/src/edge-headers.ts:106](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L106)
|
||||
|
||||
---
|
||||
|
||||
@@ -166,7 +166,7 @@ Returns the IP address of the request from the headers.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:77](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L77)
|
||||
[packages/edge/src/edge-headers.ts:77](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L77)
|
||||
|
||||
---
|
||||
|
||||
@@ -209,7 +209,7 @@ const response = json(
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/response.ts:19](https://github.com/vercel/vercel/blob/main/packages/edge/src/response.ts#L19)
|
||||
[packages/edge/src/response.ts:19](https://github.com/vercel/vercel/blob/main/packages/edge/src/response.ts#L19)
|
||||
|
||||
---
|
||||
|
||||
@@ -257,7 +257,7 @@ export default function middleware(_req: Request) {
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/middleware-helpers.ts:145](https://github.com/vercel/vercel/blob/main/packages/edge/src/middleware-helpers.ts#L145)
|
||||
[packages/edge/src/middleware-helpers.ts:145](https://github.com/vercel/vercel/blob/main/packages/edge/src/middleware-helpers.ts#L145)
|
||||
|
||||
---
|
||||
|
||||
@@ -319,4 +319,4 @@ export const config = { matcher: '/api/users/:path*' };
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/middleware-helpers.ts:101](https://github.com/vercel/vercel/blob/main/packages/edge/src/middleware-helpers.ts#L101)
|
||||
[packages/edge/src/middleware-helpers.ts:101](https://github.com/vercel/vercel/blob/main/packages/edge/src/middleware-helpers.ts#L101)
|
||||
|
||||
@@ -26,7 +26,7 @@ along with the response headers from the origin.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/middleware-helpers.ts:31](https://github.com/vercel/vercel/blob/main/packages/edge/src/middleware-helpers.ts#L31)
|
||||
[packages/edge/src/middleware-helpers.ts:31](https://github.com/vercel/vercel/blob/main/packages/edge/src/middleware-helpers.ts#L31)
|
||||
|
||||
---
|
||||
|
||||
@@ -38,7 +38,7 @@ Fields to rewrite for the upstream request.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/middleware-helpers.ts:35](https://github.com/vercel/vercel/blob/main/packages/edge/src/middleware-helpers.ts#L35)
|
||||
[packages/edge/src/middleware-helpers.ts:35](https://github.com/vercel/vercel/blob/main/packages/edge/src/middleware-helpers.ts#L35)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ The city that the request originated from.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:47](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L47)
|
||||
[packages/edge/src/edge-headers.ts:47](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L47)
|
||||
|
||||
---
|
||||
|
||||
@@ -35,7 +35,7 @@ The country that the request originated from.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:50](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L50)
|
||||
[packages/edge/src/edge-headers.ts:50](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L50)
|
||||
|
||||
---
|
||||
|
||||
@@ -48,7 +48,7 @@ See [docs](https://vercel.com/docs/concepts/edge-network/headers#x-vercel-ip-cou
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:58](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L58)
|
||||
[packages/edge/src/edge-headers.ts:58](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L58)
|
||||
|
||||
---
|
||||
|
||||
@@ -60,7 +60,7 @@ The latitude of the client.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:61](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L61)
|
||||
[packages/edge/src/edge-headers.ts:61](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L61)
|
||||
|
||||
---
|
||||
|
||||
@@ -72,7 +72,7 @@ The longitude of the client.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:64](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L64)
|
||||
[packages/edge/src/edge-headers.ts:64](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L64)
|
||||
|
||||
---
|
||||
|
||||
@@ -84,4 +84,4 @@ The [Vercel Edge Network region](https://vercel.com/docs/concepts/edge-network/r
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/edge-headers.ts:53](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L53)
|
||||
[packages/edge/src/edge-headers.ts:53](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L53)
|
||||
|
||||
@@ -35,4 +35,4 @@ export default async function middleware(request: Request): Promise<Response> {
|
||||
|
||||
#### Defined in
|
||||
|
||||
[src/middleware-helpers.ts:23](https://github.com/vercel/vercel/blob/main/packages/edge/src/middleware-helpers.ts#L23)
|
||||
[packages/edge/src/middleware-helpers.ts:23](https://github.com/vercel/vercel/blob/main/packages/edge/src/middleware-helpers.ts#L23)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/edge",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.6",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
@@ -8,8 +8,8 @@
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts --dts --format esm,cjs",
|
||||
"test": "jest --env node --verbose --runInBand --bail",
|
||||
"test-unit": "yarn test",
|
||||
"build:docs": "typedoc && prettier --write docs/**/*.md docs/*.md"
|
||||
"test-unit": "pnpm test",
|
||||
"build:docs": "typedoc && node scripts/fix-links.js && prettier --write docs/**/*.md docs/*.md"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edge-runtime/jest-environment": "2.0.0",
|
||||
|
||||
24
packages/edge/scripts/fix-links.js
Normal file
24
packages/edge/scripts/fix-links.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// this step is necessary until https://github.com/TypeStrong/typedoc/issues/2140 is fixed
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const docs = path.join(__dirname, '..', 'docs');
|
||||
const interfaces = path.join(docs, 'interfaces');
|
||||
|
||||
for (const dir of [docs, interfaces]) {
|
||||
for (const entity of fs.readdirSync(dir)) {
|
||||
try {
|
||||
const entityPath = path.join(dir, entity);
|
||||
const stat = fs.statSync(entityPath);
|
||||
|
||||
if (stat.isFile()) {
|
||||
const contents = fs.readFileSync(entityPath, 'utf-8');
|
||||
const pattern = /node_modules\/\.pnpm\/typescript@\d*\.\d*\.\d*\//gi;
|
||||
fs.writeFileSync(entityPath, contents.replace(pattern, ''));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error fixing links in docs', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
packages/edge/test/docs.test.ts
vendored
7
packages/edge/test/docs.test.ts
vendored
@@ -9,7 +9,7 @@ const test = process.platform === 'win32' ? it.skip : it;
|
||||
|
||||
test('docs are up to date', async () => {
|
||||
const cwd = path.resolve(__dirname, '../');
|
||||
await execAsync(`yarn build:docs`, { cwd });
|
||||
await execAsync(`pnpm build:docs`, { cwd });
|
||||
const result = await execAsync(`git status --short docs`, {
|
||||
cwd,
|
||||
encoding: 'utf-8',
|
||||
@@ -27,10 +27,7 @@ test('docs are up to date', async () => {
|
||||
if (lines !== '') {
|
||||
const diff = await execAsync(`git diff docs`, { cwd, encoding: 'utf8' });
|
||||
throw new Error(
|
||||
'Docs are not up to date. Please re-run `yarn build:docs` to re-generate them.\nChanges:\n' +
|
||||
lines +
|
||||
'\n\n' +
|
||||
diff.stdout
|
||||
`Docs are not up to date. Please re-run \`pnpm build:docs\` to re-generate them.\nChanges:\n${lines}\n\n${diff.stdout}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/error-utils",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.8",
|
||||
"description": "A collection of error utilities for vercel/vercel",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -12,7 +12,7 @@
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest --coverage --env node --verbose",
|
||||
"test-unit": "yarn test"
|
||||
"test-unit": "pnpm test"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/frameworks",
|
||||
"version": "1.1.18",
|
||||
"version": "1.2.4",
|
||||
"main": "./dist/frameworks.js",
|
||||
"types": "./dist/frameworks.d.ts",
|
||||
"files": [
|
||||
@@ -10,7 +10,7 @@
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest --env node --verbose --runInBand --bail",
|
||||
"test-unit": "yarn test"
|
||||
"test-unit": "pnpm test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iarna/toml": "2.2.3",
|
||||
@@ -21,7 +21,7 @@
|
||||
"@types/js-yaml": "3.12.1",
|
||||
"@types/node": "14.18.33",
|
||||
"@types/node-fetch": "2.5.8",
|
||||
"@vercel/routing-utils": "2.1.3",
|
||||
"@vercel/routing-utils": "2.1.8",
|
||||
"ajv": "6.12.2",
|
||||
"typescript": "4.3.4"
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ export const frameworks = [
|
||||
useRuntime: { src: 'package.json', use: '@vercel/next' },
|
||||
detectors: {
|
||||
some: [
|
||||
// Intentionally does not detect a package name
|
||||
// https://github.com/vercel/vercel/pull/8432
|
||||
{
|
||||
path: 'blitz.config.js',
|
||||
},
|
||||
@@ -74,9 +76,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"next":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'next',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -119,9 +119,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"gatsby":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'gatsby',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -205,6 +203,8 @@ export const frameworks = [
|
||||
ignoreRuntimes: ['@vercel/node'],
|
||||
detectors: {
|
||||
every: [
|
||||
// Intentionally does not detect a package name
|
||||
// https://github.com/vercel/vercel/pull/7761
|
||||
{
|
||||
path: 'remix.config.js',
|
||||
},
|
||||
@@ -244,9 +244,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"astro":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'astro',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -288,9 +286,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"hexo":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'hexo',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -325,9 +321,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@11ty\\/eleventy":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@11ty/eleventy',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -364,9 +358,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
some: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@docusaurus\\/core":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@docusaurus/core',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -452,9 +444,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
some: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"docusaurus":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'docusaurus',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -502,10 +492,10 @@ export const frameworks = [
|
||||
website: 'https://preactjs.com',
|
||||
detectors: {
|
||||
every: [
|
||||
// Intentionally does not detect "preact" package because that can be
|
||||
// used to power other frameworks.
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"preact-cli":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'preact-cli',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -549,14 +539,10 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"solid-js":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'solid-js',
|
||||
},
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"solid-start":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'solid-start',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -589,9 +575,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
some: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@dojo\\/framework":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@dojo/framework',
|
||||
},
|
||||
{
|
||||
path: '.dojorc',
|
||||
@@ -649,11 +633,12 @@ export const frameworks = [
|
||||
description: 'An Ember app, created with the Ember CLI.',
|
||||
website: 'https://emberjs.com/',
|
||||
detectors: {
|
||||
every: [
|
||||
some: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"ember-cli":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'ember-source',
|
||||
},
|
||||
{
|
||||
matchPackage: 'ember-cli',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -698,9 +683,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@vue\\/cli-service":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@vue/cli-service',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -753,9 +736,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@scullyio\\/init":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@scullyio/init',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -790,9 +771,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@ionic\\/angular":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@ionic/angular',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -835,9 +814,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@angular\\/cli":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@angular/cli',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -895,9 +872,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"polymer-cli":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'polymer-cli',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -953,14 +928,10 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"svelte":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'svelte',
|
||||
},
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"sirv-cli":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'sirv-cli',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -992,6 +963,7 @@ export const frameworks = [
|
||||
],
|
||||
},
|
||||
{
|
||||
// TODO: fix detected as "sveltekit-1"
|
||||
name: 'SvelteKit (Legacy Beta)',
|
||||
slug: 'sveltekit',
|
||||
demo: 'https://sveltekit-template.vercel.app',
|
||||
@@ -1081,9 +1053,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@ionic\\/react":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@ionic/react',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1143,14 +1113,10 @@ export const frameworks = [
|
||||
detectors: {
|
||||
some: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"react-scripts":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'react-scripts',
|
||||
},
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"react-dev-utils":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'react-dev-utils',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1209,9 +1175,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"gridsome":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'gridsome',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1246,9 +1210,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"umi":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'umi',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1292,9 +1254,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"sapper":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'sapper',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1329,9 +1289,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"saber":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'saber',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1380,9 +1338,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@stencil\\/core":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@stencil/core',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1443,11 +1399,15 @@ export const frameworks = [
|
||||
sort: 2,
|
||||
envPrefix: 'NUXT_ENV_',
|
||||
detectors: {
|
||||
every: [
|
||||
some: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"nuxt3?(-edge)?":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'nuxt',
|
||||
},
|
||||
{
|
||||
matchPackage: 'nuxt3',
|
||||
},
|
||||
{
|
||||
matchPackage: 'nuxt-edge',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1503,9 +1463,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@redwoodjs\\/core":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@redwoodjs/core',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1630,7 +1588,10 @@ export const frameworks = [
|
||||
description: 'A Brunch app, created with the Brunch CLI.',
|
||||
website: 'https://brunch.io/',
|
||||
detectors: {
|
||||
every: [
|
||||
some: [
|
||||
{
|
||||
matchPackage: 'brunch',
|
||||
},
|
||||
{
|
||||
path: 'brunch-config.js',
|
||||
},
|
||||
@@ -1723,6 +1684,47 @@ export const frameworks = [
|
||||
getOutputDirName: async () => 'public',
|
||||
defaultVersion: '0.13.0', // Must match the build image
|
||||
},
|
||||
{
|
||||
name: 'Hydrogen',
|
||||
slug: 'hydrogen',
|
||||
demo: 'https://hydrogen-template.vercel.app',
|
||||
logo: 'https://api-frameworks.vercel.sh/framework-logos/hydrogen.svg',
|
||||
tagline: 'React framework for headless commerce',
|
||||
description: 'React framework for headless commerce',
|
||||
website: 'https://hydrogen.shopify.dev',
|
||||
useRuntime: { src: 'package.json', use: '@vercel/hydrogen' },
|
||||
detectors: {
|
||||
some: [
|
||||
{
|
||||
matchPackage: '@shopify/hydrogen',
|
||||
},
|
||||
{
|
||||
path: 'hydrogen.config.js',
|
||||
},
|
||||
{
|
||||
path: 'hydrogen.config.ts',
|
||||
},
|
||||
],
|
||||
},
|
||||
settings: {
|
||||
installCommand: {
|
||||
placeholder: '`yarn install`, `pnpm install`, or `npm install`',
|
||||
},
|
||||
buildCommand: {
|
||||
value: 'shopify hydrogen build',
|
||||
placeholder: '`npm run build` or `shopify hydrogen build`',
|
||||
},
|
||||
devCommand: {
|
||||
value: 'shopify hydrogen dev',
|
||||
placeholder: 'shopify hydrogen dev',
|
||||
},
|
||||
outputDirectory: {
|
||||
value: 'dist',
|
||||
},
|
||||
},
|
||||
dependency: '@shopify/hydrogen',
|
||||
getOutputDirName: async () => 'dist',
|
||||
},
|
||||
{
|
||||
name: 'Vite',
|
||||
slug: 'vite',
|
||||
@@ -1736,9 +1738,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"vite":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'vite',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1772,9 +1772,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"vitepress":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'vitepress',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1806,9 +1804,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*vuepress:\\s*".+?"[^}]*}',
|
||||
matchPackage: 'vuepress',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1841,9 +1837,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"parcel":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'parcel',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1931,44 +1925,6 @@ export const frameworks = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Hydrogen',
|
||||
slug: 'hydrogen',
|
||||
demo: 'https://hydrogen-template.vercel.app',
|
||||
logo: 'https://api-frameworks.vercel.sh/framework-logos/hydrogen.svg',
|
||||
tagline: 'React framework for headless commerce',
|
||||
description: 'React framework for headless commerce',
|
||||
website: 'https://hydrogen.shopify.dev',
|
||||
useRuntime: { src: 'package.json', use: '@vercel/hydrogen' },
|
||||
detectors: {
|
||||
some: [
|
||||
{
|
||||
path: 'hydrogen.config.js',
|
||||
},
|
||||
{
|
||||
path: 'hydrogen.config.ts',
|
||||
},
|
||||
],
|
||||
},
|
||||
settings: {
|
||||
installCommand: {
|
||||
placeholder: '`yarn install`, `pnpm install`, or `npm install`',
|
||||
},
|
||||
buildCommand: {
|
||||
value: 'shopify hydrogen build',
|
||||
placeholder: '`npm run build` or `shopify hydrogen build`',
|
||||
},
|
||||
devCommand: {
|
||||
value: 'shopify hydrogen dev',
|
||||
placeholder: 'shopify hydrogen dev',
|
||||
},
|
||||
outputDirectory: {
|
||||
value: 'dist',
|
||||
},
|
||||
},
|
||||
dependency: '@shopify/hydrogen',
|
||||
getOutputDirName: async () => 'dist',
|
||||
},
|
||||
{
|
||||
name: 'Other',
|
||||
slug: null,
|
||||
|
||||
@@ -2,15 +2,24 @@ import { Rewrite, Route } from '@vercel/routing-utils';
|
||||
|
||||
export interface FrameworkDetectionItem {
|
||||
/**
|
||||
* A file path
|
||||
* @example "package.json"
|
||||
* A file path to detect.
|
||||
* If specified, "matchPackage" cannot be specified.
|
||||
* @example "some-framework.config.json"
|
||||
*/
|
||||
path: string;
|
||||
path?: string;
|
||||
/**
|
||||
* A matcher
|
||||
* A matcher for the entire file.
|
||||
* If specified, "matchPackage" cannot be specified.
|
||||
* @example "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"next\":\\s*\".+?\"[^}]*}"
|
||||
*/
|
||||
matchContent?: string;
|
||||
/**
|
||||
* A matcher for a package specifically found in a "package.json" file.
|
||||
* If specified, "path" and "matchContext" cannot be specified.
|
||||
* If specified in multiple detectors, the first one will be used to resolve the framework version.
|
||||
* @example "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"next\":\\s*\".+?\"[^}]*}"
|
||||
*/
|
||||
matchPackage?: string;
|
||||
}
|
||||
|
||||
export interface SettingPlaceholder {
|
||||
|
||||
@@ -17,7 +17,7 @@ const SchemaFrameworkDetectionItem = {
|
||||
items: [
|
||||
{
|
||||
type: 'object',
|
||||
required: ['path'],
|
||||
required: [],
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
path: {
|
||||
@@ -26,6 +26,9 @@ const SchemaFrameworkDetectionItem = {
|
||||
matchContent: {
|
||||
type: 'string',
|
||||
},
|
||||
matchPackage: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/fs-detectors",
|
||||
"version": "3.6.2",
|
||||
"version": "3.7.5",
|
||||
"description": "Vercel filesystem detectors",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -16,12 +16,12 @@
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest --env node --verbose --runInBand --bail test/unit.*test.*",
|
||||
"test-unit": "yarn test"
|
||||
"test-unit": "pnpm test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/error-utils": "1.0.3",
|
||||
"@vercel/frameworks": "1.1.18",
|
||||
"@vercel/routing-utils": "2.1.3",
|
||||
"@vercel/error-utils": "1.0.8",
|
||||
"@vercel/frameworks": "1.2.4",
|
||||
"@vercel/routing-utils": "2.1.8",
|
||||
"glob": "8.0.3",
|
||||
"js-yaml": "4.1.0",
|
||||
"json5": "2.2.2",
|
||||
@@ -35,7 +35,7 @@
|
||||
"@types/minimatch": "3.0.5",
|
||||
"@types/node": "14.18.33",
|
||||
"@types/semver": "7.3.10",
|
||||
"@vercel/build-utils": "5.7.5",
|
||||
"@vercel/build-utils": "5.9.0",
|
||||
"typescript": "4.3.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,9 +453,7 @@ function getApiMatches() {
|
||||
|
||||
return [
|
||||
{ src: 'middleware.[jt]s', use: `@vercel/node`, config },
|
||||
{ src: 'api/**/*.js', use: `@vercel/node`, config },
|
||||
{ src: 'api/**/*.mjs', use: `@vercel/node`, config },
|
||||
{ src: 'api/**/*.ts', use: `@vercel/node`, config },
|
||||
{ src: 'api/**/*.+(js|mjs|ts|tsx)', use: `@vercel/node`, config },
|
||||
{ src: 'api/**/!(*_test).go', use: `@vercel/go`, config },
|
||||
{ src: 'api/**/*.py', use: `@vercel/python`, config },
|
||||
{ src: 'api/**/*.rb', use: `@vercel/ruby`, config },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Framework, FrameworkDetectionItem } from '@vercel/frameworks';
|
||||
import { spawnSync } from 'child_process';
|
||||
import { DetectorFilesystem } from './detectors/filesystem';
|
||||
|
||||
interface BaseFramework {
|
||||
@@ -11,49 +12,96 @@ export interface DetectFrameworkOptions {
|
||||
frameworkList: readonly BaseFramework[];
|
||||
}
|
||||
|
||||
async function matches(fs: DetectorFilesystem, framework: BaseFramework) {
|
||||
export interface DetectFrameworkRecordOptions {
|
||||
fs: DetectorFilesystem;
|
||||
frameworkList: readonly Framework[];
|
||||
}
|
||||
|
||||
type MatchResult = {
|
||||
framework: BaseFramework;
|
||||
detectedVersion?: string;
|
||||
};
|
||||
|
||||
async function matches(
|
||||
fs: DetectorFilesystem,
|
||||
framework: BaseFramework
|
||||
): Promise<MatchResult | undefined> {
|
||||
const { detectors } = framework;
|
||||
|
||||
if (!detectors) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
const { every, some } = detectors;
|
||||
|
||||
if (every !== undefined && !Array.isArray(every)) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (some !== undefined && !Array.isArray(some)) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
const check = async ({ path, matchContent }: FrameworkDetectionItem) => {
|
||||
const check = async ({
|
||||
path,
|
||||
matchContent,
|
||||
matchPackage,
|
||||
}: FrameworkDetectionItem): Promise<MatchResult | undefined> => {
|
||||
if (matchPackage && matchContent) {
|
||||
throw new Error(
|
||||
`Cannot specify "matchPackage" and "matchContent" in the same detector for "${framework.slug}"`
|
||||
);
|
||||
}
|
||||
if (matchPackage && path) {
|
||||
throw new Error(
|
||||
`Cannot specify "matchPackage" and "path" in the same detector for "${framework.slug}" because "path" is assumed to be "package.json".`
|
||||
);
|
||||
}
|
||||
|
||||
if (!path && !matchPackage) {
|
||||
throw new Error(
|
||||
`Must specify either "path" or "matchPackage" in detector for "${framework.slug}".`
|
||||
);
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
return false;
|
||||
path = 'package.json';
|
||||
}
|
||||
|
||||
if (matchPackage) {
|
||||
matchContent = `"(dev)?(d|D)ependencies":\\s*{[^}]*"${matchPackage}":\\s*"(.+?)"[^}]*}`;
|
||||
}
|
||||
|
||||
if ((await fs.hasPath(path)) === false) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchContent) {
|
||||
if ((await fs.isFile(path)) === false) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
const regex = new RegExp(matchContent, 'gm');
|
||||
const regex = new RegExp(matchContent, 'm');
|
||||
const content = await fs.readFile(path);
|
||||
|
||||
if (!regex.test(content.toString())) {
|
||||
return false;
|
||||
const match = content.toString().match(regex);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
if (matchPackage && match[3]) {
|
||||
return {
|
||||
framework,
|
||||
detectedVersion: match[3],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return {
|
||||
framework,
|
||||
};
|
||||
};
|
||||
|
||||
const result: boolean[] = [];
|
||||
const result: (MatchResult | undefined)[] = [];
|
||||
|
||||
if (every) {
|
||||
const everyResult = await Promise.all(every.map(item => check(item)));
|
||||
@@ -61,11 +109,12 @@ async function matches(fs: DetectorFilesystem, framework: BaseFramework) {
|
||||
}
|
||||
|
||||
if (some) {
|
||||
let someResult = false;
|
||||
let someResult: MatchResult | undefined;
|
||||
|
||||
for (const item of some) {
|
||||
if (await check(item)) {
|
||||
someResult = true;
|
||||
const itemResult = await check(item);
|
||||
if (itemResult) {
|
||||
someResult = itemResult;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -73,9 +122,20 @@ async function matches(fs: DetectorFilesystem, framework: BaseFramework) {
|
||||
result.push(someResult);
|
||||
}
|
||||
|
||||
return result.every(res => res === true);
|
||||
if (!result.every(res => !!res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const detectedVersion = result.find(
|
||||
r => typeof r === 'object' && r.detectedVersion
|
||||
)?.detectedVersion;
|
||||
return {
|
||||
framework,
|
||||
detectedVersion,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Deprecate and replace with `detectFrameworkRecord`
|
||||
export async function detectFramework({
|
||||
fs,
|
||||
frameworkList,
|
||||
@@ -90,3 +150,70 @@ export async function detectFramework({
|
||||
);
|
||||
return result.find(res => res !== null) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Framework with a `detectedVersion` specifying the version
|
||||
* or version range of the relevant package
|
||||
*/
|
||||
type VersionedFramework = Framework & {
|
||||
detectedVersion?: string;
|
||||
};
|
||||
|
||||
// Note: Does not currently support a `frameworkList` of monorepo managers
|
||||
export async function detectFrameworkRecord({
|
||||
fs,
|
||||
frameworkList,
|
||||
}: DetectFrameworkRecordOptions): Promise<VersionedFramework | null> {
|
||||
const result = await Promise.all(
|
||||
frameworkList.map(async frameworkMatch => {
|
||||
const matchResult = await matches(fs, frameworkMatch);
|
||||
if (matchResult) {
|
||||
return {
|
||||
...frameworkMatch,
|
||||
detectedVersion: matchResult?.detectedVersion,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
})
|
||||
);
|
||||
const frameworkRecord = result.find(res => res !== null) ?? null;
|
||||
|
||||
return frameworkRecord;
|
||||
}
|
||||
|
||||
export function detectFrameworkVersion(
|
||||
frameworkRecord: Framework
|
||||
): string | undefined {
|
||||
const allDetectors = [
|
||||
...(frameworkRecord.detectors?.every || []),
|
||||
...(frameworkRecord.detectors?.some || []),
|
||||
];
|
||||
const firstMatchPackage = allDetectors.find(d => d.matchPackage);
|
||||
|
||||
if (!firstMatchPackage?.matchPackage) {
|
||||
return;
|
||||
}
|
||||
|
||||
return lookupInstalledVersion(
|
||||
process.execPath,
|
||||
firstMatchPackage.matchPackage
|
||||
);
|
||||
}
|
||||
|
||||
function lookupInstalledVersion(
|
||||
cwd: string,
|
||||
packageName: string
|
||||
): string | undefined {
|
||||
try {
|
||||
const script = `require('${packageName}/package.json').version`;
|
||||
return spawnSync(cwd, ['-p', script], {
|
||||
encoding: 'utf-8',
|
||||
}).stdout.trim();
|
||||
} catch (error) {
|
||||
console.debug(
|
||||
`Error looking up version of installed package "${packageName}": ${error}`
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,11 @@ export {
|
||||
detectApiExtensions,
|
||||
} from './detect-builders';
|
||||
export { detectFileSystemAPI } from './detect-file-system-api';
|
||||
export { detectFramework } from './detect-framework';
|
||||
export {
|
||||
detectFramework,
|
||||
detectFrameworkRecord,
|
||||
detectFrameworkVersion,
|
||||
} from './detect-framework';
|
||||
export { getProjectPaths } from './get-project-paths';
|
||||
export { DetectorFilesystem } from './detectors/filesystem';
|
||||
export { LocalFileSystemDetector } from './detectors/local-file-system-detector';
|
||||
|
||||
@@ -66,7 +66,7 @@ export async function getMonorepoDefaultSettings(
|
||||
|
||||
return {
|
||||
monorepoManager: 'turbo',
|
||||
buildCommand: `cd ${relativeToRoot} && npx turbo run build --filter=${projectName}...`,
|
||||
buildCommand: `cd ${relativeToRoot} && npx turbo run build --filter={${projectPath}}...`,
|
||||
installCommand: `cd ${relativeToRoot} && ${packageManager} install`,
|
||||
commandForIgnoringBuildStep: `cd ${relativeToRoot} && npx turbo-ignore`,
|
||||
};
|
||||
|
||||
@@ -1369,6 +1369,25 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('api detect node tsx files', async () => {
|
||||
const files = [
|
||||
'api/index.tsx',
|
||||
'api/users.tsx',
|
||||
'api/config/staging.tsx',
|
||||
'api/config/production.tsx',
|
||||
'api/src/controllers/health.tsx',
|
||||
'api/src/controllers/user.module.tsx',
|
||||
];
|
||||
|
||||
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders?.length).toBe(6);
|
||||
expect(builders!.every(b => b.src!.endsWith('.tsx'))).toBe(true);
|
||||
expect(errorRoutes?.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('just public', async () => {
|
||||
const files = ['public/index.html', 'public/favicon.ico', 'README.md'];
|
||||
|
||||
|
||||
35
packages/fs-detectors/test/unit.examples.test.ts
vendored
Normal file
35
packages/fs-detectors/test/unit.examples.test.ts
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
import frameworkList from '@vercel/frameworks';
|
||||
import { detectFramework } from '../src';
|
||||
import { FixtureFilesystem } from './utils/fixture-filesystem';
|
||||
import { readdirSync, lstatSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
function getExamples() {
|
||||
const root = join(__dirname, '..', '..', '..');
|
||||
const examplesPath = join(root, 'examples');
|
||||
const examples = readdirSync(examplesPath);
|
||||
|
||||
const exampleDirs = examples.filter(example => {
|
||||
const examplePath = join(examplesPath, example);
|
||||
const stat = lstatSync(examplePath);
|
||||
return stat.isDirectory();
|
||||
});
|
||||
|
||||
return exampleDirs.map(exampleDirName => {
|
||||
return [exampleDirName, join(examplesPath, exampleDirName)];
|
||||
});
|
||||
}
|
||||
|
||||
describe('examples should be detected', () => {
|
||||
const examples = getExamples();
|
||||
|
||||
it.each(examples)('%s', async (example, examplePath) => {
|
||||
const fs = new FixtureFilesystem(examplePath);
|
||||
const framework = await detectFramework({ fs, frameworkList });
|
||||
if (!framework) {
|
||||
throw new Error(`Framework not detected for example "${example}".`);
|
||||
}
|
||||
|
||||
expect(framework).toBe(example);
|
||||
});
|
||||
});
|
||||
194
packages/fs-detectors/test/unit.framework-detector-record.test.ts
vendored
Normal file
194
packages/fs-detectors/test/unit.framework-detector-record.test.ts
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
import frameworkList from '@vercel/frameworks';
|
||||
import { detectFrameworkRecord } from '../src';
|
||||
import VirtualFilesystem from './virtual-file-system';
|
||||
|
||||
describe('detectFrameworkRecord', () => {
|
||||
it('Do not detect anything', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'README.md': '# hi',
|
||||
'api/cheese.js': 'export default (req, res) => res.end("cheese");',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe(undefined);
|
||||
});
|
||||
|
||||
it('Detects a framework record with a matchPackage detector', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
next: '9.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const frameworkRecord = await detectFrameworkRecord({ fs, frameworkList });
|
||||
if (!frameworkRecord) {
|
||||
throw new Error(
|
||||
'`frameworkRecord` was not detected, expected "nextjs" frameworks object'
|
||||
);
|
||||
}
|
||||
expect(frameworkRecord.slug).toBe('nextjs');
|
||||
expect(frameworkRecord.name).toBe('Next.js');
|
||||
expect(frameworkRecord.detectedVersion).toBe('9.0.0');
|
||||
});
|
||||
|
||||
it('Detects a framework record with a matchPackage detector with slashes', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'@ionic/angular': '5.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const frameworkRecord = await detectFrameworkRecord({ fs, frameworkList });
|
||||
if (!frameworkRecord) {
|
||||
throw new Error(
|
||||
'`frameworkRecord` was not detected, expected "ionic-angular" frameworks object'
|
||||
);
|
||||
}
|
||||
expect(frameworkRecord.slug).toBe('ionic-angular');
|
||||
expect(frameworkRecord.detectedVersion).toBe('5.0.0');
|
||||
});
|
||||
|
||||
it('Detect first framework version found', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'nuxt-edge': '3.0.0',
|
||||
nuxt3: '2.0.0',
|
||||
nuxt: '1.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('nuxtjs');
|
||||
expect(framework?.detectedVersion).toBe('1.0.0');
|
||||
});
|
||||
|
||||
it('Detect frameworks based on ascending order in framework list', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
next: '9.0.0',
|
||||
gatsby: '4.18.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('nextjs');
|
||||
});
|
||||
|
||||
it('Detect Nuxt.js', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
nuxt: '1.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('nuxtjs');
|
||||
});
|
||||
|
||||
it('Detect Nuxt.js edge', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'nuxt-edge': '1.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('nuxtjs');
|
||||
});
|
||||
|
||||
it('Detect Gatsby', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
gatsby: '1.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('gatsby');
|
||||
});
|
||||
|
||||
it('Detect Hugo #1', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.yaml': 'baseURL: http://example.org/',
|
||||
'content/post.md': '# hello world',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('hugo');
|
||||
});
|
||||
|
||||
it('Detect Hugo #2', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.json': '{ "baseURL": "http://example.org/" }',
|
||||
'content/post.md': '# hello world',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('hugo');
|
||||
});
|
||||
|
||||
it('Detect Hugo #3', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.toml': 'baseURL = "http://example.org/"',
|
||||
'content/post.md': '# hello world',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('hugo');
|
||||
});
|
||||
|
||||
it('Detect Jekyll', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'_config.yml': 'config',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('jekyll');
|
||||
});
|
||||
|
||||
it('Detect Middleman', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.rb': 'config',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('middleman');
|
||||
});
|
||||
|
||||
it('Detect Scully', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'@angular/cli': 'latest',
|
||||
'@scullyio/init': 'latest',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('scully');
|
||||
});
|
||||
|
||||
it('Detect Zola', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.toml': 'base_url = "/"',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('zola');
|
||||
});
|
||||
});
|
||||
@@ -1,131 +1,7 @@
|
||||
import path from 'path';
|
||||
import frameworkList from '@vercel/frameworks';
|
||||
import workspaceManagers from '../src/workspaces/workspace-managers';
|
||||
import { detectFramework, DetectorFilesystem } from '../src';
|
||||
import { DetectorFilesystemStat } from '../src/detectors/filesystem';
|
||||
|
||||
const posixPath = path.posix;
|
||||
|
||||
class VirtualFilesystem extends DetectorFilesystem {
|
||||
private files: Map<string, Buffer>;
|
||||
private cwd: string;
|
||||
|
||||
constructor(files: { [key: string]: string | Buffer }, cwd = '') {
|
||||
super();
|
||||
this.files = new Map();
|
||||
this.cwd = cwd;
|
||||
Object.entries(files).map(([key, value]) => {
|
||||
const buffer = typeof value === 'string' ? Buffer.from(value) : value;
|
||||
this.files.set(key, buffer);
|
||||
});
|
||||
}
|
||||
|
||||
private _normalizePath(rawPath: string): string {
|
||||
return posixPath.normalize(rawPath);
|
||||
}
|
||||
|
||||
async _hasPath(name: string): Promise<boolean> {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
for (const file of this.files.keys()) {
|
||||
if (file.startsWith(basePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async _isFile(name: string): Promise<boolean> {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
return this.files.has(basePath);
|
||||
}
|
||||
|
||||
async _readFile(name: string): Promise<Buffer> {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
const file = this.files.get(basePath);
|
||||
|
||||
if (file === undefined) {
|
||||
throw new Error('File does not exist');
|
||||
}
|
||||
|
||||
if (typeof file === 'string') {
|
||||
return Buffer.from(file);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* An example of how to implement readdir for a virtual filesystem.
|
||||
*/
|
||||
async _readdir(name = '/'): Promise<DetectorFilesystemStat[]> {
|
||||
return (
|
||||
[...this.files.keys()]
|
||||
.map(filepath => {
|
||||
const basePath = this._normalizePath(
|
||||
posixPath.join(this.cwd, name === '/' ? '' : name)
|
||||
);
|
||||
const fileDirectoryName = posixPath.dirname(filepath);
|
||||
|
||||
if (fileDirectoryName === basePath) {
|
||||
return {
|
||||
name: posixPath.basename(filepath),
|
||||
path: filepath.replace(
|
||||
this.cwd === '' ? this.cwd : `${this.cwd}/`,
|
||||
''
|
||||
),
|
||||
type: 'file',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
(basePath === '.' && fileDirectoryName !== '.') ||
|
||||
fileDirectoryName.startsWith(basePath)
|
||||
) {
|
||||
let subDirectoryName = fileDirectoryName.replace(
|
||||
basePath === '.' ? '' : `${basePath}/`,
|
||||
''
|
||||
);
|
||||
|
||||
if (subDirectoryName.includes('/')) {
|
||||
subDirectoryName = subDirectoryName.split('/')[0];
|
||||
}
|
||||
|
||||
return {
|
||||
name: subDirectoryName,
|
||||
path:
|
||||
name === '/'
|
||||
? subDirectoryName
|
||||
: this._normalizePath(posixPath.join(name, subDirectoryName)),
|
||||
type: 'dir',
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
// remove nulls
|
||||
.filter((stat): stat is DetectorFilesystemStat => stat !== null)
|
||||
// remove duplicates
|
||||
.filter(
|
||||
(stat, index, self) =>
|
||||
index ===
|
||||
self.findIndex(s => s.name === stat.name && s.path === stat.path)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* An example of how to implement chdir for a virtual filesystem.
|
||||
*/
|
||||
_chdir(name: string): DetectorFilesystem {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
const files = Object.fromEntries(
|
||||
[...this.files.keys()].map(key => [key, this.files.get(key) ?? ''])
|
||||
);
|
||||
|
||||
return new VirtualFilesystem(files, basePath);
|
||||
}
|
||||
}
|
||||
import { detectFramework } from '../src';
|
||||
import VirtualFilesystem from './virtual-file-system';
|
||||
|
||||
describe('DetectorFilesystem', () => {
|
||||
it('should return the directory contents relative to the cwd', async () => {
|
||||
@@ -345,6 +221,30 @@ describe('DetectorFilesystem', () => {
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('nuxtjs');
|
||||
});
|
||||
|
||||
it('Detect Nuxt.js Edge', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'nuxt-edge': '1.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('nuxtjs');
|
||||
});
|
||||
|
||||
it('Detect Nuxt.js 3', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
nuxt3: '1.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('nuxtjs');
|
||||
});
|
||||
|
||||
it('Detect Gatsby', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
@@ -428,5 +328,69 @@ describe('DetectorFilesystem', () => {
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('blitzjs');
|
||||
});
|
||||
|
||||
it('Detect Ember via `ember-source`', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'ember-source': 'latest',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('ember');
|
||||
});
|
||||
|
||||
it('Detect Ember via `ember-cli`', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'ember-cli': 'latest',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('ember');
|
||||
});
|
||||
|
||||
it('Detect Brunch via `brunch`', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
brunch: 'latest',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('brunch');
|
||||
});
|
||||
|
||||
it('Detect Brunch via `brunch-config.js`', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'brunch-config.js': '// some config',
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('brunch');
|
||||
});
|
||||
|
||||
it('Detect Hydrogen via `hydrogen.config.js`', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'hydrogen.config.js': '// some config',
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('hydrogen');
|
||||
});
|
||||
|
||||
it('Detect Hydrogen via `@shopify/hydrogen`', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'@shopify/hydrogen': 'latest',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('hydrogen');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,7 +37,8 @@ describe('getMonorepoDefaultSettings', () => {
|
||||
const expectedResultMap: Record<string, Record<string, string>> = {
|
||||
turbo: {
|
||||
monorepoManager: 'turbo',
|
||||
buildCommand: 'cd ../.. && npx turbo run build --filter=app-1...',
|
||||
buildCommand:
|
||||
'cd ../.. && npx turbo run build --filter={packages/app-1}...',
|
||||
installCommand: 'cd ../.. && yarn install',
|
||||
commandForIgnoringBuildStep: 'cd ../.. && npx turbo-ignore',
|
||||
},
|
||||
|
||||
126
packages/fs-detectors/test/virtual-file-system.ts
vendored
Normal file
126
packages/fs-detectors/test/virtual-file-system.ts
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
import path from 'path';
|
||||
import { DetectorFilesystem } from '../src';
|
||||
import { DetectorFilesystemStat } from '../src/detectors/filesystem';
|
||||
|
||||
const posixPath = path.posix;
|
||||
|
||||
export default class VirtualFilesystem extends DetectorFilesystem {
|
||||
private files: Map<string, Buffer>;
|
||||
private cwd: string;
|
||||
|
||||
constructor(files: { [key: string]: string | Buffer }, cwd = '') {
|
||||
super();
|
||||
this.files = new Map();
|
||||
this.cwd = cwd;
|
||||
Object.entries(files).map(([key, value]) => {
|
||||
const buffer = typeof value === 'string' ? Buffer.from(value) : value;
|
||||
this.files.set(key, buffer);
|
||||
});
|
||||
}
|
||||
|
||||
private _normalizePath(rawPath: string): string {
|
||||
return posixPath.normalize(rawPath);
|
||||
}
|
||||
|
||||
async _hasPath(name: string): Promise<boolean> {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
for (const file of this.files.keys()) {
|
||||
if (file.startsWith(basePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async _isFile(name: string): Promise<boolean> {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
return this.files.has(basePath);
|
||||
}
|
||||
|
||||
async _readFile(name: string): Promise<Buffer> {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
const file = this.files.get(basePath);
|
||||
|
||||
if (file === undefined) {
|
||||
throw new Error('File does not exist');
|
||||
}
|
||||
|
||||
if (typeof file === 'string') {
|
||||
return Buffer.from(file);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* An example of how to implement readdir for a virtual filesystem.
|
||||
*/
|
||||
async _readdir(name = '/'): Promise<DetectorFilesystemStat[]> {
|
||||
return (
|
||||
[...this.files.keys()]
|
||||
.map(filepath => {
|
||||
const basePath = this._normalizePath(
|
||||
posixPath.join(this.cwd, name === '/' ? '' : name)
|
||||
);
|
||||
const fileDirectoryName = posixPath.dirname(filepath);
|
||||
|
||||
if (fileDirectoryName === basePath) {
|
||||
return {
|
||||
name: posixPath.basename(filepath),
|
||||
path: filepath.replace(
|
||||
this.cwd === '' ? this.cwd : `${this.cwd}/`,
|
||||
''
|
||||
),
|
||||
type: 'file',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
(basePath === '.' && fileDirectoryName !== '.') ||
|
||||
fileDirectoryName.startsWith(basePath)
|
||||
) {
|
||||
let subDirectoryName = fileDirectoryName.replace(
|
||||
basePath === '.' ? '' : `${basePath}/`,
|
||||
''
|
||||
);
|
||||
|
||||
if (subDirectoryName.includes('/')) {
|
||||
subDirectoryName = subDirectoryName.split('/')[0];
|
||||
}
|
||||
|
||||
return {
|
||||
name: subDirectoryName,
|
||||
path:
|
||||
name === '/'
|
||||
? subDirectoryName
|
||||
: this._normalizePath(posixPath.join(name, subDirectoryName)),
|
||||
type: 'dir',
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
// remove nulls
|
||||
.filter((stat): stat is DetectorFilesystemStat => stat !== null)
|
||||
// remove duplicates
|
||||
.filter(
|
||||
(stat, index, self) =>
|
||||
index ===
|
||||
self.findIndex(s => s.name === stat.name && s.path === stat.path)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* An example of how to implement chdir for a virtual filesystem.
|
||||
*/
|
||||
_chdir(name: string): DetectorFilesystem {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
const files = Object.fromEntries(
|
||||
[...this.files.keys()].map(key => [key, this.files.get(key) ?? ''])
|
||||
);
|
||||
|
||||
return new VirtualFilesystem(files, basePath);
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,6 @@ This plugin sends [Core Web Vitals](https://web.dev/vitals/) to Vercel Analytics
|
||||
npm i @vercel/gatsby-plugin-vercel-analytics
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
yarn add @vercel/gatsby-plugin-vercel-analytics
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/gatsby-plugin-vercel-analytics",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.6",
|
||||
"description": "Track Core Web Vitals in Gatsby projects with Vercel Analytics.",
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
|
||||
4
packages/gatsby-plugin-vercel-builder/.gitignore
vendored
Normal file
4
packages/gatsby-plugin-vercel-builder/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
gatsby-node.*
|
||||
!gatsby-node.ts
|
||||
|
||||
dist
|
||||
0
packages/gatsby-plugin-vercel-builder/README.md
Normal file
0
packages/gatsby-plugin-vercel-builder/README.md
Normal file
28
packages/gatsby-plugin-vercel-builder/gatsby-node.ts
Normal file
28
packages/gatsby-plugin-vercel-builder/gatsby-node.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import path from 'path';
|
||||
|
||||
import type { GatsbyNode } from 'gatsby';
|
||||
|
||||
// this gets built separately, so import from "dist" instead of "src"
|
||||
import { generateVercelBuildOutputAPI3Output } from './dist';
|
||||
|
||||
export const pluginOptionsSchema: GatsbyNode['pluginOptionsSchema'] = ({
|
||||
Joi,
|
||||
}) => {
|
||||
return Joi.object({
|
||||
exportPath: Joi.string().optional(),
|
||||
});
|
||||
};
|
||||
|
||||
export const onPostBuild: GatsbyNode['onPostBuild'] = async (
|
||||
{ store },
|
||||
pluginOptions
|
||||
) => {
|
||||
// validated by `pluginOptionSchema`
|
||||
const exportPath = (pluginOptions?.exportPath ??
|
||||
path.join('.vercel', 'output', 'config.json')) as string;
|
||||
|
||||
await generateVercelBuildOutputAPI3Output({
|
||||
exportPath,
|
||||
gatsbyStoreState: store.getState(),
|
||||
});
|
||||
};
|
||||
33
packages/gatsby-plugin-vercel-builder/package.json
Normal file
33
packages/gatsby-plugin-vercel-builder/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@vercel/gatsby-plugin-vercel-builder",
|
||||
"version": "0.1.1",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"dist",
|
||||
"gatsby-node.ts",
|
||||
"gatsby-node.js",
|
||||
"gatsby-node.js.map"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "pnpm build:src && pnpm build:gatsby",
|
||||
"build:gatsby": "tsc -p tsconfig.gatsby.json",
|
||||
"build:src": "tsc -p tsconfig.src.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "5.9.0",
|
||||
"@vercel/node": "2.8.14",
|
||||
"@vercel/routing-utils": "2.1.8",
|
||||
"ajv": "8.12.0",
|
||||
"esbuild": "0.16.17",
|
||||
"etag": "1.8.1",
|
||||
"fs-extra": "11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/etag": "1.8.0",
|
||||
"@types/fs-extra": "11.0.1",
|
||||
"@types/node": "14.18.33",
|
||||
"@types/react": "18.0.26",
|
||||
"gatsby": "4.25.2",
|
||||
"typescript": "4.3.4"
|
||||
}
|
||||
}
|
||||
129
packages/gatsby-plugin-vercel-builder/src/handlers/build.ts
Normal file
129
packages/gatsby-plugin-vercel-builder/src/handlers/build.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { getNodeVersion } from '@vercel/build-utils';
|
||||
import { build } from 'esbuild';
|
||||
import {
|
||||
copy,
|
||||
copyFile,
|
||||
pathExists,
|
||||
writeJson,
|
||||
writeFileSync,
|
||||
ensureFileSync,
|
||||
} from 'fs-extra';
|
||||
|
||||
import type {
|
||||
NodejsServerlessFunctionConfig,
|
||||
PrerenderFunctionConfig,
|
||||
} from './../types';
|
||||
|
||||
export const writeHandler = async ({
|
||||
outDir,
|
||||
handlerFile,
|
||||
}: {
|
||||
outDir: string;
|
||||
handlerFile: string;
|
||||
}) => {
|
||||
const { major } = await getNodeVersion(process.cwd());
|
||||
|
||||
try {
|
||||
return await build({
|
||||
entryPoints: [handlerFile],
|
||||
loader: { '.ts': 'ts' },
|
||||
outfile: join(outDir, './index.js'),
|
||||
format: 'cjs',
|
||||
target: `node${major}`,
|
||||
platform: 'node',
|
||||
bundle: true,
|
||||
minify: true,
|
||||
define: {
|
||||
'process.env.NODE_ENV': "'production'",
|
||||
},
|
||||
});
|
||||
} catch (e: any) {
|
||||
console.error('Failed to build lambda handler', e.message);
|
||||
}
|
||||
};
|
||||
|
||||
export const writeVCConfig = async ({
|
||||
functionDir,
|
||||
handler = 'index.js',
|
||||
}: {
|
||||
functionDir: string;
|
||||
handler?: string;
|
||||
}) => {
|
||||
const { runtime } = await getNodeVersion(process.cwd());
|
||||
|
||||
const config: NodejsServerlessFunctionConfig = {
|
||||
runtime,
|
||||
handler,
|
||||
launcherType: 'Nodejs',
|
||||
shouldAddHelpers: true,
|
||||
};
|
||||
|
||||
return writeJson(`${functionDir}/.vc-config.json`, config);
|
||||
};
|
||||
|
||||
export const writePrerenderConfig = (outputPath: string) => {
|
||||
const config: PrerenderFunctionConfig = {
|
||||
expiration: false,
|
||||
};
|
||||
|
||||
ensureFileSync(outputPath);
|
||||
return writeFileSync(outputPath, JSON.stringify(config));
|
||||
};
|
||||
|
||||
export async function movePageData({ functionDir }: { functionDir: string }) {
|
||||
await copy(
|
||||
join('.vercel', 'output', 'static', 'page-data'),
|
||||
join(functionDir, 'page-data')
|
||||
);
|
||||
}
|
||||
|
||||
export async function copyFunctionLibs({
|
||||
functionDir,
|
||||
}: {
|
||||
functionDir: string;
|
||||
}) {
|
||||
/* Copies the required libs for Serverless Functions from .cache to the <name>.func folder */
|
||||
await Promise.allSettled(
|
||||
[
|
||||
{
|
||||
src: join('.cache', 'query-engine'),
|
||||
dest: join(functionDir, '.cache', 'query-engine'),
|
||||
},
|
||||
{
|
||||
src: join('.cache', 'page-ssr'),
|
||||
dest: join(functionDir, '.cache', 'page-ssr'),
|
||||
},
|
||||
// {
|
||||
// src: join(functionDir, '.cache', 'query-engine', 'assets'),
|
||||
// dest: join(functionDir, 'assets'),
|
||||
// },
|
||||
{
|
||||
src: join('.cache', 'data', 'datastore'),
|
||||
dest: join(functionDir, '.cache', 'data', 'datastore'),
|
||||
},
|
||||
{
|
||||
src: join('.cache', 'caches'),
|
||||
dest: join(functionDir, '.cache', 'caches'),
|
||||
},
|
||||
].map(({ src, dest }) => copy(src, dest))
|
||||
);
|
||||
}
|
||||
|
||||
export async function copyHTMLFiles({ functionDir }: { functionDir: string }) {
|
||||
/* If available, copies the 404.html and 500.html files to the <name>.func/lib folder */
|
||||
for (const htmlFile of ['404', '500']) {
|
||||
if (await pathExists(join('public', `${htmlFile}.html`))) {
|
||||
try {
|
||||
await copyFile(
|
||||
join('public', `${htmlFile}.html`),
|
||||
join(functionDir, `${htmlFile}.html`)
|
||||
);
|
||||
} catch (e: any) {
|
||||
console.error('Failed to copy HTML files', e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import os from 'os';
|
||||
import { join } from 'path';
|
||||
import etag from 'etag';
|
||||
import { copySync, existsSync, readFileSync } from 'fs-extra';
|
||||
|
||||
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
||||
|
||||
import { getGraphQLEngine, getPageSSRHelpers } from '../utils';
|
||||
|
||||
const TMP_DATA_PATH = join(os.tmpdir(), 'data/datastore');
|
||||
const CUR_DATA_PATH = join(__dirname, '.cache/data/datastore');
|
||||
|
||||
if (!existsSync(TMP_DATA_PATH)) {
|
||||
// Copies executable `data` files to the writable /tmp directory.
|
||||
copySync(CUR_DATA_PATH, TMP_DATA_PATH);
|
||||
}
|
||||
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
const splitPathName = req.url!.split('/')[2];
|
||||
const pathName = splitPathName === `index` ? `/` : splitPathName;
|
||||
|
||||
if (
|
||||
existsSync(join(__dirname, 'page-data', splitPathName, 'page-data.json'))
|
||||
) {
|
||||
/* Non-SSR/DSG pages already have a pre-generated page-data.json file.
|
||||
Instead of generating this dynamically, we can directly serve this JSON. */
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.json(
|
||||
readFileSync(
|
||||
join(__dirname, 'page-data', splitPathName, 'page-data.json'),
|
||||
'utf-8'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { getData, renderPageData } = await getPageSSRHelpers();
|
||||
const graphqlEngine = await getGraphQLEngine();
|
||||
|
||||
const data = await getData({
|
||||
req,
|
||||
graphqlEngine,
|
||||
pathName,
|
||||
});
|
||||
|
||||
const pageData = await renderPageData({ data });
|
||||
|
||||
if (data.serverDataHeaders) {
|
||||
for (const [name, value] of Object.entries(data.serverDataHeaders)) {
|
||||
res.setHeader(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
res.setHeader('ETag', etag(JSON.stringify(pageData)));
|
||||
return res.json(pageData);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { join } from 'path';
|
||||
import os from 'os';
|
||||
import { copySync, existsSync } from 'fs-extra';
|
||||
|
||||
import { getPageSSRHelpers, getGraphQLEngine } from '../utils';
|
||||
|
||||
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
||||
|
||||
const TMP_DATA_PATH = join(os.tmpdir(), 'data/datastore');
|
||||
const CUR_DATA_PATH = join(__dirname, '.cache/data/datastore');
|
||||
|
||||
if (!existsSync(TMP_DATA_PATH)) {
|
||||
// Copies executable `data` files to the writable /tmp directory.
|
||||
copySync(CUR_DATA_PATH, TMP_DATA_PATH);
|
||||
}
|
||||
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
const graphqlEngine = await getGraphQLEngine();
|
||||
const { getData, renderHTML } = await getPageSSRHelpers();
|
||||
|
||||
const data = await getData({
|
||||
pathName: req.url as string,
|
||||
graphqlEngine,
|
||||
req,
|
||||
});
|
||||
|
||||
const results = await renderHTML({ data });
|
||||
|
||||
if (data.serverDataHeaders) {
|
||||
for (const [name, value] of Object.entries(data.serverDataHeaders)) {
|
||||
res.setHeader(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.serverDataStatus) {
|
||||
res.statusCode = data.serverDataStatus;
|
||||
}
|
||||
|
||||
res.send(results);
|
||||
}
|
||||
20
packages/gatsby-plugin-vercel-builder/src/handlers/utils.ts
Normal file
20
packages/gatsby-plugin-vercel-builder/src/handlers/utils.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { join } from 'path';
|
||||
import os from 'os';
|
||||
|
||||
const TMP_DATA_PATH = join(os.tmpdir(), 'data/datastore');
|
||||
|
||||
export async function getGraphQLEngine() {
|
||||
const { GraphQLEngine } = (await import(
|
||||
join(__dirname, '.cache/query-engine/index.js')
|
||||
)) as typeof import('gatsby/dist/schema/graphql-engine/entry');
|
||||
|
||||
return new GraphQLEngine({ dbPath: TMP_DATA_PATH });
|
||||
}
|
||||
|
||||
export async function getPageSSRHelpers() {
|
||||
const { getData, renderPageData, renderHTML } = (await import(
|
||||
join(__dirname, '.cache/page-ssr/index.js')
|
||||
)) as typeof import('gatsby/dist/utils/page-ssr-module/entry');
|
||||
|
||||
return { getData, renderPageData, renderHTML };
|
||||
}
|
||||
126
packages/gatsby-plugin-vercel-builder/src/helpers/functions.ts
Normal file
126
packages/gatsby-plugin-vercel-builder/src/helpers/functions.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { ensureDir } from 'fs-extra';
|
||||
|
||||
import { createSymlink } from '../utils/symlink';
|
||||
import {
|
||||
writeHandler,
|
||||
writeVCConfig,
|
||||
copyFunctionLibs,
|
||||
movePageData,
|
||||
copyHTMLFiles,
|
||||
writePrerenderConfig,
|
||||
} from '../handlers/build';
|
||||
import { GatsbyFunction } from '../schemas';
|
||||
import { Routes } from '../types';
|
||||
|
||||
export async function createServerlessFunctions({
|
||||
dsgRoutes,
|
||||
ssrRoutes,
|
||||
}: Routes) {
|
||||
/* Gatsby SSR/DSG on Vercel is enabled through Vercel Serverless Functions.
|
||||
This plugin creates one Serverless Function called `_ssr.func` that is used by SSR and DSG pages through symlinks.
|
||||
DSG is enabled through prerender functions.
|
||||
*/
|
||||
const functionName = '_ssr.func';
|
||||
const functionDir = join('.vercel', 'output', 'functions', functionName);
|
||||
const handlerFile = join(
|
||||
__dirname,
|
||||
'..',
|
||||
'handlers',
|
||||
'templates',
|
||||
'./ssr-handler.js'
|
||||
);
|
||||
|
||||
await ensureDir(functionDir);
|
||||
|
||||
await Promise.all([
|
||||
writeHandler({ outDir: functionDir, handlerFile }),
|
||||
copyFunctionLibs({ functionDir }),
|
||||
copyHTMLFiles({ functionDir }),
|
||||
writeVCConfig({ functionDir }),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
...ssrRoutes.map(async (pathName: string) => {
|
||||
return createSymlink(pathName, functionName);
|
||||
}),
|
||||
...dsgRoutes.map(async (pathName: string) => {
|
||||
writePrerenderConfig(
|
||||
join(
|
||||
'.vercel',
|
||||
'output',
|
||||
'functions',
|
||||
`${pathName}.prerender-config.json`
|
||||
)
|
||||
);
|
||||
|
||||
return createSymlink(pathName, functionName);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function createPageDataFunction({ dsgRoutes, ssrRoutes }: Routes) {
|
||||
/* Gatsby uses /page-data/<path>/page-data.json to fetch data. This plugin creates a
|
||||
`_page-data.func` function that dynamically generates this data if it's not available in `static/page-data`. */
|
||||
const functionName = '_page-data.func';
|
||||
const functionDir = join('.vercel', 'output', 'functions', functionName);
|
||||
const handlerFile = join(
|
||||
__dirname,
|
||||
'..',
|
||||
'handlers',
|
||||
'templates',
|
||||
'./page-data.js'
|
||||
);
|
||||
|
||||
await ensureDir(functionDir);
|
||||
|
||||
await Promise.all([
|
||||
writeHandler({ outDir: functionDir, handlerFile }),
|
||||
copyFunctionLibs({ functionDir }),
|
||||
movePageData({ functionDir }),
|
||||
writeVCConfig({ functionDir }),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
...ssrRoutes.map(async (pathName: string) => {
|
||||
return createSymlink(
|
||||
`page-data/${pathName}/page-data.json`,
|
||||
functionName
|
||||
);
|
||||
}),
|
||||
...dsgRoutes.map(async (pathName: string) => {
|
||||
const funcPath = `page-data/${pathName}/page-data.json`;
|
||||
|
||||
writePrerenderConfig(
|
||||
join(
|
||||
'.vercel',
|
||||
'output',
|
||||
'functions',
|
||||
`${funcPath}.prerender-config.json`
|
||||
)
|
||||
);
|
||||
|
||||
return createSymlink(funcPath, functionName);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function createAPIRoutes(functions: GatsbyFunction[]) {
|
||||
const apiDir = join('.vercel', 'output', 'functions', 'api');
|
||||
await ensureDir(apiDir);
|
||||
|
||||
await Promise.allSettled(
|
||||
functions.map(async (func: GatsbyFunction) => {
|
||||
const apiRouteDir = `${apiDir}/${func.functionRoute}.func`;
|
||||
const handlerFile = func.originalAbsoluteFilePath;
|
||||
|
||||
await ensureDir(apiRouteDir);
|
||||
|
||||
await Promise.all([
|
||||
writeHandler({ outDir: apiRouteDir, handlerFile }),
|
||||
writeVCConfig({ functionDir: apiRouteDir }),
|
||||
]);
|
||||
})
|
||||
);
|
||||
}
|
||||
10
packages/gatsby-plugin-vercel-builder/src/helpers/static.ts
Normal file
10
packages/gatsby-plugin-vercel-builder/src/helpers/static.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { copy, ensureDir } from 'fs-extra';
|
||||
|
||||
export async function createStaticDir() {
|
||||
const targetDir = join(process.cwd(), '.vercel', 'output', 'static');
|
||||
await ensureDir(targetDir);
|
||||
|
||||
await copy(join(process.cwd(), 'public'), targetDir);
|
||||
}
|
||||
108
packages/gatsby-plugin-vercel-builder/src/index.ts
Normal file
108
packages/gatsby-plugin-vercel-builder/src/index.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { getTransformedRoutes } from '@vercel/routing-utils';
|
||||
import { pathExists, writeJson, remove, mkdirp } from 'fs-extra';
|
||||
|
||||
import { validateGatsbyState } from './schemas';
|
||||
import {
|
||||
createServerlessFunctions,
|
||||
createPageDataFunction,
|
||||
createAPIRoutes,
|
||||
} from './helpers/functions';
|
||||
import { createStaticDir } from './helpers/static';
|
||||
|
||||
export interface GenerateVercelBuildOutputAPI3OutputOptions {
|
||||
exportPath: string;
|
||||
gatsbyStoreState: {
|
||||
pages: Map<string, unknown>;
|
||||
redirects: unknown;
|
||||
functions: unknown;
|
||||
};
|
||||
[x: string]: unknown;
|
||||
}
|
||||
import type { Config, Routes } from './types';
|
||||
export async function generateVercelBuildOutputAPI3Output({
|
||||
exportPath,
|
||||
gatsbyStoreState,
|
||||
}: GenerateVercelBuildOutputAPI3OutputOptions) {
|
||||
const state = {
|
||||
pages: Array.from(gatsbyStoreState.pages.entries()), // must transform from a Map for validation
|
||||
redirects: gatsbyStoreState.redirects,
|
||||
functions: gatsbyStoreState.functions,
|
||||
};
|
||||
|
||||
console.log(state);
|
||||
|
||||
if (validateGatsbyState(state)) {
|
||||
console.log('▲ Creating Vercel build output');
|
||||
await remove(join('.vercel', 'output'));
|
||||
|
||||
const { pages, redirects, functions } = state;
|
||||
|
||||
const { ssrRoutes, dsgRoutes } = pages.reduce<Routes>(
|
||||
(acc, [, cur]) => {
|
||||
if (cur.mode === 'SSR') {
|
||||
acc.ssrRoutes.push(cur.path);
|
||||
} else if (cur.mode === 'DSG') {
|
||||
acc.dsgRoutes.push(cur.path);
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
ssrRoutes: [],
|
||||
dsgRoutes: [],
|
||||
}
|
||||
);
|
||||
|
||||
await createStaticDir();
|
||||
|
||||
await mkdirp(join('.cache', 'caches'));
|
||||
|
||||
const createPromises: Promise<void>[] = [];
|
||||
|
||||
if (functions.length > 0) createPromises.push(createAPIRoutes(functions));
|
||||
|
||||
if (ssrRoutes.length > 0 || dsgRoutes.length > 0) {
|
||||
createPromises.push(createPageDataFunction({ ssrRoutes, dsgRoutes }));
|
||||
createPromises.push(createServerlessFunctions({ ssrRoutes, dsgRoutes }));
|
||||
}
|
||||
|
||||
await Promise.all(createPromises);
|
||||
|
||||
const vercelConfigPath = `${process.cwd()}/vercel.config.js`;
|
||||
const vercelConfig: Config = (await pathExists(vercelConfigPath))
|
||||
? require(vercelConfigPath).default
|
||||
: {};
|
||||
|
||||
const { routes } = getTransformedRoutes({
|
||||
...vercelConfig,
|
||||
trailingSlash: false,
|
||||
redirects: redirects.map(({ fromPath, toPath, isPermanent }) => ({
|
||||
source: fromPath,
|
||||
destination: toPath,
|
||||
permanent: isPermanent,
|
||||
})),
|
||||
rewrites: [
|
||||
{
|
||||
source: '^/page-data(?:/(.*))/page-data\\.json$',
|
||||
destination: '/_page-data',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const config: Config = {
|
||||
version: 3,
|
||||
routes: routes || undefined,
|
||||
};
|
||||
|
||||
console.log(config);
|
||||
|
||||
await writeJson(exportPath, config);
|
||||
console.log('Vercel output has been generated');
|
||||
} else {
|
||||
throw new Error(
|
||||
'Gatsby state validation error. Please file an issue https://vercel.com/help#issues'
|
||||
);
|
||||
}
|
||||
}
|
||||
82
packages/gatsby-plugin-vercel-builder/src/schemas.ts
Normal file
82
packages/gatsby-plugin-vercel-builder/src/schemas.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type {
|
||||
IGatsbyPage,
|
||||
IGatsbyFunction,
|
||||
IRedirect,
|
||||
} from 'gatsby/dist/redux/types';
|
||||
import Ajv, { JSONSchemaType } from 'ajv';
|
||||
|
||||
export type GatsbyPage = Pick<IGatsbyPage, 'mode' | 'path'>;
|
||||
const GatsbyPageSchema: JSONSchemaType<GatsbyPage> = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
mode: {
|
||||
type: 'string',
|
||||
enum: ['SSG', 'DSG', 'SSR'],
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['mode', 'path'],
|
||||
} as const;
|
||||
|
||||
export interface GatsbyState {
|
||||
pages: Array<[string, GatsbyPage]>;
|
||||
redirects: Array<GatsbyRedirect>;
|
||||
functions: Array<GatsbyFunction>;
|
||||
}
|
||||
|
||||
export type GatsbyFunction = Pick<
|
||||
IGatsbyFunction,
|
||||
'functionRoute' | 'originalAbsoluteFilePath'
|
||||
>;
|
||||
const GatsbyFunctionSchema: JSONSchemaType<GatsbyFunction> = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
functionRoute: { type: 'string' },
|
||||
originalAbsoluteFilePath: { type: 'string' },
|
||||
},
|
||||
required: ['functionRoute', 'originalAbsoluteFilePath'],
|
||||
} as const;
|
||||
|
||||
export type GatsbyRedirect = Pick<
|
||||
IRedirect,
|
||||
'fromPath' | 'toPath' | 'isPermanent'
|
||||
>;
|
||||
const GatsbyRedirectSchema: JSONSchemaType<GatsbyRedirect> = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fromPath: { type: 'string' },
|
||||
toPath: { type: 'string' },
|
||||
isPermanent: { type: 'boolean', nullable: true },
|
||||
},
|
||||
required: ['fromPath', 'toPath'],
|
||||
} as const;
|
||||
|
||||
const GatsbyStateSchema: JSONSchemaType<GatsbyState> = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pages: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'array',
|
||||
minItems: 2,
|
||||
maxItems: 2,
|
||||
items: [{ type: 'string' }, GatsbyPageSchema],
|
||||
},
|
||||
},
|
||||
redirects: {
|
||||
type: 'array',
|
||||
items: GatsbyRedirectSchema,
|
||||
},
|
||||
functions: {
|
||||
type: 'array',
|
||||
items: GatsbyFunctionSchema,
|
||||
},
|
||||
},
|
||||
required: ['pages', 'redirects', 'functions'],
|
||||
additionalProperties: true,
|
||||
} as const;
|
||||
|
||||
export const ajv = new Ajv({ allErrors: true });
|
||||
export const validateGatsbyState = ajv.compile(GatsbyStateSchema);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user