Compare commits

..

19 Commits

Author SHA1 Message Date
Kiko Beats
a5d2e86274 migrate waituntil for edge 2024-05-06 18:00:38 +02:00
Kiko Beats
e53063dfa2 remove unused 2024-05-06 16:41:41 +02:00
Kiko Beats
56c53201fe fix filename 2024-05-06 16:39:32 +02:00
Kiko Beats
94a915a023 move logic into a package 2024-05-06 16:19:22 +02:00
Kiko Beats
cfc42df9f8 better accommodation 2024-05-06 16:09:45 +02:00
Kiko Beats
49595bf263 unify context & import implementation 2024-05-06 16:01:00 +02:00
Kiko Beats
85db2f1f2f add waitUntil from import 2024-05-06 15:36:23 +02:00
Kiko Beats
be74f5d995 refactor: remove unused file 2024-05-02 20:10:41 +02:00
Kiko Beats
edf30866fe refactor: naming 2024-05-02 20:00:20 +02:00
Kiko Beats
0e98d20d8c fix: linter 2024-05-02 17:52:43 +02:00
Kiko Beats
9ef3bd09fd refactor: extract common code 2024-05-02 17:48:43 +02:00
Kiko Beats
d22d69d053 refactor: change the message 2024-05-02 17:48:43 +02:00
Kiko Beats
d0e5710d44 Create wild-ladybugs-leave.md 2024-05-02 16:47:08 +02:00
Kiko Beats
c5f7485275 refactor: remove old test
it's unnecessary after switched from node-fetch to undici:
https://github.com/vercel/vercel/pull/10767
2024-05-02 16:37:35 +02:00
Kiko Beats
df1523f8e2 migrate tests 2024-05-02 11:48:24 +02:00
Kiko Beats
e90c7b8513 add printLogs argument
useful for debugging
2024-05-02 11:46:32 +02:00
Kiko Beats
e1cbe0c4e0 chore: add warning 2024-04-30 20:43:48 +02:00
Kiko Beats
ad10c6850a fix: linter 2024-04-30 19:56:46 +02:00
Kiko Beats
e6b7ae72b8 [cli]: wait waitUntil promises in Serverless 2024-04-30 19:53:49 +02:00
285 changed files with 1923 additions and 9590 deletions

View File

@@ -0,0 +1,5 @@
---
"@vercel/node": patch
---
[cli]: wait waitUntil promises in Serverless

4
.github/CODEOWNERS vendored
View File

@@ -5,13 +5,13 @@
* @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @erikareads
/.github/workflows @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @erikareads @ijjk
/packages/fs-detectors @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @erikareads @agadzik @chloetedder
/packages/next @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @erikareads @timneutkens @ijjk @ztanner @huozhi @Ethan-Arrowood
/packages/next @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @erikareads @timneutkens @ijjk @ztanner @huozhi @Ethan-Arrowood @styfle
/packages/routing-utils @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @erikareads @ijjk
/packages/static-build @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @erikareads
/packages/edge @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @erikareads @vercel/compute
/examples @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @erikareads @leerob
/examples/create-react-app @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @erikareads @Timer
/examples/nextjs @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @erikareads @timneutkens @ijjk @ztanner @huozhi @Ethan-Arrowood
/examples/nextjs @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @erikareads @timneutkens @ijjk @ztanner @huozhi @Ethan-Arrowood @styfle
/packages/node @TooTallNate @EndangeredMassa @trek @onsclom @jeffsee55 @erikareads @Kikobeats
# Unrestricted Paths

View File

@@ -1,124 +0,0 @@
name: Tests Node.js v18
on:
push:
branches:
- main
tags:
- '!*'
pull_request:
env:
NODE_VERSION: '18'
TURBO_REMOTE_ONLY: 'true'
TURBO_TEAM: 'vercel'
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
setup:
name: Find Changes
runs-on: ubuntu-latest
outputs:
tests: ${{ steps['set-tests'].outputs['tests'] }}
dplUrl: ${{ steps.waitForTarball.outputs.url }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: install pnpm@8.3.1
run: npm i -g pnpm@8.3.1
- run: pnpm install
- id: set-tests
run: |
TESTS_ARRAY=$(node utils/chunk-tests-18.js $SCRIPT_NAME)
echo "Files to test:"
echo "$TESTS_ARRAY"
echo "tests=$TESTS_ARRAY" >> $GITHUB_OUTPUT
- uses: patrickedqvist/wait-for-vercel-preview@bfdff514ff78a669f2536e9f4dd4ef5813a704a2
id: waitForTarball
with:
token: ${{ secrets.GITHUB_TOKEN }}
max_timeout: 360
check_interval: 5
test:
timeout-minutes: 120
runs-on: ${{ matrix.runner }}
name: ${{matrix.scriptName}} (${{matrix.packageName}}, ${{matrix.chunkNumber}}, ${{ matrix.runner }})
if: ${{ needs.setup.outputs['tests'] != '[]' }}
needs:
- setup
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.setup.outputs['tests']) }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.nodeVersion || env.NODE_VERSION }}
# yarn 1.22.21 introduced a Corepack bug when running tests.
# this can be removed once https://github.com/yarnpkg/yarn/issues/9015 is resolved
- name: install yarn@1.22.19
run: npm i -g yarn@1.22.19
- name: install pnpm@8.3.1
run: npm i -g pnpm@8.3.1
- 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" --log-order=stream --scope=${{matrix.packageName}} --include-dependencies --no-deps
env:
FORCE_COLOR: '1'
- name: Test ${{matrix.packageName}}
run: node utils/gen.js && node_modules/.bin/turbo run ${{matrix.testScript}} --summarize --cache-dir=".turbo" --log-order=stream --scope=${{matrix.packageName}} --no-deps -- ${{ join(matrix.testPaths, ' ') }}
shell: bash
env:
JEST_JUNIT_OUTPUT_FILE: ${{github.workspace}}/.junit-reports/${{matrix.scriptName}}-${{matrix.packageName}}-${{matrix.chunkNumber}}-${{ matrix.runner }}.xml
VERCEL_CLI_VERSION: ${{ needs.setup.outputs.dplUrl }}/tarballs/vercel.tgz
VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }}
VERCEL_TEST_REGISTRATION_URL: ${{ secrets.VERCEL_TEST_REGISTRATION_URL }}
FORCE_COLOR: '1'
- name: 'Determine Turbo HIT or MISS'
if: ${{ !cancelled() }}
id: turbo-summary
shell: bash
run: |
TURBO_MISS_COUNT=`node utils/determine-turbo-hit-or-miss.js`
echo "MISS COUNT: $TURBO_MISS_COUNT"
echo "misses=$TURBO_MISS_COUNT" >> $GITHUB_OUTPUT
- name: 'Upload Test Report to Datadog'
if: ${{ steps['turbo-summary'].outputs.misses != '0' && !cancelled() }}
run: 'npx @datadog/datadog-ci@2.18.1 junit upload --service vercel-cli .junit-reports'
env:
DATADOG_API_KEY: ${{secrets.DATADOG_API_KEY_CLI}}
DD_ENV: ci
summary:
name: Summary
runs-on: ubuntu-latest
timeout-minutes: 5
if: always()
needs:
- test
steps:
- name: Check All
run: |-
for status in ${{ join(needs.*.result, ' ') }}
do
if [ "$status" != "success" ] && [ "$status" != "skipped" ]
then
echo "Some checks failed"
exit 1
fi
done

View File

@@ -1,4 +1,4 @@
name: Tests Node.js v16
name: Tests
on:
push:

View File

@@ -37,7 +37,7 @@ You can use the `dev` script to run local changes as if you were invoking Vercel
When contributing to this repository, please first discuss the change you wish to make via [GitHub Discussions](https://github.com/vercel/vercel/discussions/new) with the owners of this repository before submitting a Pull Request.
Please read our [Code of Conduct](./.github/CODE_OF_CONDUCT.md) and follow it in all your interactions with the project.
Please read our [Code of Conduct](CODE_OF_CONDUCT.md) and follow it in all your interactions with the project.
### Local development

View File

@@ -9,7 +9,7 @@
},
"devDependencies": {
"@types/jest": "27.4.1",
"@vercel/build-utils": "8.2.0",
"@vercel/build-utils": "8.0.0",
"@vercel/frameworks": "3.0.2"
},
"version": null

View File

@@ -9,18 +9,18 @@
"typecheck": "tsc"
},
"dependencies": {
"@remix-run/node": "^2.9.2",
"@remix-run/react": "^2.9.2",
"@remix-run/server-runtime": "^2.9.2",
"@remix-run/node": "^2.8.0",
"@remix-run/react": "^2.8.0",
"@remix-run/server-runtime": "^2.8.0",
"@vercel/analytics": "^1.2.2",
"@vercel/remix": "2.9.2-patch.2",
"@vercel/remix": "^2.8.0",
"isbot": "^4",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@remix-run/dev": "^2.9.2",
"@remix-run/eslint-config": "^2.9.2",
"@remix-run/dev": "^2.8.0",
"@remix-run/eslint-config": "^2.8.0",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"eslint": "^8.38.0",

View File

@@ -1,40 +1,5 @@
# @vercel-internals/types
## 1.0.36
### Patch Changes
- Updated dependencies [[`d3c1267e2`](https://github.com/vercel/vercel/commit/d3c1267e24082789ea6382cf6af81dd40df288ff), [`ccd7eb1fb`](https://github.com/vercel/vercel/commit/ccd7eb1fb78f7ac9effdbe1935de3bda82c97fe3)]:
- @vercel/build-utils@8.2.0
## 1.0.35
### Patch Changes
- Updated dependencies [[`ad6945435`](https://github.com/vercel/vercel/commit/ad69454352b519b2b0ed326f245c779530554bf2)]:
- @vercel/build-utils@8.1.3
## 1.0.34
### Patch Changes
- Updated dependencies [[`1682ad43d`](https://github.com/vercel/vercel/commit/1682ad43d0064b22b1248a7e946746b838f00076)]:
- @vercel/build-utils@8.1.2
## 1.0.33
### Patch Changes
- Updated dependencies [[`2f7a6ed5f`](https://github.com/vercel/vercel/commit/2f7a6ed5f92d454000f92247d3b6548e2064f4e6)]:
- @vercel/build-utils@8.1.1
## 1.0.32
### Patch Changes
- Updated dependencies [[`5014b1e82`](https://github.com/vercel/vercel/commit/5014b1e82a46181baeb727ffe6d14000b6a4b1d7)]:
- @vercel/build-utils@8.1.0
## 1.0.31
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "@vercel-internals/types",
"version": "1.0.36",
"version": "1.0.31",
"types": "index.d.ts",
"main": "index.d.ts",
"files": [
@@ -10,7 +10,7 @@
"dependencies": {
"@types/node": "14.14.31",
"@vercel-internals/constants": "1.0.4",
"@vercel/build-utils": "8.2.0",
"@vercel/build-utils": "8.0.0",
"@vercel/routing-utils": "3.1.0"
},
"devDependencies": {

View File

@@ -1,40 +1,5 @@
# @vercel/build-utils
## 8.2.0
### Minor Changes
- fix corepack detection for package manager version determination ([#11596](https://github.com/vercel/vercel/pull/11596))
### Patch Changes
- Fix triggering of ignored project settings node version warning ([#11550](https://github.com/vercel/vercel/pull/11550))
## 8.1.3
### Patch Changes
- [build-utils] log more around package manager detection ([#11594](https://github.com/vercel/vercel/pull/11594))
## 8.1.2
### Patch Changes
- add log to package manager version detection ([#11592](https://github.com/vercel/vercel/pull/11592))
## 8.1.1
### Patch Changes
- [build-utils] pnpm lockfile testing and fixing ([#11591](https://github.com/vercel/vercel/pull/11591))
## 8.1.0
### Minor Changes
- Update pnpm version detection logic ([#11445](https://github.com/vercel/vercel/pull/11445))
Add support for pnpm 9
## 8.0.0
### Major Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "8.2.0",
"version": "8.0.0",
"license": "Apache-2.0",
"main": "./dist/index.js",
"types": "./dist/index.d.js",

View File

@@ -240,51 +240,38 @@ export function getSpawnOptions(
export async function getNodeVersion(
destPath: string,
fallbackVersion = process.env.VERCEL_PROJECT_SETTINGS_NODE_VERSION,
nodeVersionFallback = process.env.VERCEL_PROJECT_SETTINGS_NODE_VERSION,
config: Config = {},
meta: Meta = {},
availableVersions = getAvailableNodeVersions()
): Promise<NodeVersion> {
const latestVersion = getLatestNodeVersion(availableVersions);
const latest = getLatestNodeVersion(availableVersions);
if (meta.isDev) {
// Use the system-installed version of `node` in PATH for `vercel dev`
return { ...latestVersion, runtime: 'nodejs' };
return { ...latest, runtime: 'nodejs' };
}
const { packageJson } = await scanParentDirs(destPath, true);
const configuredVersion = config.nodeVersion || fallbackVersion;
const packageJsonVersion = packageJson?.engines?.node;
const supportedNodeVersion = await getSupportedNodeVersion(
packageJsonVersion || configuredVersion,
!packageJsonVersion,
availableVersions
);
let nodeVersion = config.nodeVersion || nodeVersionFallback;
let isAuto = true;
if (packageJson?.engines?.node) {
const { node } = packageJson.engines;
if (
configuredVersion &&
!intersects(configuredVersion, supportedNodeVersion.range)
) {
if (nodeVersion && validRange(node) && !intersects(nodeVersion, node)) {
console.warn(
`Warning: Due to "engines": { "node": "${node}" } in your \`package.json\` file, the Node.js Version defined in your Project Settings ("${configuredVersion}") will not apply. Learn More: http://vercel.link/node-version`
`Warning: Due to "engines": { "node": "${node}" } in your \`package.json\` file, the Node.js Version defined in your Project Settings ("${nodeVersion}") will not apply. Learn More: http://vercel.link/node-version`
);
}
if (coerce(node)?.raw === node) {
} else if (coerce(node)?.raw === node) {
console.warn(
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` with major.minor.patch, but only major Node.js Version can be selected. Learn More: http://vercel.link/node-version`
);
} else if (
validRange(node) &&
intersects(`${latestVersion.major + 1}.x`, node)
) {
} else if (validRange(node) && intersects(`${latest.major + 1}.x`, node)) {
console.warn(
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version`
);
}
nodeVersion = node;
isAuto = false;
}
return supportedNodeVersion;
return getSupportedNodeVersion(nodeVersion, isAuto, availableVersions);
}
export async function scanParentDirs(
@@ -426,8 +413,9 @@ export async function runNpmInstall(
try {
await runNpmInstallSema.acquire();
const { cliType, packageJsonPath, packageJson, lockfileVersion } =
await scanParentDirs(destPath, true);
const { cliType, packageJsonPath, lockfileVersion } = await scanParentDirs(
destPath
);
if (!packageJsonPath) {
debug(
@@ -462,7 +450,6 @@ export async function runNpmInstall(
opts.env = getEnvForPackageManager({
cliType,
lockfileVersion,
packageJsonPackageManager: packageJson?.packageManager,
nodeVersion,
env,
});
@@ -547,217 +534,64 @@ export async function runNpmInstall(
export function getEnvForPackageManager({
cliType,
lockfileVersion,
packageJsonPackageManager,
nodeVersion,
env,
}: {
cliType: CliType;
lockfileVersion: number | undefined;
packageJsonPackageManager?: string | undefined;
nodeVersion: NodeVersion | undefined;
env: { [x: string]: string | undefined };
}) {
const corepackFlagged = env.ENABLE_EXPERIMENTAL_COREPACK === '1';
const corepackEnabled = corepackFlagged && Boolean(packageJsonPackageManager);
const {
detectedLockfile,
detectedPackageManager,
path: newPath,
} = getPathOverrideForPackageManager({
yarnNodeLinker,
} = getPathForPackageManager({
cliType,
lockfileVersion,
corepackEnabled,
nodeVersion,
env,
});
if (corepackEnabled) {
debug(
`Detected corepack use for "${packageJsonPackageManager}". Not overriding package manager version.`
);
} else {
debug(
`Detected ${detectedPackageManager}. Added "${newPath}" to path. Based on assumed package manager "${cliType}", lockfile "${detectedLockfile}", and lockfileVersion "${lockfileVersion}"`
);
}
const newEnv: { [x: string]: string | undefined } = {
...env,
};
const alreadyInPath = (newPath: string) => {
const oldPath = env.PATH ?? '';
return oldPath.split(path.delimiter).includes(newPath);
};
if (newPath && !alreadyInPath(newPath)) {
if (newPath) {
// Ensure that the binaries of the detected package manager are at the
// beginning of the `$PATH`.
const oldPath = env.PATH + '';
newEnv.PATH = `${newPath}${path.delimiter}${oldPath}`;
if (detectedLockfile && detectedPackageManager) {
// For pnpm we also show the version of the lockfile we found
const versionString =
cliType === 'pnpm' ? `version ${lockfileVersion} ` : '';
console.log(
`Detected \`${detectedLockfile}\` ${versionString}generated by ${detectedPackageManager}`
);
if (cliType === 'bun') {
console.warn(
'Warning: Bun is used as a package manager at build time only, not at runtime with Functions'
);
}
}
}
if (cliType === 'yarn' && !env.YARN_NODE_LINKER) {
newEnv.YARN_NODE_LINKER = 'node-modules';
if (yarnNodeLinker) {
newEnv.YARN_NODE_LINKER = yarnNodeLinker;
}
if (detectedLockfile && detectedPackageManager) {
// For pnpm we also show the version of the lockfile we found
const versionString =
cliType === 'pnpm' ? `version ${lockfileVersion} ` : '';
console.log(
`Detected \`${detectedLockfile}\` ${versionString}generated by ${detectedPackageManager}`
);
if (cliType === 'bun') {
console.warn(
'Warning: Bun is used as a package manager at build time only, not at runtime with Functions'
);
}
}
return newEnv;
}
type DetectedPnpmVersion =
| 'not found'
| 'pnpm 6'
| 'pnpm 7'
| 'pnpm 8'
| 'pnpm 9'
| 'corepack_enabled';
function detectPnpmVersion(
lockfileVersion: number | undefined,
corepackEnabled: boolean
): DetectedPnpmVersion {
switch (true) {
case corepackEnabled:
return 'corepack_enabled';
case lockfileVersion === undefined:
return 'not found';
case lockfileVersion === 5.3:
return 'pnpm 6';
case lockfileVersion === 5.4:
return 'pnpm 7';
case lockfileVersion === 6.0 || lockfileVersion === 6.1:
return 'pnpm 8';
case lockfileVersion === 7.0 || lockfileVersion === 9.0:
return 'pnpm 9';
default:
return 'not found';
}
}
function shouldUseNpm7(
lockfileVersion: number | undefined,
nodeVersion: NodeVersion | undefined
): boolean {
if (lockfileVersion === undefined) return false;
return lockfileVersion >= 2 && (nodeVersion?.major || 0) < 16;
}
/**
* Helper to get the binary paths that link to the used package manager.
* Note: Make sure it doesn't contain any `console.log` calls.
*/
export function getPathOverrideForPackageManager({
cliType,
lockfileVersion,
corepackEnabled,
nodeVersion,
}: {
cliType: CliType;
lockfileVersion: number | undefined;
corepackEnabled: boolean;
nodeVersion: NodeVersion | undefined;
}): {
/**
* Which lockfile was detected.
*/
detectedLockfile: string | undefined;
/**
* Detected package manager that generated the found lockfile.
*/
detectedPackageManager: string | undefined;
/**
* Value of $PATH that includes the binaries for the detected package manager.
* Undefined if no $PATH are necessary.
*/
path: string | undefined;
} {
const no_override = {
detectedLockfile: undefined,
detectedPackageManager: undefined,
path: undefined,
};
switch (cliType) {
case 'npm':
switch (true) {
case corepackEnabled:
return no_override;
case shouldUseNpm7(lockfileVersion, nodeVersion):
return {
path: '/node16/bin-npm7',
detectedLockfile: 'package-lock.json',
detectedPackageManager: 'npm 7+',
};
default:
return no_override;
}
case 'pnpm':
switch (detectPnpmVersion(lockfileVersion, corepackEnabled)) {
case 'corepack_enabled':
return no_override;
case 'pnpm 7':
// pnpm 7
return {
path: '/pnpm7/node_modules/.bin',
detectedLockfile: 'pnpm-lock.yaml',
detectedPackageManager: 'pnpm 7',
};
case 'pnpm 8':
// pnpm 8
return {
path: '/pnpm8/node_modules/.bin',
detectedLockfile: 'pnpm-lock.yaml',
detectedPackageManager: 'pnpm 8',
};
case 'pnpm 9':
// pnpm 9
return {
path: '/pnpm9/node_modules/.bin',
detectedLockfile: 'pnpm-lock.yaml',
detectedPackageManager: 'pnpm 9',
};
case 'pnpm 6':
default:
return no_override;
}
case 'bun':
switch (true) {
case corepackEnabled:
return no_override;
default:
// Bun 1
return {
path: '/bun1',
detectedLockfile: 'bun.lockb',
detectedPackageManager: 'Bun',
};
}
case 'yarn':
return no_override;
}
}
/**
* Helper to get the binary paths that link to the used package manager.
* Note: Make sure it doesn't contain any `console.log` calls.
* @deprecated use `getEnvForPackageManager` instead
*/
export function getPathForPackageManager({
cliType,
lockfileVersion,
@@ -788,36 +622,70 @@ export function getPathForPackageManager({
*/
yarnNodeLinker: string | undefined;
} {
// This is not the correct check for whether or not corepack is being used. For that, you'd have to check
// the package.json's `packageManager` property. However, this deprecated function is keeping it's old,
// broken behavior.
let detectedLockfile: string | undefined;
let detectedPackageManager: string | undefined;
let pathValue: string | undefined;
let yarnNodeLinker: string | undefined;
const oldPath = env.PATH + '';
const npm7 = '/node16/bin-npm7';
const pnpm7 = '/pnpm7/node_modules/.bin';
const pnpm8 = '/pnpm8/node_modules/.bin';
const bun1 = '/bun1';
const corepackEnabled = env.ENABLE_EXPERIMENTAL_COREPACK === '1';
const overrides = getPathOverrideForPackageManager({
cliType,
lockfileVersion,
corepackEnabled,
nodeVersion,
});
const alreadyInPath = (newPath: string) => {
const oldPath = env.PATH ?? '';
return oldPath.split(path.delimiter).includes(newPath);
};
switch (true) {
case cliType === 'yarn' && !env.YARN_NODE_LINKER:
return { ...overrides, yarnNodeLinker: 'node-modules' };
case alreadyInPath(overrides.path ?? ''):
return {
detectedLockfile: undefined,
detectedPackageManager: undefined,
path: undefined,
yarnNodeLinker: undefined,
};
default:
return { ...overrides, yarnNodeLinker: undefined };
if (cliType === 'npm') {
if (
typeof lockfileVersion === 'number' &&
lockfileVersion >= 2 &&
(nodeVersion?.major || 0) < 16 &&
!oldPath.includes(npm7) &&
!corepackEnabled
) {
// npm 7
pathValue = npm7;
detectedLockfile = 'package-lock.json';
detectedPackageManager = 'npm 7+';
}
} else if (cliType === 'pnpm') {
if (
typeof lockfileVersion === 'number' &&
lockfileVersion === 5.4 &&
!oldPath.includes(pnpm7) &&
!corepackEnabled
) {
// pnpm 7
pathValue = pnpm7;
detectedLockfile = 'pnpm-lock.yaml';
detectedPackageManager = 'pnpm 7';
} else if (
typeof lockfileVersion === 'number' &&
lockfileVersion >= 6 &&
!oldPath.includes(pnpm8) &&
!corepackEnabled
) {
// pnpm 8
pathValue = pnpm8;
detectedLockfile = 'pnpm-lock.yaml';
detectedPackageManager = 'pnpm 8';
}
} else if (cliType === 'bun') {
if (!oldPath.includes(bun1) && !corepackEnabled) {
// Bun 1
pathValue = bun1;
detectedLockfile = 'bun.lockb';
detectedPackageManager = 'Bun';
}
} else {
// Yarn v2 PnP mode may be activated, so force "node-modules" linker style
if (!env.YARN_NODE_LINKER) {
yarnNodeLinker = 'node-modules';
}
}
return {
detectedLockfile,
detectedPackageManager,
path: pathValue,
yarnNodeLinker,
};
}
export async function runCustomInstallCommand({
@@ -832,14 +700,10 @@ export async function runCustomInstallCommand({
spawnOpts?: SpawnOptions;
}) {
console.log(`Running "install" command: \`${installCommand}\`...`);
const { cliType, lockfileVersion, packageJson } = await scanParentDirs(
destPath,
true
);
const { cliType, lockfileVersion } = await scanParentDirs(destPath);
const env = getEnvForPackageManager({
cliType,
lockfileVersion,
packageJsonPackageManager: packageJson?.packageManager,
nodeVersion,
env: spawnOpts?.env || {},
});
@@ -877,7 +741,6 @@ export async function runPackageJsonScript(
env: getEnvForPackageManager({
cliType,
lockfileVersion,
packageJsonPackageManager: packageJson?.packageManager,
nodeVersion: undefined,
env: cloneEnv(process.env, spawnOpts?.env),
}),

View File

@@ -4,7 +4,7 @@
"probes": [
{
"path": "/",
"mustContain": "pnpm version: 7",
"mustContain": "pnpm version: 6",
"logMustContain": "pnpm run build"
}
]

View File

@@ -1,6 +1,5 @@
import { delimiter } from 'path';
import { getEnvForPackageManager, getPathForPackageManager } from '../src';
import { getPathOverrideForPackageManager } from '../src/fs/run-user-scripts';
describe('Test `getEnvForPackageManager()`', () => {
let consoleLogSpy: jest.SpyInstance;
@@ -35,7 +34,6 @@ describe('Test `getEnvForPackageManager()`', () => {
args: {
cliType: 'npm',
nodeVersion: { major: 14, range: '14.x', runtime: 'nodejs14.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 1,
env: {
FOO: 'bar',
@@ -52,7 +50,6 @@ describe('Test `getEnvForPackageManager()`', () => {
args: {
cliType: 'npm',
nodeVersion: { major: 14, range: '14.x', runtime: 'nodejs14.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 2,
env: {
FOO: 'bar',
@@ -71,7 +68,6 @@ describe('Test `getEnvForPackageManager()`', () => {
args: {
cliType: 'npm',
nodeVersion: { major: 14, range: '14.x', runtime: 'nodejs14.x' },
packageJsonPackageManager: 'pnpm@latest',
lockfileVersion: 2,
env: {
FOO: 'bar',
@@ -90,7 +86,6 @@ describe('Test `getEnvForPackageManager()`', () => {
args: {
cliType: 'npm',
nodeVersion: { major: 14, range: '14.x', runtime: 'nodejs14.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 2,
env: {
FOO: 'bar',
@@ -109,7 +104,6 @@ describe('Test `getEnvForPackageManager()`', () => {
args: {
cliType: 'npm',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 2,
env: {
FOO: 'bar',
@@ -128,7 +122,6 @@ describe('Test `getEnvForPackageManager()`', () => {
args: {
cliType: 'yarn',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 2,
env: {
FOO: 'bar',
@@ -146,7 +139,6 @@ describe('Test `getEnvForPackageManager()`', () => {
args: {
cliType: 'yarn',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 2,
env: {
FOO: 'bar',
@@ -165,7 +157,6 @@ describe('Test `getEnvForPackageManager()`', () => {
args: {
cliType: 'pnpm',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 5.4,
env: {
FOO: 'bar',
@@ -180,52 +171,11 @@ describe('Test `getEnvForPackageManager()`', () => {
'Detected `pnpm-lock.yaml` version 5.4 generated by pnpm 7',
consoleWarnOutput: null,
},
{
name: 'should set path if pnpm 8 is detected',
args: {
cliType: 'pnpm',
nodeVersion: { major: 18, range: '18.x', runtime: 'nodejs18.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 6.0,
env: {
FOO: 'bar',
PATH: 'foo',
},
},
want: {
FOO: 'bar',
PATH: `/pnpm8/node_modules/.bin${delimiter}foo`,
},
consoleLogOutput:
'Detected `pnpm-lock.yaml` version 6 generated by pnpm 8',
consoleWarnOutput: null,
},
{
name: 'should set path if pnpm 8 is detected',
args: {
cliType: 'pnpm',
nodeVersion: { major: 18, range: '18.x', runtime: 'nodejs18.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 9.0,
env: {
FOO: 'bar',
PATH: 'foo',
},
},
want: {
FOO: 'bar',
PATH: `/pnpm9/node_modules/.bin${delimiter}foo`,
},
consoleLogOutput:
'Detected `pnpm-lock.yaml` version 9 generated by pnpm 9',
consoleWarnOutput: null,
},
{
name: 'should set path if bun v1 is detected',
args: {
cliType: 'bun',
nodeVersion: { major: 18, range: '18.x', runtime: 'nodejs18.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 0,
env: {
FOO: 'bar',
@@ -245,7 +195,6 @@ describe('Test `getEnvForPackageManager()`', () => {
args: {
cliType: 'pnpm',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
packageJsonPackageManager: 'npm@latest',
lockfileVersion: 5.4,
env: {
FOO: 'bar',
@@ -264,7 +213,6 @@ describe('Test `getEnvForPackageManager()`', () => {
args: {
cliType: 'pnpm',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 5.4,
env: {
FOO: 'bar',
@@ -278,12 +226,27 @@ describe('Test `getEnvForPackageManager()`', () => {
consoleLogOutput: null,
consoleWarnOutput: null,
},
{
name: 'should not set path if pnpm 6 is detected',
args: {
cliType: 'pnpm',
nodeVersion: { major: 14, range: '14.x', runtime: 'nodejs14.x' },
lockfileVersion: 5.3,
env: {
FOO: 'bar',
},
},
want: {
FOO: 'bar',
},
consoleLogOutput: null,
consoleWarnOutput: null,
},
])('$name', ({ args, want, consoleLogOutput, consoleWarnOutput }) => {
expect(
getEnvForPackageManager({
cliType: args.cliType,
lockfileVersion: args.lockfileVersion,
packageJsonPackageManager: args.packageJsonPackageManager,
nodeVersion: args.nodeVersion,
env: args.env,
})
@@ -305,209 +268,10 @@ describe('Test `getEnvForPackageManager()`', () => {
});
});
describe('Test `getPathOverrideForPackageManager()`', () => {
test.each<{
name: string;
args: Parameters<typeof getEnvForPackageManager>[0];
want: unknown;
}>([
{
name: 'should do nothing to env for npm < 6 and node < 16',
args: {
cliType: 'npm',
nodeVersion: { major: 14, range: '14.x', runtime: 'nodejs14.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 1,
env: {
FOO: 'bar',
},
},
want: {
detectedLockfile: undefined,
detectedPackageManager: undefined,
path: undefined,
},
},
{
name: 'should set path if npm 7+ is detected and node < 16',
args: {
cliType: 'npm',
nodeVersion: { major: 14, range: '14.x', runtime: 'nodejs14.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 2,
env: {
FOO: 'bar',
PATH: 'foo',
},
},
want: {
detectedLockfile: 'package-lock.json',
detectedPackageManager: 'npm 7+',
path: '/node16/bin-npm7',
},
},
{
name: 'should not set npm path if corepack enabled',
args: {
cliType: 'npm',
nodeVersion: { major: 14, range: '14.x', runtime: 'nodejs14.x' },
packageJsonPackageManager: 'pnpm@latest',
lockfileVersion: 2,
env: {
FOO: 'bar',
ENABLE_EXPERIMENTAL_COREPACK: '1',
},
},
want: {
detectedLockfile: undefined,
detectedPackageManager: undefined,
path: undefined,
},
},
{
name: 'should not set path if node is 16 and npm 7+ is detected',
args: {
cliType: 'npm',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 2,
env: {
FOO: 'bar',
PATH: 'foo',
},
},
want: {
detectedLockfile: undefined,
detectedPackageManager: undefined,
path: undefined,
},
},
{
name: 'should not set path if pnpm 6 is detected',
args: {
cliType: 'pnpm',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 5.3, // detects as pnpm@6, which is the default
env: {
FOO: 'bar',
PATH: 'foo',
},
},
want: {
detectedLockfile: undefined,
detectedPackageManager: undefined,
path: undefined,
},
},
{
name: 'should set path if pnpm 7+ is detected',
args: {
cliType: 'pnpm',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 5.4,
env: {
FOO: 'bar',
PATH: 'foo',
},
},
want: {
detectedLockfile: 'pnpm-lock.yaml',
detectedPackageManager: 'pnpm 7',
path: '/pnpm7/node_modules/.bin',
},
},
{
name: 'should set path if pnpm 8 is detected',
args: {
cliType: 'pnpm',
nodeVersion: { major: 18, range: '18.x', runtime: 'nodejs18.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 6.1,
env: {
FOO: 'bar',
PATH: 'foo',
},
},
want: {
detectedLockfile: 'pnpm-lock.yaml',
detectedPackageManager: 'pnpm 8',
path: '/pnpm8/node_modules/.bin',
},
},
{
name: 'should set path if pnpm 9 is detected',
args: {
cliType: 'pnpm',
nodeVersion: { major: 18, range: '18.x', runtime: 'nodejs18.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 7.0,
env: {
FOO: 'bar',
PATH: 'foo',
},
},
want: {
detectedLockfile: 'pnpm-lock.yaml',
detectedPackageManager: 'pnpm 9',
path: '/pnpm9/node_modules/.bin',
},
},
{
name: 'should set path if bun v1 is detected',
args: {
cliType: 'bun',
nodeVersion: { major: 18, range: '18.x', runtime: 'nodejs18.x' },
packageJsonPackageManager: undefined,
lockfileVersion: 0,
env: {
FOO: 'bar',
PATH: '/usr/local/bin',
},
},
want: {
detectedLockfile: 'bun.lockb',
detectedPackageManager: 'Bun',
path: '/bun1',
},
},
{
name: 'should not set pnpm path if corepack is enabled',
args: {
cliType: 'pnpm',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
packageJsonPackageManager: 'npm@latest',
lockfileVersion: 5.4,
env: {
FOO: 'bar',
ENABLE_EXPERIMENTAL_COREPACK: '1',
},
},
want: {
detectedLockfile: undefined,
detectedPackageManager: undefined,
path: undefined,
},
},
])('$name', ({ args, want }) => {
expect(
getPathOverrideForPackageManager({
cliType: args.cliType,
lockfileVersion: args.lockfileVersion,
// naive assumption that enabling corepack as a feature means it's used, but this is fine for tests
corepackEnabled: Boolean(args.env.ENABLE_EXPERIMENTAL_COREPACK),
nodeVersion: args.nodeVersion,
})
).toStrictEqual(want);
});
});
describe('Test `getPathForPackageManager()`', () => {
test.each<{
name: string;
args: Parameters<typeof getPathForPackageManager>[0];
args: Parameters<typeof getEnvForPackageManager>[0];
want: unknown;
}>([
{
@@ -706,6 +470,23 @@ describe('Test `getPathForPackageManager()`', () => {
yarnNodeLinker: undefined,
},
},
{
name: 'should not set path if pnpm 6 is detected',
args: {
cliType: 'pnpm',
nodeVersion: { major: 14, range: '14.x', runtime: 'nodejs14.x' },
lockfileVersion: 5.3,
env: {
FOO: 'bar',
},
},
want: {
detectedLockfile: undefined,
detectedPackageManager: undefined,
path: undefined,
yarnNodeLinker: undefined,
},
},
])('$name', ({ args, want }) => {
expect(
getPathForPackageManager({

View File

@@ -221,21 +221,6 @@ it('should warn when package.json engines is greater than', async () => {
]);
});
it('should warn when project settings gets overrided', async () => {
expect(
await getNodeVersion(
path.join(__dirname, 'pkg-engine-node-greaterthan'),
undefined,
{ nodeVersion: '16.x' },
{}
)
).toHaveProperty('range', '20.x');
expect(warningMessages).toStrictEqual([
'Warning: Due to "engines": { "node": ">=16" } in your `package.json` file, the Node.js Version defined in your Project Settings ("16.x") will not apply. Learn More: http://vercel.link/node-version',
'Warning: Detected "engines": { "node": ">=16" } in your `package.json` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version',
]);
});
it('should not warn when package.json engines matches project setting from config', async () => {
expect(
await getNodeVersion(

View File

@@ -1,97 +1,5 @@
# vercel
## 34.2.0
### Minor Changes
- Stop sending system environment variables in dev ([#11526](https://github.com/vercel/vercel/pull/11526))
### Patch Changes
- Updated dependencies [[`d3c1267e2`](https://github.com/vercel/vercel/commit/d3c1267e24082789ea6382cf6af81dd40df288ff), [`ccd7eb1fb`](https://github.com/vercel/vercel/commit/ccd7eb1fb78f7ac9effdbe1935de3bda82c97fe3)]:
- @vercel/build-utils@8.2.0
- @vercel/node@3.1.5
- @vercel/static-build@2.5.9
## 34.1.14
### Patch Changes
- Updated dependencies [[`ad6945435`](https://github.com/vercel/vercel/commit/ad69454352b519b2b0ed326f245c779530554bf2)]:
- @vercel/build-utils@8.1.3
- @vercel/node@3.1.4
- @vercel/static-build@2.5.8
## 34.1.13
### Patch Changes
- Updated dependencies [[`1682ad43d`](https://github.com/vercel/vercel/commit/1682ad43d0064b22b1248a7e946746b838f00076)]:
- @vercel/build-utils@8.1.2
- @vercel/node@3.1.3
- @vercel/static-build@2.5.7
## 34.1.12
### Patch Changes
- Updated dependencies [[`67afc2608`](https://github.com/vercel/vercel/commit/67afc26085e2ebdaa33a8cbce112afec4cb1c4f5), [`2f7a6ed5f`](https://github.com/vercel/vercel/commit/2f7a6ed5f92d454000f92247d3b6548e2064f4e6)]:
- @vercel/next@4.2.11
- @vercel/build-utils@8.1.1
- @vercel/node@3.1.2
- @vercel/static-build@2.5.6
## 34.1.11
### Patch Changes
- Updated dependencies [[`5014b1e82`](https://github.com/vercel/vercel/commit/5014b1e82a46181baeb727ffe6d14000b6a4b1d7), [`18d1703d5`](https://github.com/vercel/vercel/commit/18d1703d5b4f2adc61fd56011f874c393fa57d0d), [`e87d4c14d`](https://github.com/vercel/vercel/commit/e87d4c14d0e718281f9ba91c9ec1cc6e142d383d), [`bc5fd4115`](https://github.com/vercel/vercel/commit/bc5fd41158ec9f36e5db1fe749589adcbaec6950)]:
- @vercel/build-utils@8.1.0
- @vercel/next@4.2.10
- @vercel/redwood@2.0.9
- @vercel/remix-builder@2.1.6
- @vercel/node@3.1.1
- @vercel/static-build@2.5.5
## 34.1.10
### Patch Changes
- Updated dependencies [[`119f80e96`](https://github.com/vercel/vercel/commit/119f80e9611a7a5a755aa689502dcdab323194aa), [`11584b0e9`](https://github.com/vercel/vercel/commit/11584b0e9b55f312f34d0d6467ab498e472ac9df), [`3023122d4`](https://github.com/vercel/vercel/commit/3023122d4e0dd292340d9e9e61ef232baf6e610d), [`0e774b6be`](https://github.com/vercel/vercel/commit/0e774b6be0c832213a64124e1f4fc6d150e87d9f)]:
- @vercel/next@4.2.9
- @vercel/static-build@2.5.4
## 34.1.9
### Patch Changes
- Updated dependencies [[`5a532a5b9`](https://github.com/vercel/vercel/commit/5a532a5b948994ba04783ac560357eed9f94a3f3), [`50fc27ba5`](https://github.com/vercel/vercel/commit/50fc27ba5773870956300bbbaffbe387d549bc12), [`c1d852295`](https://github.com/vercel/vercel/commit/c1d85229509dd319a1f11beb940a759113564d33), [`a5ea04154`](https://github.com/vercel/vercel/commit/a5ea04154ba26ee4e635d8953aa4f0d9d82d3a96)]:
- @vercel/next@4.2.8
- @vercel/node@3.1.0
## 34.1.8
### Patch Changes
- Updated dependencies [[`52e435aa5`](https://github.com/vercel/vercel/commit/52e435aa5d7b4014d19477969ad5cbfbe94aa76f), [`124846a3e`](https://github.com/vercel/vercel/commit/124846a3e65a3bf1ae82327fd4ba2b132674fb39), [`dc974b679`](https://github.com/vercel/vercel/commit/dc974b6797de0b6e90373c92e1f2bbdafcfc6687), [`58c6755e0`](https://github.com/vercel/vercel/commit/58c6755e0c12cae2ce55978b7bf8722133151196)]:
- @vercel/next@4.2.7
- @vercel/static-build@2.5.3
## 34.1.7
### Patch Changes
- Updated dependencies [[`3420ba015`](https://github.com/vercel/vercel/commit/3420ba0153dcabffef7114ba2361fb0f3c43a7b3)]:
- @vercel/next@4.2.6
## 34.1.6
### Patch Changes
- Updated dependencies [[`b1adaf76e`](https://github.com/vercel/vercel/commit/b1adaf76ec17d1bbfe30a2bf65405bd886fa9bcf), [`3fb97d1d2`](https://github.com/vercel/vercel/commit/3fb97d1d270e835ce34a687bd234ea53dfe446a2)]:
- @vercel/next@4.2.5
- @vercel/static-build@2.5.2
## 34.1.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "34.2.0",
"version": "34.1.5",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -32,17 +32,17 @@
"node": ">= 16"
},
"dependencies": {
"@vercel/build-utils": "8.2.0",
"@vercel/build-utils": "8.0.0",
"@vercel/fun": "1.1.0",
"@vercel/go": "3.1.1",
"@vercel/hydrogen": "1.0.2",
"@vercel/next": "4.2.11",
"@vercel/node": "3.1.5",
"@vercel/next": "4.2.4",
"@vercel/node": "3.0.28",
"@vercel/python": "4.2.0",
"@vercel/redwood": "2.0.9",
"@vercel/remix-builder": "2.1.6",
"@vercel/redwood": "2.0.8",
"@vercel/remix-builder": "2.1.5",
"@vercel/ruby": "2.1.0",
"@vercel/static-build": "2.5.9",
"@vercel/static-build": "2.5.1",
"chokidar": "3.3.1"
},
"devDependencies": {
@@ -92,8 +92,8 @@
"@types/yauzl-promise": "2.1.0",
"@vercel-internals/constants": "1.0.4",
"@vercel-internals/get-package-json": "1.0.0",
"@vercel-internals/types": "1.0.36",
"@vercel/client": "13.2.7",
"@vercel-internals/types": "1.0.31",
"@vercel/client": "13.2.2",
"@vercel/error-utils": "2.0.2",
"@vercel/frameworks": "3.0.2",
"@vercel/fs-detectors": "5.2.3",

View File

@@ -10,7 +10,7 @@ export const buildCommand: Command = {
name: 'prod',
description: 'Build a production deployment',
shorthand: null,
type: Boolean,
type: String,
deprecated: false,
},
{

View File

@@ -43,7 +43,7 @@ import type { VercelConfig } from '@vercel/client';
import pull from '../pull';
import { staticFiles as getFiles } from '../../util/get-files';
import Client from '../../util/client';
import { parseArguments } from '../../util/get-args';
import getArgs from '../../util/get-args';
import cmd from '../../util/output/cmd';
import * as cli from '../../util/pkg-name';
import cliPkg from '../../util/pkg';
@@ -66,13 +66,12 @@ import {
import { importBuilders } from '../../util/build/import-builders';
import { initCorepack, cleanupCorepack } from '../../util/build/corepack';
import { sortBuilders } from '../../util/build/sort-builders';
import { handleError, toEnumerableError } from '../../util/error';
import { toEnumerableError } from '../../util/error';
import { validateConfig } from '../../util/validate-config';
import { setMonorepoDefaultSettings } from '../../util/build/monorepo';
import { help } from '../help';
import { buildCommand } from './command';
import { scrubArgv } from '../../util/build/scrub-argv';
import { getFlagsSpecification } from '../../util/get-flags-specification';
type BuildResult = BuildResultV2 | BuildResultV3;
@@ -134,26 +133,22 @@ export default async function main(client: Client): Promise<number> {
process.env.__VERCEL_BUILD_RUNNING = '1';
}
let parsedArgs = null;
const flagsSpecification = getFlagsSpecification(buildCommand.options);
// Parse CLI args
try {
parsedArgs = parseArguments(client.argv.slice(2), flagsSpecification);
} catch (error) {
handleError(error);
return 1;
}
const argv = getArgs(client.argv.slice(2), {
'--output': String,
'--prod': Boolean,
'--yes': Boolean,
'-y': '--yes',
});
if (parsedArgs.flags['--help']) {
if (argv['--help']) {
output.print(help(buildCommand, { columns: client.stderr.columns }));
return 2;
}
// Build `target` influences which environment variables will be used
const target = parsedArgs.flags['--prod'] ? 'production' : 'preview';
const yes = Boolean(parsedArgs.flags['--yes']);
const target = argv['--prod'] ? 'production' : 'preview';
const yes = Boolean(argv['--yes']);
try {
await validateNpmrc(cwd);
@@ -218,8 +213,8 @@ export default async function main(client: Client): Promise<number> {
// Delete output directory from potential previous build
const defaultOutputDir = join(cwd, projectRootDirectory, OUTPUT_DIR);
const outputDir = parsedArgs.flags['--output']
? resolve(parsedArgs.flags['--output'])
const outputDir = argv['--output']
? resolve(argv['--output'])
: defaultOutputDir;
await Promise.all([
fs.remove(outputDir),

View File

@@ -70,7 +70,7 @@ export async function pullEnvRecords(
);
const query = new URLSearchParams();
let url = `/v2/env/pull/${projectId}`;
let url = `/v1/env/pull/${projectId}`;
if (target) {
url += `/${encodeURIComponent(target)}`;

View File

@@ -917,9 +917,9 @@ test('Deploy `api-env` fixture and test `vercel env` command', async () => {
expect(homeJson['MY_STDIN_VAR']).toBe('{"expect":"quotes"}');
expect(homeJson['MY_DECRYPTABLE_SECRET_ENV']).toBe('decryptable value');
// system env vars are hidden in dev
expect(apiJson['VERCEL']).toBeUndefined();
expect(homeJson['VERCEL']).toBeUndefined();
// system env vars are automatically exposed
expect(apiJson['VERCEL']).toBe('1');
expect(homeJson['VERCEL']).toBe('1');
// sleep before kill, otherwise the dev process doesn't clean up and exit properly
await sleep(100);
@@ -949,7 +949,7 @@ test('Deploy `api-env` fixture and test `vercel env` command', async () => {
async function vcEnvPullFetchSystemVars() {
const { exitCode, stdout, stderr } = await execCli(
binaryPath,
['env', 'pull', '-y', '--environment', 'production'],
['env', 'pull', '-y'],
{
cwd: target,
}
@@ -963,7 +963,7 @@ test('Deploy `api-env` fixture and test `vercel env` command', async () => {
expect(lines).toContain('VERCEL="1"');
expect(lines).toContain('VERCEL_URL=""');
expect(lines).toContain('VERCEL_ENV="production"');
expect(lines).toContain('VERCEL_ENV="development"');
expect(lines).toContain('VERCEL_GIT_PROVIDER=""');
expect(lines).toContain('VERCEL_GIT_REPO_SLUG=""');
}
@@ -980,24 +980,22 @@ test('Deploy `api-env` fixture and test `vercel env` command', async () => {
const localhostNoProtocol = localhost[0].slice('http://'.length);
const apiJson = await apiRes.json();
// environment variables are not set in dev
expect(apiJson['VERCEL']).toBeUndefined();
expect(apiJson['VERCEL_ENV']).toBeUndefined();
expect(apiJson['VERCEL_GIT_PROVIDER']).toBeUndefined();
expect(apiJson['VERCEL_GIT_REPO_SLUG']).toBeUndefined();
// except for these because vc dev
expect(apiJson['VERCEL']).toBe('1');
expect(apiJson['VERCEL_URL']).toBe(localhostNoProtocol);
expect(apiJson['VERCEL_ENV']).toBe('development');
expect(apiJson['VERCEL_REGION']).toBe('dev1');
expect(apiJson['VERCEL_GIT_PROVIDER']).toBe('');
expect(apiJson['VERCEL_GIT_REPO_SLUG']).toBe('');
const homeUrl = localhost[0];
const homeRes = await fetch(homeUrl);
const homeJson = await homeRes.json();
expect(homeJson['VERCEL']).toBeUndefined();
expect(homeJson['VERCEL']).toBe('1');
expect(homeJson['VERCEL_URL']).toBe(localhostNoProtocol);
expect(homeJson['VERCEL_ENV']).toBeUndefined();
expect(homeJson['VERCEL_REGION']).toBeUndefined();
expect(homeJson['VERCEL_GIT_PROVIDER']).toBeUndefined();
expect(homeJson['VERCEL_GIT_REPO_SLUG']).toBeUndefined();
expect(homeJson['VERCEL_ENV']).toBe('development');
expect(homeJson['VERCEL_REGION']).toBe(undefined);
expect(homeJson['VERCEL_GIT_PROVIDER']).toBe('');
expect(homeJson['VERCEL_GIT_REPO_SLUG']).toBe('');
// sleep before kill, otherwise the dev process doesn't clean up and exit properly
await sleep(100);

View File

@@ -1255,7 +1255,7 @@ test('fail to deploy a Lambda with an incorrect value for maxDuration', async ()
expect(output.exitCode, formatOutput(output)).toBe(1);
expect(output.stderr).toMatch(
/maxDuration must be between \d+ second and \d+ seconds/gm
/maxDuration must be between 1 second and 10 seconds/gm
);
});

View File

@@ -216,7 +216,7 @@ export function useProject(
res.json(project);
});
client.scenario.get(
`/v2/env/pull/${project.id}/:target?/:gitBranch?`,
`/v1/env/pull/${project.id}/:target?/:gitBranch?`,
(req, res) => {
const target =
typeof req.params.target === 'string'
@@ -390,7 +390,7 @@ function exposeSystemEnvs(
) {
const envs: Env = {};
if (autoExposeSystemEnvs && target !== 'development') {
if (autoExposeSystemEnvs) {
envs['VERCEL'] = '1';
envs['VERCEL_ENV'] = target || 'development';

View File

@@ -195,41 +195,6 @@ describe('env', () => {
});
it('should expose production system env variables', async () => {
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
id: 'vercel-env-pull',
name: 'vercel-env-pull',
autoExposeSystemEnvs: true,
});
const cwd = setupUnitFixture('vercel-env-pull');
client.cwd = cwd;
client.setArgv(
'env',
'pull',
'other.env',
'--yes',
'--environment',
'production'
);
const exitCodePromise = env(client);
await expect(client.stderr).toOutput(
'Downloading `production` Environment Variables for Project vercel-env-pull'
);
await expect(client.stderr).toOutput('Created other.env file');
await expect(client.stderr).not.toOutput('and added it to .gitignore');
await expect(exitCodePromise).resolves.toEqual(0);
const rawDevEnv = await fs.readFile(path.join(cwd, 'other.env'));
const productionFileHasVercelEnv = rawDevEnv
.toString()
.includes('VERCEL_ENV="production"');
expect(productionFileHasVercelEnv).toBeTruthy();
});
it('should not expose system env variables in dev', async () => {
useUser();
useTeams('team_dummy');
useProject({
@@ -249,18 +214,12 @@ describe('env', () => {
await expect(client.stderr).not.toOutput('and added it to .gitignore');
await expect(exitCodePromise).resolves.toEqual(0);
const devEnv = (
await fs.readFile(path.join(cwd, 'other.env'))
).toString();
const rawDevEnv = await fs.readFile(path.join(cwd, 'other.env'));
const devFileHasVercelEnv = [
'VERCEL',
'VERCEL_ENV',
'VERCEL_URL',
'VERCEL_REGION',
'VERCEL_DEPLOYMENT_ID',
].some(envVar => devEnv.includes(envVar));
expect(devFileHasVercelEnv).toBeFalsy();
const productionFileHasVercelEnv = rawDevEnv
.toString()
.includes('VERCEL_ENV="development"');
expect(productionFileHasVercelEnv).toBeTruthy();
});
it('should show a delta string', async () => {

View File

@@ -1,40 +1,5 @@
# @vercel/client
## 13.2.7
### Patch Changes
- Updated dependencies [[`d3c1267e2`](https://github.com/vercel/vercel/commit/d3c1267e24082789ea6382cf6af81dd40df288ff), [`ccd7eb1fb`](https://github.com/vercel/vercel/commit/ccd7eb1fb78f7ac9effdbe1935de3bda82c97fe3)]:
- @vercel/build-utils@8.2.0
## 13.2.6
### Patch Changes
- Updated dependencies [[`ad6945435`](https://github.com/vercel/vercel/commit/ad69454352b519b2b0ed326f245c779530554bf2)]:
- @vercel/build-utils@8.1.3
## 13.2.5
### Patch Changes
- Updated dependencies [[`1682ad43d`](https://github.com/vercel/vercel/commit/1682ad43d0064b22b1248a7e946746b838f00076)]:
- @vercel/build-utils@8.1.2
## 13.2.4
### Patch Changes
- Updated dependencies [[`2f7a6ed5f`](https://github.com/vercel/vercel/commit/2f7a6ed5f92d454000f92247d3b6548e2064f4e6)]:
- @vercel/build-utils@8.1.1
## 13.2.3
### Patch Changes
- Updated dependencies [[`5014b1e82`](https://github.com/vercel/vercel/commit/5014b1e82a46181baeb727ffe6d14000b6a4b1d7)]:
- @vercel/build-utils@8.1.0
## 13.2.2
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "13.2.7",
"version": "13.2.2",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -37,7 +37,7 @@
"typescript": "4.9.5"
},
"dependencies": {
"@vercel/build-utils": "8.2.0",
"@vercel/build-utils": "8.0.0",
"@vercel/error-utils": "2.0.2",
"@vercel/routing-utils": "3.1.0",
"@zeit/fetch": "5.2.0",

View File

@@ -37,7 +37,7 @@
"@types/minimatch": "3.0.5",
"@types/node": "14.18.33",
"@types/semver": "7.3.10",
"@vercel/build-utils": "8.2.0",
"@vercel/build-utils": "8.0.0",
"jest-junit": "16.0.0",
"typescript": "4.9.5"
}

View File

@@ -1,19 +0,0 @@
# @vercel/functions
## 1.0.2
### Patch Changes
- Convert package to CommonJS ([#11569](https://github.com/vercel/vercel/pull/11569))
## 1.0.1
### Patch Changes
- Don't throw error if context is missing ([`0817527f9`](https://github.com/vercel/vercel/commit/0817527f9e9d0d5fceb73f21e695089349a96d3e))
## 1.0.0
### Major Changes
- Initial release ([#11553](https://github.com/vercel/vercel/pull/11553))

View File

@@ -1,17 +0,0 @@
/**
* Extends the lifetime of the request handler for the lifetime of the given {@link Promise}
* @see https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil
*
* @param promise The promise to wait for.
* @example
*
* ```
* import { waitUntil } from '@vercel/functions';
*
* export function GET(request) {
* waitUntil(fetch('https://vercel.com'));
* return new Response('OK');
* }
* ```
*/
export function waitUntil(promise: Promise<unknown>): void;

View File

@@ -1,16 +1,5 @@
/* global globalThis */
exports.waitUntil = promise => {
if (
promise === null ||
typeof promise !== 'object' ||
typeof promise.then !== 'function'
) {
throw new TypeError(
`waitUntil can only be called with a Promise, got ${typeof promise}`
);
}
const ctx = globalThis[Symbol.for('@vercel/request-context')]?.get?.() ?? {};
ctx.waitUntil?.(promise);
export const waitUntil = promise => {
const context = globalThis[Symbol.for('@vercel/request-context')].get();
return context.waitUntil(promise);
};

View File

@@ -1,42 +0,0 @@
/* global globalThis */
import { vi, expect, test } from 'vitest';
import { waitUntil } from '.';
test.each([
{},
() => {},
function () {},
NaN,
1,
false,
undefined,
null,
[],
'▲',
])('waitUntil throws when called with %s', input => {
expect(() => waitUntil(input)).toThrow(TypeError);
expect(() => waitUntil(input)).toThrow(
`waitUntil can only be called with a Promise, got ${typeof input}`
);
});
test.each([null, undefined, {}])(
'waitUntil does not throw an error when context is %s',
input => {
const promise = Promise.resolve();
globalThis[Symbol.for('@vercel/request-context')] = input;
expect(() => waitUntil(promise)).not.toThrow();
}
);
test('waitUntil calls ctx.waitUntil when available', async () => {
const promise = Promise.resolve();
const waitUntilMock = vi.fn().mockReturnValue(promise);
globalThis[Symbol.for('@vercel/request-context')] = {
get: () => ({ waitUntil: waitUntilMock }),
};
waitUntil(promise);
expect(waitUntilMock).toHaveBeenCalledWith(promise);
});

View File

@@ -1,33 +1,3 @@
{
"name": "@vercel/functions",
"description": "Runtime functions to be used with your Vercel Functions",
"homepage": "https://vercel.com",
"version": "1.0.2",
"types": "index.d.ts",
"main": "index.js",
"repository": {
"directory": "packages/functions",
"type": "git",
"url": "git+https://github.com/vercel/vercel.git"
},
"bugs": {
"url": "https://github.com/vercel/vercel/issues"
},
"devDependencies": {
"vitest": "1.3.1"
},
"engines": {
"node": ">= 16"
},
"files": [
"index.d.ts",
"index.js"
],
"scripts": {
"test": "vitest"
},
"license": "Apache-2.0",
"publishConfig": {
"access": "public"
}
"name": "@vercel/functions"
}

View File

@@ -1,40 +1,5 @@
# @vercel/gatsby-plugin-vercel-builder
## 2.0.31
### Patch Changes
- Updated dependencies [[`d3c1267e2`](https://github.com/vercel/vercel/commit/d3c1267e24082789ea6382cf6af81dd40df288ff), [`ccd7eb1fb`](https://github.com/vercel/vercel/commit/ccd7eb1fb78f7ac9effdbe1935de3bda82c97fe3)]:
- @vercel/build-utils@8.2.0
## 2.0.30
### Patch Changes
- Updated dependencies [[`ad6945435`](https://github.com/vercel/vercel/commit/ad69454352b519b2b0ed326f245c779530554bf2)]:
- @vercel/build-utils@8.1.3
## 2.0.29
### Patch Changes
- Updated dependencies [[`1682ad43d`](https://github.com/vercel/vercel/commit/1682ad43d0064b22b1248a7e946746b838f00076)]:
- @vercel/build-utils@8.1.2
## 2.0.28
### Patch Changes
- Updated dependencies [[`2f7a6ed5f`](https://github.com/vercel/vercel/commit/2f7a6ed5f92d454000f92247d3b6548e2064f4e6)]:
- @vercel/build-utils@8.1.1
## 2.0.27
### Patch Changes
- Updated dependencies [[`5014b1e82`](https://github.com/vercel/vercel/commit/5014b1e82a46181baeb727ffe6d14000b6a4b1d7)]:
- @vercel/build-utils@8.1.0
## 2.0.26
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/gatsby-plugin-vercel-builder",
"version": "2.0.31",
"version": "2.0.26",
"main": "dist/index.js",
"files": [
"dist",
@@ -20,7 +20,7 @@
},
"dependencies": {
"@sinclair/typebox": "0.25.24",
"@vercel/build-utils": "8.2.0",
"@vercel/build-utils": "8.0.0",
"@vercel/routing-utils": "3.1.0",
"esbuild": "0.14.47",
"etag": "1.8.1",

View File

@@ -29,7 +29,7 @@
"@types/node-fetch": "^2.3.0",
"@types/tar": "6.1.5",
"@types/yauzl-promise": "2.1.0",
"@vercel/build-utils": "8.2.0",
"@vercel/build-utils": "8.0.0",
"async-retry": "1.3.3",
"execa": "^1.0.0",
"fs-extra": "^7.0.0",

View File

@@ -26,7 +26,7 @@
"devDependencies": {
"@types/jest": "27.5.1",
"@types/node": "14.18.33",
"@vercel/build-utils": "8.2.0",
"@vercel/build-utils": "8.0.0",
"execa": "3.2.0",
"fs-extra": "11.1.0",
"jest-junit": "16.0.0"

View File

@@ -51,15 +51,11 @@ export const build: BuildV2 = async ({
);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
const { cliType, lockfileVersion, packageJson } = await scanParentDirs(
entrypointDir,
true
);
const { cliType, lockfileVersion } = await scanParentDirs(entrypointDir);
spawnOpts.env = getEnvForPackageManager({
cliType,
lockfileVersion,
packageJsonPackageManager: packageJson?.packageManager,
nodeVersion,
env: spawnOpts.env || {},
});

View File

@@ -1,61 +1,5 @@
# @vercel/next
## 4.2.11
### Patch Changes
- normalize source file locations for special metadata files ([#11579](https://github.com/vercel/vercel/pull/11579))
## 4.2.10
### Patch Changes
- skip action rewrites for RSC requests ([#11576](https://github.com/vercel/vercel/pull/11576))
- Bump `@vercel/nft@0.27.0` ([#11580](https://github.com/vercel/vercel/pull/11580))
## 4.2.9
### Patch Changes
- Support incremental partial prerendering ([#11560](https://github.com/vercel/vercel/pull/11560))
- ensure `.action` outputs are created for edge functions ([#11568](https://github.com/vercel/vercel/pull/11568))
- ([#11566](https://github.com/vercel/vercel/pull/11566))
## 4.2.8
### Patch Changes
- Fix missing initial RSC headers ([#11552](https://github.com/vercel/vercel/pull/11552))
- Remove .prefetch.rsc rewrites for non-PPR ([#11540](https://github.com/vercel/vercel/pull/11540))
- [next] rename middleware manifest env ([#11549](https://github.com/vercel/vercel/pull/11549))
## 4.2.7
### Patch Changes
- Fix missing .rsc outputs for pages prerenders ([#11503](https://github.com/vercel/vercel/pull/11503))
- Fix base path app index RSC handling ([#11528](https://github.com/vercel/vercel/pull/11528))
- Fix interception routes PPR route handling ([#11527](https://github.com/vercel/vercel/pull/11527))
## 4.2.6
### Patch Changes
- [next] Update test fixture version ([#11514](https://github.com/vercel/vercel/pull/11514))
## 4.2.5
### Patch Changes
- Only rewrite next-action requests to `.action` handlers ([#11504](https://github.com/vercel/vercel/pull/11504))
## 4.2.4
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "4.2.11",
"version": "4.2.4",
"license": "Apache-2.0",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -23,7 +23,7 @@
"dist"
],
"dependencies": {
"@vercel/nft": "0.27.0"
"@vercel/nft": "0.26.4"
},
"devDependencies": {
"@types/aws-lambda": "8.10.19",
@@ -40,7 +40,7 @@
"@types/semver": "6.0.0",
"@types/text-table": "0.2.1",
"@types/webpack-sources": "3.2.0",
"@vercel/build-utils": "8.2.0",
"@vercel/build-utils": "8.0.0",
"@vercel/routing-utils": "3.1.0",
"async-sema": "3.0.1",
"buffer-crc32": "0.2.13",

View File

@@ -257,15 +257,10 @@ export const build: BuildV2 = async ({
const nextVersionRange = await getNextVersionRange(entryPath);
const nodeVersion = await getNodeVersion(entryPath, undefined, config, meta);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
const { cliType, lockfileVersion, packageJson } = await scanParentDirs(
entryPath,
true
);
const { cliType, lockfileVersion } = await scanParentDirs(entryPath);
spawnOpts.env = getEnvForPackageManager({
cliType,
lockfileVersion,
packageJsonPackageManager: packageJson?.packageManager,
nodeVersion,
env: spawnOpts.env || {},
});
@@ -1933,7 +1928,6 @@ export const build: BuildV2 = async ({
bypassToken: prerenderManifest.bypassToken || '',
isServerMode,
experimentalPPRRoutes,
hasActionOutputSupport: false,
}).then(arr =>
localizeDynamicRoutes(
arr,
@@ -1964,7 +1958,6 @@ export const build: BuildV2 = async ({
bypassToken: prerenderManifest.bypassToken || '',
isServerMode,
experimentalPPRRoutes,
hasActionOutputSupport: false,
}).then(arr =>
arr.map(route => {
route.src = route.src.replace('^', `^${dynamicPrefix}`);

View File

@@ -51,7 +51,6 @@ import {
normalizePrefetches,
CreateLambdaFromPseudoLayersOptions,
getPostponeResumePathname,
LambdaGroup,
MAX_UNCOMPRESSED_LAMBDA_SIZE,
} from './utils';
import {
@@ -69,7 +68,6 @@ const CORRECT_NOT_FOUND_ROUTES_VERSION = 'v12.0.1';
const CORRECT_MIDDLEWARE_ORDER_VERSION = 'v12.1.7-canary.29';
const NEXT_DATA_MIDDLEWARE_RESOLVING_VERSION = 'v12.1.7-canary.33';
const EMPTY_ALLOW_QUERY_FOR_PRERENDERED_VERSION = 'v12.2.0';
const ACTION_OUTPUT_SUPPORT_VERSION = 'v14.2.2';
const CORRECTED_MANIFESTS_VERSION = 'v12.2.0';
// Ideally this should be in a Next.js manifest so we can change it in
@@ -201,9 +199,6 @@ export async function serverBuild({
nextVersion,
EMPTY_ALLOW_QUERY_FOR_PRERENDERED_VERSION
);
const hasActionOutputSupport =
semver.gte(nextVersion, ACTION_OUTPUT_SUPPORT_VERSION) &&
Boolean(process.env.NEXT_EXPERIMENTAL_STREAMING_ACTIONS);
const projectDir = requiredServerFilesManifest.relativeAppDir
? path.join(baseDir, requiredServerFilesManifest.relativeAppDir)
: requiredServerFilesManifest.appDir || entryPath;
@@ -218,9 +213,7 @@ export async function serverBuild({
}
const experimental = {
ppr:
requiredServerFilesManifest.config.experimental?.ppr === true ||
requiredServerFilesManifest.config.experimental?.ppr === 'incremental',
ppr: requiredServerFilesManifest.config.experimental?.ppr === true,
};
let appRscPrefetches: UnwrapPromise<ReturnType<typeof glob>> = {};
@@ -230,6 +223,8 @@ export async function serverBuild({
if (appPathRoutesManifest) {
appDir = path.join(pagesDir, '../app');
appBuildTraces = await glob('**/*.js.nft.json', appDir);
// TODO: maybe?
appRscPrefetches = experimental.ppr
? {}
: await glob(`**/*${RSC_PREFETCH_SUFFIX}`, appDir);
@@ -251,7 +246,7 @@ export async function serverBuild({
if (rewrite.src && rewrite.dest) {
rewrite.src = rewrite.src.replace(
/\/?\(\?:\/\)\?/,
`(?<rscsuff>${experimental.ppr ? '(\\.prefetch)?' : ''}\\.rsc)?(?:/)?`
'(?<rscsuff>(\\.prefetch)?\\.rsc)?(?:/)?'
);
let destQueryIndex = rewrite.dest.indexOf('?');
@@ -931,23 +926,11 @@ export async function serverBuild({
inversedAppPathManifest,
});
const appRouterStreamingActionLambdaGroups: LambdaGroup[] = [];
for (const group of appRouterLambdaGroups) {
if (!group.isPrerenders || group.isExperimentalPPR) {
group.isStreaming = true;
}
group.isAppRouter = true;
// We create a streaming variant of the Prerender lambda group
// to support actions that are part of a Prerender
if (hasActionOutputSupport) {
appRouterStreamingActionLambdaGroups.push({
...group,
isActionLambda: true,
isStreaming: true,
});
}
}
for (const group of appRouteHandlersLambdaGroups) {
@@ -999,13 +982,6 @@ export async function serverBuild({
pseudoLayerBytes: group.pseudoLayerBytes,
uncompressedLayerBytes: group.pseudoLayerUncompressedBytes,
})),
appRouterStreamingPrerenderLambdaGroups:
appRouterStreamingActionLambdaGroups.map(group => ({
pages: group.pages,
isPrerender: group.isPrerenders,
pseudoLayerBytes: group.pseudoLayerBytes,
uncompressedLayerBytes: group.pseudoLayerUncompressedBytes,
})),
appRouteHandlersLambdaGroups: appRouteHandlersLambdaGroups.map(
group => ({
pages: group.pages,
@@ -1023,7 +999,6 @@ export async function serverBuild({
const combinedGroups = [
...pageLambdaGroups,
...appRouterLambdaGroups,
...appRouterStreamingActionLambdaGroups,
...apiLambdaGroups,
...appRouteHandlersLambdaGroups,
];
@@ -1233,11 +1208,6 @@ export async function serverBuild({
let outputName = path.posix.join(entryDirectory, pageName);
if (group.isActionLambda) {
// give the streaming prerenders a .action suffix
outputName = `${outputName}.action`;
}
// If this is a PPR page, then we should prefix the output name.
if (isPPR) {
if (!revalidate) {
@@ -1408,7 +1378,6 @@ export async function serverBuild({
isServerMode: true,
dynamicMiddlewareRouteMap: middleware.dynamicRouteMap,
experimentalPPRRoutes,
hasActionOutputSupport,
}).then(arr =>
localizeDynamicRoutes(
arr,
@@ -1588,23 +1557,10 @@ export async function serverBuild({
if (lambdas[pathname]) {
lambdas[`${pathname}.rsc`] = lambdas[pathname];
if (experimental.ppr) {
lambdas[`${pathname}${RSC_PREFETCH_SUFFIX}`] = lambdas[pathname];
}
}
if (edgeFunctions[pathname]) {
edgeFunctions[`${pathname}.rsc`] = edgeFunctions[pathname];
if (experimental.ppr) {
edgeFunctions[`${pathname}${RSC_PREFETCH_SUFFIX}`] =
edgeFunctions[pathname];
}
if (hasActionOutputSupport) {
edgeFunctions[`${pathname}.action`] = edgeFunctions[pathname];
}
}
}
}
@@ -1907,7 +1863,7 @@ export async function serverBuild({
...(appDir
? [
...(rscPrefetchHeader && experimental.ppr
...(rscPrefetchHeader
? [
{
src: `^${path.posix.join('/', entryDirectory, '/')}`,
@@ -1949,56 +1905,9 @@ export async function serverBuild({
},
]
: []),
...(hasActionOutputSupport
? [
// Create rewrites for streaming prerenders (.action routes)
// This contains separate rewrites for each possible "has" (action header, or content-type)
// Also includes separate handling for index routes which should match to /index.action.
// This follows the same pattern as the rewrites for .rsc files.
{
src: `^${path.posix.join('/', entryDirectory, '/?')}`,
dest: path.posix.join('/', entryDirectory, '/index.action'),
has: [
{
type: 'header',
key: 'next-action',
},
],
missing: [
{
type: 'header',
key: rscHeader,
},
],
continue: true,
override: true,
},
{
src: `^${path.posix.join(
'/',
entryDirectory,
'/((?!.+\\.action).+?)(?:/)?$'
)}`,
dest: path.posix.join('/', entryDirectory, '/$1.action'),
has: [
{
type: 'header',
key: 'next-action',
},
],
missing: [
{
type: 'header',
key: rscHeader,
},
],
continue: true,
override: true,
},
]
: []),
{
src: `^${path.posix.join('/', entryDirectory, '/?')}`,
src: `^${path.posix.join('/', entryDirectory, '/')}`,
has: [
{
type: 'header',
@@ -2061,6 +1970,47 @@ export async function serverBuild({
]
: []),
...(rscPrefetchHeader && !experimental.ppr
? [
{
src: path.posix.join(
'/',
entryDirectory,
`/__index${RSC_PREFETCH_SUFFIX}`
),
dest: path.posix.join('/', entryDirectory, '/index.rsc'),
has: [
{
type: 'header',
key: rscPrefetchHeader,
},
],
continue: true,
override: true,
},
{
src: `^${path.posix.join(
'/',
entryDirectory,
`/(.+?)${RSC_PREFETCH_SUFFIX}(?:/)?$`
)}`,
dest: path.posix.join(
'/',
entryDirectory,
`/$1${experimental.ppr ? RSC_PREFETCH_SUFFIX : '.rsc'}`
),
has: [
{
type: 'header',
key: rscPrefetchHeader,
},
],
continue: true,
override: true,
},
]
: []),
// These need to come before handle: miss or else they are grouped
// with that routing section
...afterFilesRewrites,

View File

@@ -321,7 +321,6 @@ export async function getDynamicRoutes({
isServerMode,
dynamicMiddlewareRouteMap,
experimentalPPRRoutes,
hasActionOutputSupport,
}: {
entryPath: string;
entryDirectory: string;
@@ -334,7 +333,6 @@ export async function getDynamicRoutes({
isServerMode?: boolean;
dynamicMiddlewareRouteMap?: ReadonlyMap<string, RouteWithSrc>;
experimentalPPRRoutes: ReadonlySet<string>;
hasActionOutputSupport: boolean;
}): Promise<RouteWithSrc[]> {
if (routesManifest) {
switch (routesManifest.version) {
@@ -425,25 +423,14 @@ export async function getDynamicRoutes({
});
}
if (hasActionOutputSupport) {
routes.push({
...route,
src: route.src.replace(
new RegExp(escapeStringRegexp('(?:/)?$')),
'(?<nxtsuffix>(?:\\.action|\\.rsc))(?:/)?$'
),
dest: route.dest?.replace(/($|\?)/, '$nxtsuffix$1'),
});
} else {
routes.push({
...route,
src: route.src.replace(
new RegExp(escapeStringRegexp('(?:/)?$')),
'(?:\\.rsc)(?:/)?$'
),
dest: route.dest?.replace(/($|\?)/, '.rsc$1'),
});
}
routes.push({
...route,
src: route.src.replace(
new RegExp(escapeStringRegexp('(?:/)?$')),
'(?:\\.rsc)(?:/)?$'
),
dest: route.dest?.replace(/($|\?)/, '.rsc$1'),
});
routes.push(route);
}
@@ -1439,13 +1426,6 @@ async function getSourceFilePathFromPage({
}
}
// if we got here, and didn't find a source not-found file, then it was the one injected
// by Next.js. There's no need to warn or return a source file in this case, as it won't have
// any configuration applied to it.
if (page === '/_not-found/page') {
return '';
}
console.log(
`WARNING: Unable to find source file for page ${page} with extensions: ${extensionsToTry.join(
', '
@@ -1507,7 +1487,6 @@ export type LambdaGroup = {
isStreaming?: boolean;
isPrerenders?: boolean;
isExperimentalPPR?: boolean;
isActionLambda?: boolean;
isPages?: boolean;
isApiLambda: boolean;
pseudoLayer: PseudoLayer;
@@ -1572,13 +1551,15 @@ export async function getPageLambdaGroups({
}
if (config && config.functions) {
// `pages` are normalized without route groups (e.g., /app/(group)/page.js).
// we keep track of that mapping in `inversedAppPathManifest`
// `getSourceFilePathFromPage` needs to use the path from source to properly match the config
const pageFromManifest = inversedAppPathManifest?.[routeName];
const sourceFile = await getSourceFilePathFromPage({
workPath: entryPath,
page: normalizeSourceFilePageFromManifest(
routeName,
page,
inversedAppPathManifest
),
// since this function is used by both `pages` and `app`, the manifest might not be provided
// so fallback to normal behavior of just checking the `page`.
page: pageFromManifest ?? page,
pageExtensions,
});
@@ -1662,43 +1643,6 @@ export async function getPageLambdaGroups({
return groups;
}
// `pages` are normalized without route groups (e.g., /app/(group)/page.js).
// we keep track of that mapping in `inversedAppPathManifest`
// `getSourceFilePathFromPage` needs to use the path from source to properly match the config
function normalizeSourceFilePageFromManifest(
routeName: string,
page: string,
inversedAppPathManifest?: Record<string, string>
) {
const pageFromManifest = inversedAppPathManifest?.[routeName];
if (!pageFromManifest) {
// since this function is used by both `pages` and `app`, the manifest might not be provided
// so fallback to normal behavior of just checking the `page`.
return page;
}
const metadataConventions = [
'/favicon.',
'/icon.',
'/apple-icon.',
'/opengraph-image.',
'/twitter-image.',
'/sitemap.',
'/robots.',
];
// these special metadata files for will not contain `/route` or `/page` suffix, so return the routeName as-is.
const isSpecialFile = metadataConventions.some(convention =>
routeName.startsWith(convention)
);
if (isSpecialFile) {
return routeName;
}
return pageFromManifest;
}
export const outputFunctionFileSizeInfo = (
pages: string[],
pseudoLayer: PseudoLayer,
@@ -2507,7 +2451,6 @@ export const onPrerenderRoute =
...(rscEnabled
? {
initialHeaders: {
...initialHeaders,
'content-type': rscContentTypeHeader,
vary: rscVaryHeader,
// If it contains a pre-render, then it was postponed.
@@ -2520,22 +2463,6 @@ export const onPrerenderRoute =
});
}
// we need to ensure all prerenders have a matching .rsc output
// otherwise routing could fall through unexpectedly for the
// fallback: false case as it doesn't have a dynamic route
// to catch the `.rsc` request for app -> pages routing
if (outputPrerenderPathData?.endsWith('.json') && appDir) {
const dummyOutput = new FileBlob({
data: '{}',
contentType: 'application/json',
});
const rscKey = `${outputPathPage}.rsc`;
const prefetchRscKey = `${outputPathPage}${RSC_PREFETCH_SUFFIX}`;
prerenders[rscKey] = dummyOutput;
prerenders[prefetchRscKey] = dummyOutput;
}
++prerenderGroup;
if (routesManifest?.i18n && isBlocking) {
@@ -2827,7 +2754,7 @@ interface EdgeFunctionInfoV2 extends BaseEdgeFunctionInfo {
interface EdgeFunctionInfoV3 extends BaseEdgeFunctionInfo {
matchers: EdgeFunctionMatcher[];
env: Record<string, string>;
environments: Record<string, string>;
}
interface EdgeFunctionMatcher {
@@ -3062,7 +2989,7 @@ export async function getMiddlewareBundle({
slug: 'nextjs',
version: nextVersion,
},
environment: edgeFunction.env,
environment: edgeFunction.environments,
});
})(),
routeMatchers: getRouteMatchers(edgeFunction, routesManifest),
@@ -3242,7 +3169,7 @@ export function upgradeMiddlewareManifestV1(
return {
...rest,
matchers: [{ regexp }],
env: {},
environments: {},
};
}
@@ -3270,7 +3197,7 @@ export function upgradeMiddlewareManifestV2(
const { ...rest } = v2Info;
return {
...rest,
env: {},
environments: {},
};
}

View File

@@ -1,6 +0,0 @@
'use server';
export async function increment(value) {
await new Promise(resolve => setTimeout(resolve, 500));
return value + 1;
}

View File

@@ -1,19 +0,0 @@
'use client';
import { useState } from 'react';
import { increment } from '../../../actions';
export default function Page() {
const [count, setCount] = useState(0);
async function updateCount() {
const newCount = await increment(count);
setCount(newCount);
}
return (
<form action={updateCount}>
<div id="count">{count}</div>
<button>Submit</button>
</form>
);
}

View File

@@ -1,5 +0,0 @@
export const dynamic = 'force-dynamic';
export default function Layout({ children }) {
return children;
}

View File

@@ -1,19 +0,0 @@
'use client';
import { useState } from 'react';
import { increment } from '../../../actions';
export default function Page() {
const [count, setCount] = useState(0);
async function updateCount() {
const newCount = await increment(count);
setCount(newCount);
}
return (
<form action={updateCount}>
<div id="count">{count}</div>
<button>Submit</button>
</form>
);
}

View File

@@ -1,5 +0,0 @@
export const dynamic = 'force-static';
export default function Layout({ children }) {
return children;
}

View File

@@ -1,19 +0,0 @@
'use client';
import { useState } from 'react';
import { increment } from '../../actions';
export default function Page() {
const [count, setCount] = useState(0);
async function updateCount() {
const newCount = await increment(count);
setCount(newCount);
}
return (
<form action={updateCount}>
<div id="count">{count}</div>
<button>Submit</button>
</form>
);
}

View File

@@ -1,19 +0,0 @@
'use client';
import { useState } from 'react';
import { increment } from '../../../../actions';
export default function Page() {
const [count, setCount] = useState(0);
async function updateCount() {
const newCount = await increment(count);
setCount(newCount);
}
return (
<form action={updateCount}>
<div id="count">{count}</div>
<button>Submit</button>
</form>
);
}

View File

@@ -1,5 +0,0 @@
export const dynamic = 'force-dynamic';
export default function Layout({ children }) {
return children;
}

View File

@@ -1,19 +0,0 @@
'use client';
import { useState } from 'react';
import { increment } from '../../../../actions';
export default function Page() {
const [count, setCount] = useState(0);
async function updateCount() {
const newCount = await increment(count);
setCount(newCount);
}
return (
<form action={updateCount}>
<div id="count">{count}</div>
<button>Submit</button>
</form>
);
}

View File

@@ -1,5 +0,0 @@
export const dynamic = 'force-static';
export default function Layout({ children }) {
return children;
}

View File

@@ -1,19 +0,0 @@
'use client';
import { useState } from 'react';
import { increment } from '../../../actions';
export default function Page() {
const [count, setCount] = useState(0);
async function updateCount() {
const newCount = await increment(count);
setCount(newCount);
}
return (
<form action={updateCount}>
<div id="count">{count}</div>
<button>Submit</button>
</form>
);
}

View File

@@ -1,5 +0,0 @@
export const runtime = 'edge';
export default function Layout({ children }) {
return <div>{children}</div>;
}

View File

@@ -1,45 +0,0 @@
'use client';
// @ts-ignore
import { useCallback, useState } from 'react';
function request(method) {
return fetch('/api/test', {
method,
headers: {
'content-type': 'multipart/form-data;.*',
},
});
}
export default function Home() {
const [result, setResult] = useState('Press submit');
const onClick = useCallback(async method => {
const res = await request(method);
const text = await res.text();
setResult(res.ok ? `${method} ${text}` : 'Error: ' + res.status);
}, []);
return (
<>
<div className="flex flex-col items-center justify-center h-screen">
<div className="flex flex-row space-x-2 items-center justify-center">
<button
className="border border-white rounded-sm p-4 mb-4"
onClick={() => onClick('GET')}
>
Submit GET
</button>
<button
className="border border-white rounded-sm p-4 mb-4"
onClick={() => onClick('POST')}
>
Submit POST
</button>
</div>
<div className="text-white">{result}</div>
</div>
</>
);
}

View File

@@ -1,5 +0,0 @@
export const dynamic = 'force-dynamic';
export default function Layout({ children }) {
return children;
}

View File

@@ -1,15 +0,0 @@
import { revalidatePath } from 'next/cache';
export default async function Page() {
async function serverAction() {
'use server';
await new Promise(resolve => setTimeout(resolve, 1000));
revalidatePath('/dynamic');
}
return (
<form action={serverAction}>
<button>Submit</button>
</form>
);
}

View File

@@ -1,15 +0,0 @@
import { revalidatePath } from 'next/cache';
export default async function Page() {
async function serverAction() {
'use server';
await new Promise(resolve => setTimeout(resolve, 1000));
revalidatePath('/dynamic');
}
return (
<form action={serverAction}>
<button>Submit</button>
</form>
);
}

View File

@@ -1,19 +0,0 @@
import { revalidatePath } from 'next/cache';
export default async function Page() {
async function serverAction() {
'use server';
await new Promise(resolve => setTimeout(resolve, 1000));
revalidatePath('/dynamic');
}
return (
<form action={serverAction}>
<button>Submit</button>
</form>
);
}
export function generateStaticParams() {
return [{ slug: 'pre-generated' }];
}

View File

@@ -1,5 +0,0 @@
export const dynamic = 'force-static';
export default function Layout({ children }) {
return children;
}

View File

@@ -1,15 +0,0 @@
import { revalidatePath } from 'next/cache'
export default async function Page() {
async function serverAction() {
'use server';
await new Promise((resolve) => setTimeout(resolve, 1000));
revalidatePath('/dynamic');
}
return (
<form action={serverAction}>
<button>Submit</button>
</form>
);
}

View File

@@ -1,10 +0,0 @@
export default function Root({ children }) {
return (
<html>
<head>
<title>Hello World</title>
</head>
<body>{children}</body>
</html>
);
}

View File

@@ -1,45 +0,0 @@
'use client';
// @ts-ignore
import { useCallback, useState } from 'react';
function request(method) {
return fetch('/api/test', {
method,
headers: {
'content-type': 'multipart/form-data;.*',
},
});
}
export default function Home() {
const [result, setResult] = useState('Press submit');
const onClick = useCallback(async method => {
const res = await request(method);
const text = await res.text();
setResult(res.ok ? `${method} ${text}` : 'Error: ' + res.status);
}, []);
return (
<>
<div className="flex flex-col items-center justify-center h-screen">
<div className="flex flex-row space-x-2 items-center justify-center">
<button
className="border border-white rounded-sm p-4 mb-4"
onClick={() => onClick('GET')}
>
Submit GET
</button>
<button
className="border border-white rounded-sm p-4 mb-4"
onClick={() => onClick('POST')}
>
Submit POST
</button>
</div>
<div className="text-white">{result}</div>
</div>
</>
);
}

View File

@@ -1,5 +0,0 @@
export const dynamic = 'force-dynamic';
export default function Layout({ children }) {
return children;
}

View File

@@ -1,15 +0,0 @@
import { revalidatePath } from 'next/cache';
export default async function Page() {
async function serverAction() {
'use server';
await new Promise(resolve => setTimeout(resolve, 1000));
revalidatePath('/dynamic');
}
return (
<form action={serverAction}>
<button>Submit</button>
</form>
);
}

View File

@@ -1,15 +0,0 @@
import { revalidatePath } from 'next/cache';
export default async function Page() {
async function serverAction() {
'use server';
await new Promise(resolve => setTimeout(resolve, 1000));
revalidatePath('/dynamic');
}
return (
<form action={serverAction}>
<button>Submit</button>
</form>
);
}

View File

@@ -1,19 +0,0 @@
import { revalidatePath } from 'next/cache';
export default async function Page() {
async function serverAction() {
'use server';
await new Promise(resolve => setTimeout(resolve, 1000));
revalidatePath('/dynamic');
}
return (
<form action={serverAction}>
<button>Submit</button>
</form>
);
}
export function generateStaticParams() {
return [{ slug: 'pre-generated' }];
}

View File

@@ -1,5 +0,0 @@
export const dynamic = 'force-static';
export default function Layout({ children }) {
return children;
}

View File

@@ -1,15 +0,0 @@
import { revalidatePath } from 'next/cache'
export default async function Page() {
async function serverAction() {
'use server';
await new Promise((resolve) => setTimeout(resolve, 1000));
revalidatePath('/dynamic');
}
return (
<form action={serverAction}>
<button>Submit</button>
</form>
);
}

View File

@@ -1,304 +0,0 @@
/* eslint-env jest */
const path = require('path');
const { deployAndTest } = require('../../utils');
const fetch = require('../../../../../test/lib/deployment/fetch-retry');
const ctx = {};
function findActionId(page, runtime) {
page = `app${page}/page`; // add /app prefix and /page suffix
for (const [actionId, details] of Object.entries(
ctx.actionManifest[runtime]
)) {
if (details.workers[page]) {
return actionId;
}
}
return null;
}
function generateFormDataPayload(actionId) {
return {
method: 'POST',
body: `------WebKitFormBoundaryHcVuFa30AN0QV3uZ\r\nContent-Disposition: form-data; name=\"1_$ACTION_ID_${actionId}\"\r\n\r\n\r\n------WebKitFormBoundaryHcVuFa30AN0QV3uZ\r\nContent-Disposition: form-data; name=\"0\"\r\n\r\n[\"$K1\"]\r\n------WebKitFormBoundaryHcVuFa30AN0QV3uZ--\r\n`,
headers: {
'Content-Type':
'multipart/form-data; boundary=----WebKitFormBoundaryHcVuFa30AN0QV3uZ',
'Next-Action': actionId,
},
};
}
describe(`${__dirname.split(path.sep).pop()}`, () => {
beforeAll(async () => {
const info = await deployAndTest(__dirname);
const actionManifest = await fetch(
`${info.deploymentUrl}/server-reference-manifest.json`
).then(res => res.json());
ctx.actionManifest = actionManifest;
Object.assign(ctx, info);
});
describe.each(['node', 'edge'])('runtime: %s', runtime => {
const basePath = runtime === 'edge' ? '/edge' : '';
describe('client component', () => {
it('should bypass the static cache for a server action', async () => {
const path = `${basePath}/client/static`;
const actionId = findActionId(path, runtime);
const res = await fetch(`${ctx.deploymentUrl}${path}`, {
method: 'POST',
body: JSON.stringify([1337]),
headers: {
'Content-Type': 'text/plain;charset=UTF-8',
'Next-Action': actionId,
},
});
expect(res.status).toEqual(200);
const body = await res.text();
expect(body).toContain('1338');
expect(res.headers.get('x-matched-path')).toBe(path + '.action');
if (runtime === 'node') {
expect(res.headers.get('x-vercel-cache')).toBe('MISS');
} else {
// in edge runtime, x-vercel-cache is not returned on MISSes for some reason.
// this checks to ensure it was routed to the edge function instead.
expect(res.headers.get('x-edge-runtime')).toBe('1');
}
});
it('should bypass the static cache for a server action on a page with dynamic params', async () => {
const path = `${basePath}/client/static/[dynamic-static]`;
const actionId = findActionId(path, runtime);
const res = await fetch(`${ctx.deploymentUrl}${path}`, {
method: 'POST',
body: JSON.stringify([1337]),
headers: {
'Content-Type': 'text/plain;charset=UTF-8',
'Next-Action': actionId,
},
});
expect(res.status).toEqual(200);
const body = await res.text();
expect(body).toContain('1338');
expect(res.headers.get('x-matched-path')).toBe(path + '.action');
if (runtime === 'node') {
expect(res.headers.get('x-vercel-cache')).toBe('MISS');
} else {
expect(res.headers.get('x-edge-runtime')).toBe('1');
}
});
it('should bypass the static cache for a multipart request (no action header)', async () => {
const path = `${basePath}/client/static`;
const actionId = findActionId(path, runtime);
const res = await fetch(`${ctx.deploymentUrl}${path}`, {
method: 'POST',
body: `------WebKitFormBoundaryHcVuFa30AN0QV3uZ\r\nContent-Disposition: form-data; name=\"1_$ACTION_ID_${actionId}\"\r\n\r\n\r\n------WebKitFormBoundaryHcVuFa30AN0QV3uZ\r\nContent-Disposition: form-data; name=\"0\"\r\n\r\n[\"$K1\"]\r\n------WebKitFormBoundaryHcVuFa30AN0QV3uZ--\r\n`,
headers: {
'Content-Type':
'multipart/form-data; boundary=----WebKitFormBoundaryHcVuFa30AN0QV3uZ',
},
});
expect(res.status).toEqual(200);
expect(res.headers.get('content-type')).toBe(
'text/html; charset=utf-8'
);
if (runtime === 'node') {
// This is a "BYPASS" because no action ID was provided, so it'll fall back to
// `experimentalBypassFor` handling.
expect(res.headers.get('x-vercel-cache')).toBe('BYPASS');
} else {
expect(res.headers.get('x-edge-runtime')).toBe('1');
}
expect(res.headers.get('x-matched-path')).toBe(path);
});
it('should properly invoke the action on a dynamic page', async () => {
const path = `${basePath}/client/dynamic/[id]`;
const actionId = findActionId(path, runtime);
const res = await fetch(`${ctx.deploymentUrl}${path}`, {
method: 'POST',
body: JSON.stringify([1337]),
headers: {
'Content-Type': 'text/plain;charset=UTF-8',
'Next-Action': actionId,
},
});
expect(res.status).toEqual(200);
const body = await res.text();
expect(body).toContain('1338');
expect(res.headers.get('x-matched-path')).toBe(path + '.action');
if (runtime === 'node') {
expect(res.headers.get('x-vercel-cache')).toBe('MISS');
} else {
expect(res.headers.get('x-edge-runtime')).toBe('1');
}
});
});
describe('server component', () => {
it('should bypass the static cache for a server action', async () => {
const path = `${basePath}/rsc/static`;
const actionId = findActionId(path, runtime);
const res = await fetch(
`${ctx.deploymentUrl}${path}`,
generateFormDataPayload(actionId)
);
expect(res.status).toEqual(200);
expect(res.headers.get('x-matched-path')).toBe(path + '.action');
expect(res.headers.get('content-type')).toBe('text/x-component');
if (runtime === 'node') {
expect(res.headers.get('x-vercel-cache')).toBe('MISS');
} else {
expect(res.headers.get('x-edge-runtime')).toBe('1');
}
});
it('should bypass the static cache for a server action on a page with dynamic params', async () => {
const path = `${basePath}/rsc/static/[dynamic-static]`;
const actionId = findActionId(path, runtime);
const res = await fetch(
`${ctx.deploymentUrl}${path}`,
generateFormDataPayload(actionId)
);
expect(res.status).toEqual(200);
expect(res.headers.get('x-matched-path')).toBe(path + '.action');
expect(res.headers.get('content-type')).toBe('text/x-component');
if (runtime === 'node') {
expect(res.headers.get('x-vercel-cache')).toBe('MISS');
} else {
expect(res.headers.get('x-edge-runtime')).toBe('1');
}
});
it('should properly invoke the action on a dynamic page', async () => {
const path = `${basePath}/rsc/dynamic`;
const actionId = findActionId(path, runtime);
const res = await fetch(
`${ctx.deploymentUrl}${path}`,
generateFormDataPayload(actionId)
);
expect(res.status).toEqual(200);
expect(res.headers.get('x-matched-path')).toBe(path + '.action');
expect(res.headers.get('content-type')).toBe('text/x-component');
if (runtime === 'node') {
expect(res.headers.get('x-vercel-cache')).toBe('MISS');
} else {
expect(res.headers.get('x-edge-runtime')).toBe('1');
}
});
describe('generateStaticParams', () => {
it('should bypass the static cache for a server action when pre-generated', async () => {
const path = `${basePath}/rsc/static/generate-static-params/pre-generated`;
const dynamicPath = `${basePath}/rsc/static/generate-static-params/[slug]`;
const actionId = findActionId(dynamicPath, runtime);
const res = await fetch(
`${ctx.deploymentUrl}${path}`,
generateFormDataPayload(actionId)
);
expect(res.status).toEqual(200);
expect(res.headers.get('x-matched-path')).toBe(
dynamicPath + '.action'
);
expect(res.headers.get('content-type')).toBe('text/x-component');
if (runtime === 'node') {
expect(res.headers.get('x-vercel-cache')).toBe('MISS');
} else {
expect(res.headers.get('x-edge-runtime')).toBe('1');
}
});
it('should bypass the static cache for a server action when not pre-generated', async () => {
const page = `${basePath}/rsc/static/generate-static-params/[slug]`;
const actionId = findActionId(page, runtime);
const res = await fetch(
`${ctx.deploymentUrl}/${basePath}/rsc/static/generate-static-params/not-pre-generated`,
generateFormDataPayload(actionId)
);
expect(res.status).toEqual(200);
expect(res.headers.get('x-matched-path')).toBe(page + '.action');
expect(res.headers.get('content-type')).toBe('text/x-component');
if (runtime === 'node') {
expect(res.headers.get('x-vercel-cache')).toBe('MISS');
} else {
expect(res.headers.get('x-edge-runtime')).toBe('1');
}
});
});
});
it('should not match to an action output when the RSC header is present', async () => {
const canonicalPath = `${basePath}/client/dynamic/1`;
const pagePath = `${basePath}/client/dynamic/[id]`;
const actionId = findActionId(pagePath, runtime);
const res = await fetch(`${ctx.deploymentUrl}${canonicalPath}`, {
method: 'POST',
body: JSON.stringify([1337]),
headers: {
'Content-Type': 'text/plain;charset=UTF-8',
'Next-Action': actionId,
'Next-Response': 'rsc',
RSC: '1',
},
});
expect(res.status).toEqual(200);
expect(res.headers.get('x-matched-path')).toBe(pagePath + '.rsc');
expect(res.headers.get('content-type')).toBe('text/x-component');
const body = await res.text();
expect(body).toContain(JSON.stringify(['id', '1', 'd']));
expect(body).not.toContain(JSON.stringify(['id', '1.action', 'd']));
});
});
describe('pages', () => {
it('should not attempt to rewrite the action path for a server action (POST)', async () => {
const res = await fetch(`${ctx.deploymentUrl}/api/test`, {
method: 'POST',
headers: {
'Content-Type':
'multipart/form-data; boundary=----WebKitFormBoundaryHcVuFa30AN0QV3uZ',
},
});
expect(res.status).toEqual(200);
expect(res.headers.get('x-matched-path')).toBe('/api/test');
expect(res.headers.get('x-vercel-cache')).toBe('MISS');
const body = await res.json();
expect(body).toEqual({ message: 'Hello from Next.js!' });
});
it('should not attempt to rewrite the action path for a server action (GET)', async () => {
const res = await fetch(`${ctx.deploymentUrl}/api/test`);
expect(res.status).toEqual(200);
expect(res.headers.get('x-matched-path')).toBe('/api/test');
const body = await res.json();
expect(body).toEqual({ message: 'Hello from Next.js!' });
});
});
});

View File

@@ -1 +0,0 @@
module.exports = {};

View File

@@ -1,9 +0,0 @@
{
"dependencies": {
"next": "canary"
},
"scripts": {
"build": "next build && cp .next/server/server-reference-manifest.json public/"
},
"ignoreNextjsUpdates": true
}

View File

@@ -1,3 +0,0 @@
export default function handler(req, res) {
res.status(200).json({ message: 'Hello from Next.js!' });
}

View File

@@ -1,14 +0,0 @@
{
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
],
"build": {
"env": {
"NEXT_EXPERIMENTAL_STREAMING_ACTIONS": "1"
}
},
"probes": []
}

View File

@@ -7,4 +7,4 @@ export default function Root({ children }) {
<body>{children}</body>
</html>
);
}
}

View File

@@ -1,45 +0,0 @@
'use client';
// @ts-ignore
import { useCallback, useState } from 'react';
function request(method) {
return fetch('/api/test', {
method,
headers: {
'content-type': 'multipart/form-data;.*',
},
});
}
export default function Home() {
const [result, setResult] = useState('Press submit');
const onClick = useCallback(async method => {
const res = await request(method);
const text = await res.text();
setResult(res.ok ? `${method} ${text}` : 'Error: ' + res.status);
}, []);
return (
<>
<div className="flex flex-col items-center justify-center h-screen">
<div className="flex flex-row space-x-2 items-center justify-center">
<button
className="border border-white rounded-sm p-4 mb-4"
onClick={() => onClick('GET')}
>
Submit GET
</button>
<button
className="border border-white rounded-sm p-4 mb-4"
onClick={() => onClick('POST')}
>
Submit POST
</button>
</div>
<div className="text-white">{result}</div>
</div>
</>
);
}

View File

@@ -37,7 +37,6 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
).then(res => res.json());
ctx.actionManifest = actionManifest;
Object.assign(ctx, info);
});
@@ -82,25 +81,6 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
expect(res.headers.get('x-vercel-cache')).toBe('BYPASS');
});
it('should bypass the static cache for a multipart request (no action header)', async () => {
const path = '/client/static';
const actionId = findActionId(path);
const res = await fetch(`${ctx.deploymentUrl}${path}`, {
method: 'POST',
body: `------WebKitFormBoundaryHcVuFa30AN0QV3uZ\r\nContent-Disposition: form-data; name=\"1_$ACTION_ID_${actionId}\"\r\n\r\n\r\n------WebKitFormBoundaryHcVuFa30AN0QV3uZ\r\nContent-Disposition: form-data; name=\"0\"\r\n\r\n[\"$K1\"]\r\n------WebKitFormBoundaryHcVuFa30AN0QV3uZ--\r\n`,
headers: {
'Content-Type':
'multipart/form-data; boundary=----WebKitFormBoundaryHcVuFa30AN0QV3uZ',
},
});
expect(res.status).toEqual(200);
expect(res.headers.get('content-type')).toBe('text/html; charset=utf-8');
expect(res.headers.get('x-vercel-cache')).toBe('BYPASS');
expect(res.headers.get('x-matched-path')).toBe(path);
});
it('should properly invoke the action on a dynamic page', async () => {
const path = '/client/dynamic/[id]';
const actionId = findActionId(path);
@@ -118,7 +98,6 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
const body = await res.text();
expect(body).toContain('1338');
expect(res.headers.get('x-matched-path')).toBe(path);
// This isn't a "BYPASS" because the action wasn't part of a static prerender
expect(res.headers.get('x-vercel-cache')).toBe('MISS');
});
});
@@ -166,7 +145,6 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
expect(res.status).toEqual(200);
expect(res.headers.get('x-matched-path')).toBe(path);
expect(res.headers.get('content-type')).toBe('text/x-component');
// This isn't a "BYPASS" because the action wasn't part of a static prerender
expect(res.headers.get('x-vercel-cache')).toBe('MISS');
});
@@ -183,9 +161,7 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
);
expect(res.status).toEqual(200);
expect(res.headers.get('x-matched-path')).toBe(
'/rsc/static/generate-static-params/pre-generated'
);
expect(res.headers.get('x-matched-path')).toBe(path);
expect(res.headers.get('content-type')).toBe('text/x-component');
expect(res.headers.get('x-vercel-cache')).toBe('BYPASS');
});
@@ -202,36 +178,8 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
expect(res.status).toEqual(200);
expect(res.headers.get('x-matched-path')).toBe(page);
expect(res.headers.get('content-type')).toBe('text/x-component');
// This isn't a "BYPASS" because the action wasn't part of a static prerender
expect(res.headers.get('x-vercel-cache')).toBe('BYPASS');
});
});
});
describe('pages', () => {
it('should not attempt to rewrite the action path for a server action (POST)', async () => {
const res = await fetch(`${ctx.deploymentUrl}/api/test`, {
method: 'POST',
headers: {
'Content-Type':
'multipart/form-data; boundary=----WebKitFormBoundaryHcVuFa30AN0QV3uZ',
},
});
expect(res.status).toEqual(200);
expect(res.headers.get('x-matched-path')).toBe('/api/test');
expect(res.headers.get('x-vercel-cache')).toBe('MISS');
const body = await res.json();
expect(body).toEqual({ message: 'Hello from Next.js!' });
});
it('should not attempt to rewrite the action path for a server action (GET)', async () => {
const res = await fetch(`${ctx.deploymentUrl}/api/test`);
expect(res.status).toEqual(200);
expect(res.headers.get('x-matched-path')).toBe('/api/test');
const body = await res.json();
expect(body).toEqual({ message: 'Hello from Next.js!' });
});
});
});

View File

@@ -1 +0,0 @@
module.exports = {};

View File

@@ -1,3 +0,0 @@
export default function handler(req, res) {
res.status(200).json({ message: 'Hello from Next.js!' });
}

View File

@@ -18,31 +18,6 @@
}
],
"probes": [
{
"path": "/hello/world",
"status": 200,
"mustContain": "index app page"
},
{
"path": "/hello/world",
"status": 200,
"mustContain": "index app page",
"mustNotContain": "<html",
"headers": {
"RSC": 1
}
},
{
"path": "/hello/world",
"status": 200,
"mustContain": ":",
"mustNotContain": "<html",
"headers": {
"RSC": 1,
"Next-Router-Prefetch": 1
}
},
{
"path": "/hello/world/dashboard/hello",
"status": 200,
@@ -67,14 +42,14 @@
"status": 200,
"mustContain": "hello from /ssg",
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url"
}
},
{
"path": "/hello/world/ssg",
"status": 200,
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url"
},
"headers": {
"RSC": "1"
@@ -107,14 +82,14 @@
"status": 200,
"mustContain": "hello from app/dashboard/deployments/[id]/settings",
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url"
}
},
{
"path": "/hello/world/dashboard/deployments/123/settings",
"status": 200,
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url"
},
"headers": {
"RSC": "1"
@@ -127,14 +102,14 @@
"status": 200,
"mustContain": "catchall",
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url"
}
},
{
"path": "/hello/world/dashboard/deployments/catchall/something",
"status": 200,
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url"
},
"headers": {
"RSC": "1"
@@ -147,7 +122,7 @@
"status": 200,
"mustContain": "hello from app/dashboard",
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url"
}
},
{
@@ -167,7 +142,7 @@
},
"responseHeaders": {
"content-type": "text/x-component",
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url"
}
},
{

View File

@@ -1,8 +1,8 @@
{
"dependencies": {
"next": "canary",
"react": "19.0.0-beta-4508873393-20240430",
"react-dom": "19.0.0-beta-4508873393-20240430"
"react": "experimental",
"react-dom": "experimental"
},
"ignoreNextjsUpdates": true
}

View File

@@ -1,8 +1,8 @@
{
"dependencies": {
"next": "canary",
"react": "19.0.0-beta-4508873393-20240430",
"react-dom": "19.0.0-beta-4508873393-20240430"
"react": "experimental",
"react-dom": "experimental"
},
"ignoreNextjsUpdates": true,
"scripts": {

View File

@@ -1,8 +1,8 @@
{
"dependencies": {
"next": "canary",
"react": "19.0.0-beta-4508873393-20240430",
"react-dom": "19.0.0-beta-4508873393-20240430"
"react": "experimental",
"react-dom": "experimental"
},
"ignoreNextjsUpdates": true
}

View File

@@ -1,8 +1,8 @@
{
"dependencies": {
"next": "canary",
"react": "19.0.0-beta-4508873393-20240430",
"react-dom": "19.0.0-beta-4508873393-20240430"
"react": "experimental",
"react-dom": "experimental"
},
"ignoreNextjsUpdates": true
}

View File

@@ -7,8 +7,8 @@
},
"dependencies": {
"next": "canary",
"react": "19.0.0-beta-4508873393-20240430",
"react-dom": "19.0.0-beta-4508873393-20240430"
"react": "experimental",
"react-dom": "experimental"
},
"ignoreNextjsUpdates": true
}

View File

@@ -1,3 +0,0 @@
export { default } from '../page';
export const experimental_ppr = false;

View File

@@ -1,7 +0,0 @@
export default function Layout({ children }) {
return (
<html>
<body>{children}</body>
</html>
);
}

View File

@@ -1,17 +0,0 @@
import { unstable_noStore } from 'next/cache';
import { Suspense } from 'react';
export const experimental_ppr = true;
function Dynamic() {
unstable_noStore();
return <div id="sentinel:dynamic">Dynamic</div>;
}
export default function Page() {
return (
<Suspense fallback={<div id="sentinel:loading">Loading...</div>}>
<Dynamic />
</Suspense>
);
}

View File

@@ -1,13 +0,0 @@
/* eslint-env jest */
const path = require('path');
const { deployAndTest } = require('../../utils');
const ctx = {};
// TODO: investigate invariant
describe.skip(`${__dirname.split(path.sep).pop()}`, () => {
it('should deploy and pass probe checks', async () => {
const info = await deployAndTest(__dirname);
Object.assign(ctx, info);
});
});

View File

@@ -1,5 +0,0 @@
module.exports = {
experimental: {
ppr: 'incremental',
},
};

View File

@@ -1,8 +0,0 @@
{
"dependencies": {
"next": "canary",
"react": "19.0.0-beta-4508873393-20240430",
"react-dom": "19.0.0-beta-4508873393-20240430"
},
"ignoreNextjsUpdates": true
}

View File

@@ -1,77 +0,0 @@
{
"builds": [
{
"src": "package.json",
"use": "@vercel/next",
"config": {
"functions": {
"app/**/*": {
"maxDuration": 5,
"memory": 512
},
"pages/api/**/*": {
"maxDuration": 5,
"memory": 512
}
}
}
}
],
"probes": [
{
"path": "/",
"status": 200,
"mustContain": "sentinel:loading"
},
{
"path": "/",
"status": 200,
"mustContain": "sentinel:dynamic"
},
{
"path": "/",
"headers": {
"RSC": "1",
"Next-Router-Prefetch": "1"
},
"status": 200,
"mustContain": "sentinel:loading",
"mustNotContain": "sentinel:dynamic"
},
{
"path": "/",
"headers": {
"RSC": "1"
},
"status": 200,
"mustContain": "sentinel:dynamic"
},
{
"path": "/disabled",
"status": 200,
"mustContain": "sentinel:loading"
},
{
"path": "/disabled",
"status": 200,
"mustContain": "sentinel:dynamic"
},
{
"path": "/disabled",
"headers": {
"RSC": "1",
"Next-Router-Prefetch": "1"
},
"status": 200,
"mustContain": "sentinel:dynamic"
},
{
"path": "/disabled",
"headers": {
"RSC": "1"
},
"status": 200,
"mustContain": "sentinel:dynamic"
}
]
}

View File

@@ -1,8 +1,8 @@
{
"dependencies": {
"next": "canary",
"react": "19.0.0-beta-4508873393-20240430",
"react-dom": "19.0.0-beta-4508873393-20240430"
"react": "experimental",
"react-dom": "experimental"
},
"ignoreNextjsUpdates": true
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,7 +1,3 @@
export const metadata = {
metadataBase: new URL(process.env.VERCEL_PROJECT_PRODUCTION_URL ?? 'http://localhost:3000'),
};
export default function Root({ children }) {
return (
<html className="this-is-the-document-html">

View File

@@ -1,5 +0,0 @@
export default function Page() {
return (
<p>nested app router catch-all</p>
)
}

View File

@@ -1 +0,0 @@
Alt text for opengraph-image

Some files were not shown because too many files have changed in this diff Show More