Skip to content

Commit 03acdb3

Browse files
committed
test(repo): verify packed consumers
1 parent 83db364 commit 03acdb3

2 files changed

Lines changed: 161 additions & 10 deletions

File tree

scripts/verify-packed-manifests.mjs

Lines changed: 131 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
#!/usr/bin/env node
22

33
import { execFileSync } from "node:child_process";
4-
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
4+
import {
5+
existsSync,
6+
mkdirSync,
7+
mkdtempSync,
8+
readdirSync,
9+
readFileSync,
10+
rmSync,
11+
writeFileSync,
12+
} from "node:fs";
513
import { extname, join, posix } from "node:path";
614
import { tmpdir } from "node:os";
715
import { fileURLToPath } from "node:url";
@@ -272,25 +280,139 @@ function verifyPackedWorkspace(workspace, filename) {
272280
verifyPackedJavaScriptImports(workspace, filename, packedFiles);
273281
}
274282

275-
function verifyWorkspace(workspace) {
283+
export function packageExportSpecifier(packageName, exportKey) {
284+
return exportKey === "." ? packageName : `${packageName}/${exportKey.replace(/^\.\//, "")}`;
285+
}
286+
287+
export function listPackedExportContracts(packedWorkspaces) {
288+
return packedWorkspaces.flatMap(({ packedPackage }) =>
289+
Object.entries(packedPackage.exports ?? {}).map(([exportKey, target]) => ({
290+
specifier: packageExportSpecifier(packedPackage.name, exportKey),
291+
typechecked: Boolean(target?.types),
292+
})),
293+
);
294+
}
295+
296+
function writeConsumerFixture(packDir, packedWorkspaces) {
297+
const fixtureDir = join(packDir, "consumer");
298+
mkdirSync(fixtureDir);
299+
const rootPackage = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));
300+
const dependencies = Object.fromEntries(
301+
packedWorkspaces.map(({ filename, packedPackage }) => [packedPackage.name, `file:${filename}`]),
302+
);
303+
dependencies.typescript = rootPackage.devDependencies.typescript;
304+
dependencies["@types/node"] = rootPackage.devDependencies["@types/node"];
305+
writeFileSync(
306+
join(fixtureDir, "package.json"),
307+
JSON.stringify(
308+
{ name: "packed-consumer", private: true, type: "module", dependencies },
309+
null,
310+
2,
311+
),
312+
);
313+
314+
const contracts = listPackedExportContracts(packedWorkspaces);
315+
const typeImports = contracts
316+
.filter(({ typechecked }) => typechecked)
317+
.map(({ specifier }) => `import ${JSON.stringify(specifier)};`)
318+
.join("\n");
319+
writeFileSync(join(fixtureDir, "consumer.ts"), `${typeImports}\n`);
320+
writeFileSync(
321+
join(fixtureDir, "tsconfig.json"),
322+
JSON.stringify(
323+
{
324+
compilerOptions: {
325+
target: "ES2022",
326+
module: "NodeNext",
327+
moduleResolution: "NodeNext",
328+
lib: ["ES2022", "DOM", "DOM.Iterable"],
329+
strict: true,
330+
skipLibCheck: true,
331+
noEmit: true,
332+
},
333+
include: ["consumer.ts"],
334+
},
335+
null,
336+
2,
337+
),
338+
);
339+
340+
const specifiers = contracts.map(({ specifier }) => specifier);
341+
writeFileSync(
342+
join(fixtureDir, "consumer-smoke.mjs"),
343+
`const specifiers = ${JSON.stringify(specifiers, null, 2)};\n` +
344+
`for (const specifier of specifiers) import.meta.resolve(specifier);\n` +
345+
`const sdk = await import("@hyperframes/sdk");\n` +
346+
`if (typeof sdk.openComposition !== "function") throw new Error("SDK root export missing");\n` +
347+
`const memory = await import("@hyperframes/sdk/adapters/memory");\n` +
348+
`if (typeof memory.createMemoryAdapter !== "function") throw new Error("memory adapter missing");\n` +
349+
`const fsAdapter = await import("@hyperframes/sdk/adapters/fs");\n` +
350+
`if (typeof fsAdapter.createFsAdapter !== "function") throw new Error("fs adapter missing");\n` +
351+
`await import("@hyperframes/core");\n` +
352+
`await import("@hyperframes/parsers");\n` +
353+
`await import("@hyperframes/lint");\n` +
354+
`await import("@hyperframes/studio-server");\n` +
355+
`console.log(\`Resolved \${specifiers.length} packed exports and loaded public SDK adapters.\`);\n`,
356+
);
357+
return fixtureDir;
358+
}
359+
360+
function verifyPackedConsumer(packDir, packedWorkspaces) {
361+
const fixtureDir = writeConsumerFixture(packDir, packedWorkspaces);
362+
execFileSync("bun", ["install", "--ignore-scripts"], {
363+
cwd: fixtureDir,
364+
encoding: "utf8",
365+
stdio: "pipe",
366+
});
367+
execFileSync(process.execPath, [join(fixtureDir, "node_modules", "typescript", "bin", "tsc")], {
368+
cwd: fixtureDir,
369+
encoding: "utf8",
370+
stdio: "pipe",
371+
});
372+
const smokeOutput = execFileSync("node", ["consumer-smoke.mjs"], {
373+
cwd: fixtureDir,
374+
encoding: "utf8",
375+
});
376+
const cliOutput = execFileSync(
377+
join(fixtureDir, "node_modules", ".bin", "hyperframes"),
378+
["--help"],
379+
{
380+
cwd: fixtureDir,
381+
encoding: "utf8",
382+
env: { ...process.env, HYPERFRAMES_TELEMETRY_DISABLED: "1" },
383+
},
384+
);
385+
if (!cliOutput.toLowerCase().includes("hyperframes")) {
386+
throw new Error("Packed CLI help did not identify HyperFrames");
387+
}
388+
console.log(smokeOutput.trim());
389+
console.log("Verified clean packed consumer install, type resolution, and CLI startup.");
390+
}
391+
392+
function packAndVerifyWorkspace(workspace, packDir) {
276393
const sourcePackageJson = readWorkspacePackage(workspace);
277-
if (sourcePackageJson.private) return;
394+
if (sourcePackageJson.private) return null;
278395

279396
assertPublishedExportsMatchSource(workspace, sourcePackageJson);
397+
const filename = packWorkspace(workspace, packDir);
398+
verifyPackedWorkspace(workspace, filename);
399+
const packedPackage = readPackedPackage(filename);
400+
console.log(`Verified ${workspace}: packed manifest is publish-safe.`);
401+
return { workspace, filename, packedPackage };
402+
}
280403

404+
function main() {
281405
const packDir = mkdtempSync(join(tmpdir(), "hyperframes-pack-"));
282406
try {
283-
verifyPackedWorkspace(workspace, packWorkspace(workspace, packDir));
284-
console.log(`Verified ${workspace}: packed manifest is publish-safe.`);
407+
const packedWorkspaces = listWorkspacePackageDirs()
408+
.map((workspace) => packAndVerifyWorkspace(workspace, packDir))
409+
.filter(Boolean);
410+
verifyPackedConsumer(packDir, packedWorkspaces);
285411
} finally {
286412
rmSync(packDir, { force: true, recursive: true });
287413
}
288414
}
289415

290-
function main() {
291-
listWorkspacePackageDirs().forEach(verifyWorkspace);
292-
}
293-
294416
if (process.argv[1] === fileURLToPath(import.meta.url)) {
295417
main();
296418
}

scripts/verify-packed-manifests.test.mjs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,38 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
44
import { tmpdir } from "node:os";
55
import { dirname, join } from "node:path";
66
import { describe, it } from "node:test";
7-
import { listPackedJavaScriptImportIssues } from "./verify-packed-manifests.mjs";
7+
import {
8+
listPackedExportContracts,
9+
listPackedJavaScriptImportIssues,
10+
packageExportSpecifier,
11+
} from "./verify-packed-manifests.mjs";
812

913
describe("packed manifest verifier", () => {
14+
it("derives consumer specifiers from the packed export map", () => {
15+
assert.equal(packageExportSpecifier("@hyperframes/sdk", "."), "@hyperframes/sdk");
16+
assert.equal(
17+
packageExportSpecifier("@hyperframes/sdk", "./adapters/fs"),
18+
"@hyperframes/sdk/adapters/fs",
19+
);
20+
assert.deepEqual(
21+
listPackedExportContracts([
22+
{
23+
packedPackage: {
24+
name: "@hyperframes/example",
25+
exports: {
26+
".": { import: "./dist/index.js", types: "./dist/index.d.ts" },
27+
"./runtime": "./dist/runtime.js",
28+
},
29+
},
30+
},
31+
]),
32+
[
33+
{ specifier: "@hyperframes/example", typechecked: true },
34+
{ specifier: "@hyperframes/example/runtime", typechecked: false },
35+
],
36+
);
37+
});
38+
1039
function withPackedFiles(files, packedFiles, callback) {
1140
const dir = mkdtempSync(join(tmpdir(), "hyperframes-pack-test-"));
1241
try {

0 commit comments

Comments
 (0)