import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { mkdir, mkdtemp, readFile, rm, symlink, writeFile, } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import test from "node:test"; import { publishSource } from "../scripts/publish-source.mjs"; async function fixture(t) { const root = await mkdtemp(join(tmpdir(), "source-export-")); t.after(() => rm(root, { recursive: true, force: true })); for (const folder of ["src", "scripts", "tests", "assets", ".vercel", "dist"]) await mkdir(join(root, folder)); await writeFile( join(root, "src/page.html"), '
Hello & goodbye
', ); await writeFile( join(root, "scripts/build.mjs"), "export const build = true;\n", ); await writeFile(join(root, "tests/example.test.mjs"), "// Portable test\n"); await writeFile(join(root, "assets/logo.svg"), ""); await writeFile(join(root, "package.json"), '{"private":true}'); await writeFile(join(root, "README.md"), "# Setup\n"); await writeFile(join(root, ".env"), "PRIVATE=never-export"); await writeFile(join(root, ".vercel/project.json"), "private-project"); await writeFile(join(root, "src/.env"), "PRIVATE=never-export"); await writeFile(join(root, "dist/old.js"), "old-output"); return root; } test("source publication exposes portable text and local asset hashes only", async (t) => { const root = await fixture(t); await symlink(join(root, ".env"), join(root, "src/linked.js")); const destination = join(root, "dist"); await publishSource(root, destination); const manifest = JSON.parse( await readFile(join(destination, "source/manifest.json")), ); const bundle = JSON.parse( await readFile(join(destination, "source/bundle.json")), ); assert.equal(manifest.version, 1); assert.deepEqual( manifest.files.map(({ path }) => path), [ "README.md", "package.json", "scripts/build.mjs", "src/page.html", "tests/example.test.mjs", ], ); for (const entry of manifest.files) { const raw = await readFile(join(destination, entry.url)); assert.equal(entry.bytes, raw.length); assert.equal(entry.sha256, createHash("sha256").update(raw).digest("hex")); assert.equal( bundle.files.find(({ path }) => path === entry.path).content, raw.toString("utf8"), ); } assert.equal(manifest.assets[0].url, "assets/logo.svg"); assert.deepEqual(bundle.assets, manifest.assets); const index = await readFile(join(destination, "source/index.html"), "utf8"); assert.ok(index.includes("<main title="example">")); assert.ok(index.includes("Hello & goodbye")); assert.ok(!index.includes("
{ const root = await fixture(t); await writeFile( join(root, "src/local.js"), `const file = '${"/" + "Users/"}someone/private';`, ); await assert.rejects( publishSource(root, join(root, "dist")), /machine-specific path.*src\/local.js/i, ); }); test("allowlisted roots cannot follow a symlink into private files", async (t) => { const root = await fixture(t); await rm(join(root, "scripts"), { recursive: true }); await symlink(join(root, ".vercel"), join(root, "scripts")); const manifest = await publishSource(root, join(root, "dist")); assert.ok(!manifest.files.some(({ path }) => path.startsWith("scripts/"))); });