diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index b45b74cee4..dbec32d19e 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -52,10 +52,23 @@ jobs: steps: - uses: actions/checkout@v5 + - name: Decide Node Version + id: decide-node-version + shell: bash + run: | + NODE_VERSION=18.x + if [ "${{ matrix.version}}" = "canary" ]; then + # this is not ideal, because we set node@20 just when explicitly using canary tag as target + # but next@canary are still on 15 major, so we can't yet use major version of resolved next version + # as condition + NODE_VERSION=20.x + fi + echo "version=$NODE_VERSION">> $GITHUB_OUTPUT + echo "Node version for 'next@${{ matrix.version }}' is '$NODE_VERSION'" - name: 'Install Node' uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: ${{ steps.decide-node-version.outputs.version }} cache: 'npm' cache-dependency-path: '**/package-lock.json' - uses: oven-sh/setup-bun@v2 @@ -118,7 +131,7 @@ jobs: fail-fast: false matrix: shard: [1, 2, 3, 4, 5, 6, 7, 8] - os: [ubuntu-latest, windows-2025] + os: [ubuntu-latest] version: ${{ fromJson(needs.setup.outputs.matrix) }} exclude: - os: windows-2025 @@ -128,10 +141,23 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v5 + - name: Decide Node Version + id: decide-node-version + shell: bash + run: | + NODE_VERSION=18.x + if [ "${{ matrix.version}}" = "canary" ]; then + # this is not ideal, because we set node@20 just when explicitly using canary tag as target + # but next@canary are still on 15 major, so we can't yet use major version of resolved next version + # as condition + NODE_VERSION=20.x + fi + echo "version=$NODE_VERSION">> $GITHUB_OUTPUT + echo "Node version for 'next@${{ matrix.version }}' is '$NODE_VERSION'" - name: 'Install Node' uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: ${{ steps.decide-node-version.outputs.version }} cache: 'npm' cache-dependency-path: '**/package-lock.json' - name: Prefer npm global on windows @@ -205,10 +231,23 @@ jobs: version: ${{ fromJson(needs.setup.outputs.matrix) }} steps: - uses: actions/checkout@v5 + - name: Decide Node Version + id: decide-node-version + shell: bash + run: | + NODE_VERSION=18.x + if [ "${{ matrix.version}}" = "canary" ]; then + # this is not ideal, because we set node@20 just when explicitly using canary tag as target + # but next@canary are still on 15 major, so we can't yet use major version of resolved next version + # as condition + NODE_VERSION=20.x + fi + echo "version=$NODE_VERSION">> $GITHUB_OUTPUT + echo "Node version for 'next@${{ matrix.version }}' is '$NODE_VERSION'" - name: 'Install Node' uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: ${{ steps.decide-node-version.outputs.version }} cache: 'npm' cache-dependency-path: '**/package-lock.json' - name: setup pnpm/yarn diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 90f2f86819..4c7a1ddb3c 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -19,7 +19,6 @@ concurrency: cancel-in-progress: true env: - NODE_VERSION: 20.9.0 PNPM_VERSION: 8.9.0 NEXT_REPO: vercel/next.js NEXT_TEST_MODE: deploy @@ -111,10 +110,24 @@ jobs: with: path: ${{ env.runtime-path }} + - name: Decide Node Version + id: decide-node-version + shell: bash + run: | + NODE_VERSION=18.x + if [ "${{ matrix.version_spec.selector }}" = "canary" ]; then + # this is not ideal, because we set node@20 just when explicitly using canary tag as target + # but next@canary are still on 15 major, so we can't yet use major version of resolved next version + # as condition + NODE_VERSION=20.x + fi + echo "version=$NODE_VERSION">> $GITHUB_OUTPUT + echo "Node version for 'next@${{ matrix.version_spec.selector }}' is '$NODE_VERSION'" + - name: setup node uses: actions/setup-node@v4 with: - node-version: ${{ env.NODE_VERSION }} + node-version: ${{ steps.decide-node-version.outputs.version }} - name: setup pnpm/yarn run: corepack enable diff --git a/tests/prepare.mjs b/tests/prepare.mjs index 4ca0565185..164f702b17 100644 --- a/tests/prepare.mjs +++ b/tests/prepare.mjs @@ -60,10 +60,12 @@ const promises = fixtures.map((fixture) => }) await Promise.all(publishDirectories.map((dir) => rm(dir, { recursive: true, force: true }))) - if (NEXT_VERSION !== 'latest') { - await setNextVersionInFixture(cwd, NEXT_VERSION, { - logPrefix: `[${fixture}] `, - }) + const fixtureNextVersionSatisfied = await setNextVersionInFixture(cwd, NEXT_VERSION, { + logPrefix: `[${fixture}] `, + }) + + if (!fixtureNextVersionSatisfied) { + return } let cmd = `` @@ -79,19 +81,26 @@ const promises = fixtures.map((fixture) => await rm(join(cwd, 'package-lock.json'), { force: true }) } - const addPrefix = new Transform({ - transform(chunk, encoding, callback) { - this.push(chunk.toString().replace(/\n/gm, `\n[${fixture}] `)) - callback() - }, - flush(callback) { - // final transform might create non-terminated line with a prefix - // so this is just to make sure we end with a newline so further writes - // to same destination stream start on a new line for better readability - this.push('\n') - callback() - }, - }) + const addPrefix = () => { + let isFirstChunk = true + return new Transform({ + transform(chunk, encoding, callback) { + if (isFirstChunk) { + this.push(`[${fixture}] `) + isFirstChunk = false + } + this.push(chunk.toString().replace(/\n/gm, `\n[${fixture}] `)) + callback() + }, + flush(callback) { + // final transform might create non-terminated line with a prefix + // so this is just to make sure we end with a newline so further writes + // to same destination stream start on a new line for better readability + this.push('\n') + callback() + }, + }) + } console.log(`[${fixture}] Running \`${cmd}\`...`) const output = execaCommand(cmd, { @@ -100,16 +109,24 @@ const promises = fixtures.map((fixture) => env: { ...process.env, FORCE_COLOR: '1' }, }) if (process.env.DEBUG) { - output.stdout?.pipe(addPrefix).pipe(process.stdout) + output.stdout?.pipe(addPrefix()).pipe(process.stdout) } - output.stderr?.pipe(addPrefix).pipe(process.stderr) + output.stderr?.pipe(addPrefix()).pipe(process.stderr) return output.finally(async () => { - if (NEXT_VERSION !== 'latest') { - await setNextVersionInFixture(cwd, 'latest', { - logPrefix: `[${fixture}] `, - operation: 'revert', - }) + if (process.env.DEBUG) { + const npmListPromise = execaCommand( + packageManager?.startsWith('pnpm') ? 'pnpm list next' : 'npm list next', + { cwd, stdio: 'pipe', reject: false }, + ) + npmListPromise.stdout?.pipe(addPrefix()).pipe(process.stdout) + npmListPromise.stderr?.pipe(addPrefix()).pipe(process.stderr) + await npmListPromise } + + await setNextVersionInFixture(cwd, 'latest', { + logPrefix: `[${fixture}] `, + operation: 'revert', + }) if (output.exitCode !== 0) { const errorMessage = `[${fixture}] 🚨 Failed to install dependencies or build a fixture` console.error(errorMessage) diff --git a/tests/utils/create-e2e-fixture.ts b/tests/utils/create-e2e-fixture.ts index 0a9d0f1e03..6036eb1efd 100644 --- a/tests/utils/create-e2e-fixture.ts +++ b/tests/utils/create-e2e-fixture.ts @@ -78,9 +78,7 @@ export const createE2EFixture = async (fixture: string, config: E2EConfig = {}) copyFixture(fixture, isolatedFixtureRoot, config), ]) - if (NEXT_VERSION !== 'latest') { - await setNextVersionInFixture(isolatedFixtureRoot, NEXT_VERSION) - } + await setNextVersionInFixture(isolatedFixtureRoot, NEXT_VERSION) await installRuntime(packageName, isolatedFixtureRoot, config) await verifyFixture(isolatedFixtureRoot, config) diff --git a/tests/utils/fixture.ts b/tests/utils/fixture.ts index 2736be62c2..d0f92bf530 100644 --- a/tests/utils/fixture.ts +++ b/tests/utils/fixture.ts @@ -39,20 +39,27 @@ const eszipHelper = join(actualCwd, 'tools/deno/eszip.ts') async function installDependencies(cwd: string) { const NEXT_VERSION = process.env.NEXT_VERSION ?? 'latest' - if (NEXT_VERSION !== 'latest') { - await setNextVersionInFixture(cwd, NEXT_VERSION, { silent: true }) - } + await setNextVersionInFixture(cwd, NEXT_VERSION, { silent: true }) const { packageManager } = JSON.parse(await readFile(join(cwd, 'package.json'), 'utf8')) if (packageManager?.startsWith('pnpm')) { - return execaCommand(`pnpm install --ignore-scripts --reporter=silent`, { + await execaCommand(`pnpm install --ignore-scripts --reporter=silent`, { cwd, }) + } else { + await execaCommand( + `npm install --ignore-scripts --no-audit --progress=false --legacy-peer-deps`, + { cwd }, + ) + } + + if (process.env.DEBUG) { + await execaCommand(packageManager?.startsWith('pnpm') ? `pnpm list next` : 'npm list next', { + cwd, + stdio: 'inherit', + reject: false, + }) } - return execaCommand( - `npm install --ignore-scripts --no-audit --progress=false --legacy-peer-deps`, - { cwd }, - ) } export const getFixtureSourceDirectory = (fixture: string) => diff --git a/tests/utils/next-version-helpers.mjs b/tests/utils/next-version-helpers.mjs index 1afb74aa5d..018639ab49 100644 --- a/tests/utils/next-version-helpers.mjs +++ b/tests/utils/next-version-helpers.mjs @@ -70,7 +70,7 @@ export function nextVersionRequiresReact19(version) { * @param {'update' | 'revert'} [options.operation] This just informs log output wording, otherwise it has no effect * @param {boolean} [options.silent] Doesn't produce any logs if truthy * @param {boolean} [options.updateReact] Update React version to match Next version - * @returns {Promise} + * @returns {Promise} true if fixture's next version requirements are satisfied */ export async function setNextVersionInFixture( cwd, @@ -87,12 +87,6 @@ export async function setNextVersionInFixture( // if resolved version is different from version, we add it to the log to provide additional details const nextVersionForLogs = `next@${version}${resolvedVersion !== version ? ` (${resolvedVersion})` : ``}` - if (!silent) { - console.log( - `${logPrefix}▲さんかく ${operation === 'revert' ? 'Reverting' : 'Updating'} to ${nextVersionForLogs}...`, - ) - } - const packageJsons = await fg.glob(['**/package.json', '!**/node_modules'], { cwd, absolute: true, @@ -100,7 +94,7 @@ export async function setNextVersionInFixture( const isSemverVersion = valid(resolvedVersion) - await Promise.all( + const areNextVersionConstraintsSatisfied = await Promise.all( packageJsons.map(async (packageJsonPath) => { const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')) if (packageJson.dependencies?.next) { @@ -110,7 +104,9 @@ export async function setNextVersionInFixture( if ( operation === 'update' && versionConstraint && - !satisfies(checkVersion, versionConstraint, { includePrerelease: true }) && + !(versionConstraint === 'canary' + ? isNextCanary() + : satisfies(checkVersion, versionConstraint, { includePrerelease: true })) && version !== versionConstraint ) { if (!silent) { @@ -118,9 +114,35 @@ export async function setNextVersionInFixture( `${logPrefix}⏩ Skipping '${packageJson.name}' because it requires next@${versionConstraint}`, ) } - return + return false } + } + return true + }), + ) + + if (areNextVersionConstraintsSatisfied.some((isSatisfied) => !isSatisfied)) { + // at least one next version constraint is not satisfied so we skip this fixture + return false + } + + if ((process.env.NEXT_VERSION ?? 'latest') === 'latest') { + // latest is default so we don't want to make any changes + return true + } + + if (!silent) { + console.log( + `${logPrefix}▲さんかく ${operation === 'revert' ? 'Reverting' : 'Updating'} to ${nextVersionForLogs}...`, + ) + } + + await Promise.all( + packageJsons.map(async (packageJsonPath) => { + const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')) + if (packageJson.dependencies?.next) { packageJson.dependencies.next = version + const checkVersion = isSemverVersion ? resolvedVersion : FUTURE_NEXT_PATCH_VERSION const { stdout } = await execaCommand( `npm info next@${resolvedVersion} peerDependencies --json`, @@ -172,4 +194,6 @@ export async function setNextVersionInFixture( `${logPrefix}▲さんかく ${operation === 'revert' ? 'Reverted' : 'Updated'} to ${nextVersionForLogs}`, ) } + + return true }

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /