Files
cloudflare__vinext/tests/e2e-deploy-script.test.ts
2026-09-11 11:02:35 +01:00

231 lines
9.6 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { describe, expect, it } from "vite-plus/test";
describe("Next.js deploy harness", () => {
it("enables Next.js test-only client instrumentation in deploy shards", () => {
const workflow = fs.readFileSync(
path.resolve(".github/workflows/nextjs-deploy-suite.yml"),
"utf8",
);
const deployShard = workflow.match(
/- name: Run deploy shard[\s\S]*?\n - name: Upload test results/,
)?.[0];
expect(deployShard).toBeDefined();
expect(deployShard).toContain("NEXT_TEST_MODE: deploy");
expect(deployShard).toContain("__NEXT_TEST_MODE: e2e");
});
it("does not recursively append failure diagnostics to deploy logs", () => {
const script = fs.readFileSync(path.resolve("scripts/e2e-deploy.sh"), "utf8");
const cleanup = script.match(
/(cleanup_on_error\(\) \{[\s\S]*?\n\})\n\ntrap cleanup_on_error EXIT/,
)?.[1];
expect(cleanup).toBeDefined();
const tempRoot = fs.mkdtempSync(path.join(process.cwd(), ".e2e-deploy-cleanup-test-"));
try {
const buildLog = path.join(tempRoot, "build.log");
fs.writeFileSync(buildLog, "build failed\n");
const result = execFileSync(
"bash",
[
"-c",
[
"persist_debug_artifacts() { :; }",
cleanup!,
"DEPLOYMENT_READY=0 PID_FILE=missing.pid PORT_FILE=missing.port SERVER_LOG=missing.log DEBUG_RUN_DIR=debug",
'cleanup_on_error 2>> "${BUILD_LOG}"',
].join("\n"),
],
{
cwd: tempRoot,
env: { ...process.env, BUILD_LOG: buildLog },
timeout: 2_000,
},
);
expect(result).toHaveLength(0);
const output = fs.readFileSync(buildLog, "utf8");
expect(output.match(/build failed/g)).toHaveLength(2);
expect(output).toContain("=== vinext e2e deploy debug ===");
expect(Buffer.byteLength(output)).toBeLessThan(2_000);
} finally {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
});
it("materializes workspace dependencies required by the local vinext package", () => {
const repoRoot = path.resolve(".");
const script = fs.readFileSync(path.join(repoRoot, "scripts/e2e-deploy.sh"), "utf8");
const injection = script.match(
/node <<'EOF' >> "\$\{BUILD_LOG\}" 2>&1\n([\s\S]*?)\nEOF\n/,
)?.[1];
expect(injection).toBeDefined();
const tempRoot = fs.mkdtempSync(path.join(process.cwd(), ".e2e-deploy-package-test-"));
try {
const workspaceRoot = path.join(tempRoot, "workspace");
const appRoot = path.join(tempRoot, "app");
fs.mkdirSync(appRoot, { recursive: true });
fs.cpSync(path.join(repoRoot, "package.json"), path.join(workspaceRoot, "package.json"));
fs.cpSync(
path.join(repoRoot, "pnpm-workspace.yaml"),
path.join(workspaceRoot, "pnpm-workspace.yaml"),
);
for (const packageName of ["vinext", "cloudflare", "types"]) {
const packageRoot = path.join(workspaceRoot, "packages", packageName);
fs.mkdirSync(packageRoot, { recursive: true });
fs.cpSync(
path.join(repoRoot, "packages", packageName, "package.json"),
path.join(packageRoot, "package.json"),
);
}
fs.mkdirSync(path.join(workspaceRoot, "packages/vinext/dist"), { recursive: true });
fs.mkdirSync(path.join(workspaceRoot, "packages/cloudflare/dist"), { recursive: true });
fs.mkdirSync(path.join(workspaceRoot, "packages/types/next"), { recursive: true });
for (const packageName of ["react", "react-dom", "react-server-dom-webpack"]) {
const packageRoot = path.join(
workspaceRoot,
packageName === "react-server-dom-webpack"
? "packages/vinext/node_modules"
: "node_modules",
packageName,
);
fs.mkdirSync(packageRoot, { recursive: true });
fs.writeFileSync(
path.join(packageRoot, "package.json"),
JSON.stringify({ name: packageName, version: "19.2.7" }),
);
}
fs.writeFileSync(
path.join(workspaceRoot, "packages/types/next/index.d.ts"),
'declare module "next" {}\n',
);
fs.mkdirSync(path.join(appRoot, "app"));
fs.writeFileSync(
path.join(appRoot, "package.json"),
JSON.stringify({
name: "fixture",
dependencies: { react: "latest", "react-dom": "^19.0.0" },
}),
);
execFileSync(process.execPath, ["-e", injection!], {
cwd: appRoot,
env: { ...process.env, VINEXT_DIR: workspaceRoot },
stdio: "pipe",
});
const localVinext = JSON.parse(
fs.readFileSync(path.join(appRoot, ".vinext-local-package/package.json"), "utf8"),
);
const localCloudflare = JSON.parse(
fs.readFileSync(
path.join(appRoot, ".vinext-local-cloudflare-package/package.json"),
"utf8",
),
);
const localTypes = JSON.parse(
fs.readFileSync(path.join(appRoot, ".vinext-local-types-package/package.json"), "utf8"),
);
const fixture = JSON.parse(fs.readFileSync(path.join(appRoot, "package.json"), "utf8"));
expect(localVinext.dependencies["@vinext/types"]).toBe("file:../.vinext-local-types-package");
expect(localCloudflare.peerDependencies.vinext).toBe("file:../.vinext-local-package");
expect(localTypes.name).toBe("@vinext/types");
expect(fixture.dependencies.react).toBe("19.2.7");
expect(fixture.dependencies["react-dom"]).toBe("19.2.7");
expect(fixture.devDependencies["react-server-dom-webpack"]).toBe("19.2.7");
expect(fs.existsSync(path.join(appRoot, ".vinext-local-types-package/next/index.d.ts"))).toBe(
true,
);
} finally {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
});
it("initializes fixtures for the Node deployment platform", () => {
const script = fs.readFileSync(path.resolve("scripts/e2e-deploy.sh"), "utf8");
expect(script).toContain('"${VINEXT_BIN}" init --platform=node --skip-check --force');
});
it("runs the installed vinext binary directly after pnpm install", () => {
const script = fs.readFileSync(path.resolve("scripts/e2e-deploy.sh"), "utf8");
expect(script).toContain('VINEXT_BIN="./node_modules/.bin/vinext"');
expect(script).toContain('if [ ! -x "${VINEXT_BIN}" ]; then');
expect(script).toContain('"${VINEXT_BIN}" build --prerender-all');
expect(script).toContain('"${VINEXT_BIN}" start --port "${PORT}" --hostname 127.0.0.1');
expect(script).not.toContain("run_pnpm exec vinext");
});
it("normalizes non-pnpm packageManager pins before pnpm install", () => {
const script = fs.readFileSync(path.resolve("scripts/e2e-deploy.sh"), "utf8");
expect(script).toContain(
"originalPackageManager && !originalPackageManager.startsWith('pnpm@')",
);
expect(script).toContain("pkg.packageManager = harnessPackageManager");
expect(script).toContain("for vinext e2e deploy harness pnpm install");
});
it("removes install-time deprecation noise from application cliOutput", () => {
const script = fs.readFileSync(path.resolve("scripts/e2e-deploy.sh"), "utf8");
expect(script).toContain('"${VINEXT_DIR}/scripts/filter-e2e-install-log.sh"');
expect(script).toContain('>> "${BUILD_LOG}"');
const output = execFileSync("bash", ["scripts/filter-e2e-install-log.sh"], {
input:
"(node:8211) [DEP0169] DeprecationWarning: `url.parse()` is deprecated\n" +
"(Use `node --trace-deprecation ...` to show where the warning was created)\n" +
"WARN 1 deprecated subdependencies found: tsconfck@3.1.6\n" +
"Progress: resolved 370, reused 298, downloaded 0, added 292, done\n" +
"Application warning: keep this diagnostic\n",
encoding: "utf8",
});
expect(output).toBe(
"Progress: resolved 370, reused 298, downloaded 0, added 292, done\n" +
"Application warning: keep this diagnostic\n",
);
});
it("preserves matching diagnostics from application lifecycle scripts", () => {
const output = execFileSync("bash", ["scripts/filter-e2e-install-log.sh"], {
input:
"WARN 1 deprecated subdependencies found: tsconfck@3.1.6\n" +
"> application@1.0.0 postinstall /tmp/application\n" +
"> node postinstall.js\n" +
"(node:9211) [DEP0169] DeprecationWarning: `url.parse()` is deprecated\n" +
"(Use `node --trace-deprecation ...` to show where the warning was created)\n" +
"1 deprecated subdependencies found: application-owned diagnostic\n" +
"Application install error: keep this diagnostic\n" +
"\n" +
"(node:9212) [DEP0169] DeprecationWarning: `url.parse()` is deprecated\n" +
"2 deprecated subdependencies found: later application diagnostic\n" +
"Done in 1.2s using pnpm v11.1.1\n" +
"WARN 2 deprecated subdependencies found: harness@1.0.0\n",
encoding: "utf8",
});
expect(output).toBe(
"> application@1.0.0 postinstall /tmp/application\n" +
"> node postinstall.js\n" +
"(node:9211) [DEP0169] DeprecationWarning: `url.parse()` is deprecated\n" +
"(Use `node --trace-deprecation ...` to show where the warning was created)\n" +
"1 deprecated subdependencies found: application-owned diagnostic\n" +
"Application install error: keep this diagnostic\n" +
"\n" +
"(node:9212) [DEP0169] DeprecationWarning: `url.parse()` is deprecated\n" +
"2 deprecated subdependencies found: later application diagnostic\n" +
"Done in 1.2s using pnpm v11.1.1\n",
);
});
});