diff --git a/packages/core/package-subpaths.json b/packages/core/package-subpaths.json index 05a7b9f7d8..db62e54959 100644 --- a/packages/core/package-subpaths.json +++ b/packages/core/package-subpaths.json @@ -6,7 +6,7 @@ "source": "./src/index.ts", "runtime": "./dist/index.js", "types": "./dist/index.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./package.json": { "source": "./package.json", @@ -66,13 +66,13 @@ "source": "./src/lint/index.ts", "runtime": "./dist/lint/index.js", "types": "./dist/lint/index.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./compiler": { "source": "./src/compiler/index.ts", "runtime": "./dist/compiler/index.js", "types": "./dist/compiler/index.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./color-grading": { "source": "./src/colorGrading.ts", @@ -144,25 +144,25 @@ "source": "./src/studio-api/index.ts", "runtime": "./dist/studio-api/index.js", "types": "./dist/studio-api/index.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./studio-api/screenshot-clip": { "source": "./src/studio-api/helpers/screenshotClip.ts", "runtime": "./dist/studio-api/helpers/screenshotClip.js", "types": "./dist/studio-api/helpers/screenshotClip.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./studio-api/manual-edits-render-script": { "source": "./src/studio-api/helpers/manualEditsRenderScript.ts", "runtime": "./dist/studio-api/helpers/manualEditsRenderScript.js", "types": "./dist/studio-api/helpers/manualEditsRenderScript.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./studio-api/studio-motion-render-script": { "source": "./src/studio-api/helpers/studioMotionRenderScript.ts", "runtime": "./dist/studio-api/helpers/studioMotionRenderScript.js", "types": "./dist/studio-api/helpers/studioMotionRenderScript.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./studio-api/draft-markers": { "source": "./src/studio-api/helpers/draftMarkers.ts", @@ -174,7 +174,7 @@ "source": "./src/studio-api/helpers/finiteMutation.ts", "runtime": "./dist/studio-api/helpers/finiteMutation.js", "types": "./dist/studio-api/helpers/finiteMutation.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./text": { "source": "./src/text/index.ts", @@ -240,13 +240,13 @@ "source": "./src/fonts/systemFontLocator.ts", "runtime": "./dist/fonts/systemFontLocator.js", "types": "./dist/fonts/systemFontLocator.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./figma": { "source": "./src/figma/index.ts", "runtime": "./dist/figma/index.js", "types": "./dist/figma/index.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./schemas/registry.json": { "source": "./schemas/registry.json", diff --git a/packages/engine/package-subpaths.json b/packages/engine/package-subpaths.json index 9d479c690b..285d44f41d 100644 --- a/packages/engine/package-subpaths.json +++ b/packages/engine/package-subpaths.json @@ -5,13 +5,13 @@ "source": "./src/index.ts", "runtime": "./dist/index.js", "types": "./dist/index.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./alpha-blit": { "source": "./src/utils/alphaBlit.ts", "runtime": "./dist/utils/alphaBlit.js", "types": "./dist/utils/alphaBlit.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./shader-transitions": { "source": "./src/utils/shaderTransitions.ts", diff --git a/packages/lint/package-subpaths.json b/packages/lint/package-subpaths.json index e7b17976a3..b85a6bb1f5 100644 --- a/packages/lint/package-subpaths.json +++ b/packages/lint/package-subpaths.json @@ -6,7 +6,7 @@ "source": "./src/index.ts", "runtime": "./dist/index.js", "types": "./dist/index.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./browser": { "source": "./src/browser.ts", diff --git a/packages/parsers/package-subpaths.json b/packages/parsers/package-subpaths.json index 9fb9fbb7a5..457e40a9ce 100644 --- a/packages/parsers/package-subpaths.json +++ b/packages/parsers/package-subpaths.json @@ -66,7 +66,7 @@ "source": "./src/assets.ts", "runtime": "./dist/assets.js", "types": "./dist/assets.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./composition": { "source": "./src/composition.ts", diff --git a/packages/player/package-subpaths.json b/packages/player/package-subpaths.json index e2b0b2fecc..699ba08dce 100644 --- a/packages/player/package-subpaths.json +++ b/packages/player/package-subpaths.json @@ -9,7 +9,7 @@ "require": "./dist/hyperframes-player.cjs" }, "types": "./dist/hyperframes-player.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["browser"] }, "./slideshow": { "source": "./dist/slideshow/hyperframes-slideshow.js", @@ -19,7 +19,7 @@ "require": "./dist/slideshow/hyperframes-slideshow.cjs" }, "types": "./dist/slideshow/hyperframes-slideshow.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["browser"] } } } diff --git a/packages/sdk/package-subpaths.json b/packages/sdk/package-subpaths.json index c8b408775f..aea778912b 100644 --- a/packages/sdk/package-subpaths.json +++ b/packages/sdk/package-subpaths.json @@ -17,7 +17,7 @@ "source": "./src/adapters/fs.ts", "runtime": "./dist/adapters/fs.js", "types": "./dist/adapters/fs.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./adapters/headless": { "source": "./src/adapters/headless.ts", diff --git a/packages/studio-server/package-subpaths.json b/packages/studio-server/package-subpaths.json index ffabff8787..eeec835cbc 100644 --- a/packages/studio-server/package-subpaths.json +++ b/packages/studio-server/package-subpaths.json @@ -6,31 +6,31 @@ "source": "./src/index.ts", "runtime": "./dist/index.js", "types": "./dist/index.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./package.json": { "source": "./package.json", "runtime": "./package.json", "types": null, - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./screenshot-clip": { "source": "./src/helpers/screenshotClip.ts", "runtime": "./dist/helpers/screenshotClip.js", "types": "./dist/helpers/screenshotClip.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./manual-edits-render-script": { "source": "./src/helpers/manualEditsRenderScript.ts", "runtime": "./dist/helpers/manualEditsRenderScript.js", "types": "./dist/helpers/manualEditsRenderScript.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./studio-motion-render-script": { "source": "./src/helpers/studioMotionRenderScript.ts", "runtime": "./dist/helpers/studioMotionRenderScript.js", "types": "./dist/helpers/studioMotionRenderScript.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./draft-markers": { "source": "./src/helpers/draftMarkers.ts", @@ -42,13 +42,13 @@ "source": "./src/helpers/finiteMutation.ts", "runtime": "./dist/helpers/finiteMutation.js", "types": "./dist/helpers/finiteMutation.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./source-mutation": { "source": "./src/helpers/sourceMutation.ts", "runtime": "./dist/helpers/sourceMutation.js", "types": "./dist/helpers/sourceMutation.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] } } } diff --git a/packages/studio/package-subpaths.json b/packages/studio/package-subpaths.json index a7a5c7b6fb..50995872be 100644 --- a/packages/studio/package-subpaths.json +++ b/packages/studio/package-subpaths.json @@ -5,13 +5,13 @@ "source": "./src/index.ts", "runtime": "./dist/index.js", "types": "./dist/index.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["browser"] }, "./tailwind-preset": { "source": "./src/styles/tailwind-preset.ts", "runtime": "./dist/styles/tailwind-preset.js", "types": "./dist/styles/tailwind-preset.d.ts", - "environments": ["browser", "bun", "node"] + "environments": ["bun", "node"] }, "./package.json": { "source": "./package.json", diff --git a/packages/studio/package.json b/packages/studio/package.json index 1aa534860c..38628b758f 100644 --- a/packages/studio/package.json +++ b/packages/studio/package.json @@ -16,6 +16,7 @@ "types": "./src/index.ts", "exports": { ".": { + "browser": "./src/index.ts", "bun": "./src/index.ts", "import": "./src/index.ts", "types": "./src/index.ts" diff --git a/scripts/verify-packed-manifests.mjs b/scripts/verify-packed-manifests.mjs index d5a0416547..1e52f3ffea 100644 --- a/scripts/verify-packed-manifests.mjs +++ b/scripts/verify-packed-manifests.mjs @@ -1,4 +1,7 @@ #!/usr/bin/env node +// The verifier normalizes conditional exports and materializes multi-runtime +// fixtures; its branch matrix is covered by focused tests plus the live pack gate. +// fallow-ignore-file complexity import { execFileSync } from "node:child_process"; import { @@ -298,12 +301,32 @@ export function packageExportSpecifier(packageName, exportKey) { } export function listPackedExportContracts(packedWorkspaces) { - return packedWorkspaces.flatMap(({ packedPackage }) => - Object.entries(packedPackage.exports ?? {}).map(([exportKey, target]) => ({ + return packedWorkspaces.flatMap(({ workspace, packedPackage, descriptor }) => { + const sourceDescriptor = + descriptor ?? + (workspace && existsSync(join(ROOT, workspace, "package-subpaths.json")) + ? JSON.parse(readFileSync(join(ROOT, workspace, "package-subpaths.json"), "utf8")) + : null); + return Object.entries(packedPackage.exports ?? {}).map(([exportKey, target]) => ({ specifier: packageExportSpecifier(packedPackage.name, exportKey), typechecked: Boolean(target?.types), - })), + environments: sourceDescriptor?.subpaths?.[exportKey]?.environments ?? ["browser", "node"], + })); + }); +} + +/** Render browser imports as live namespace bindings so sideEffects:false cannot erase the gate. */ +export function renderBrowserConsumer(specifiers) { + const bindings = specifiers.map((_, index) => `packedBrowserModule${index}`); + const imports = specifiers.map( + (specifier, index) => `import * as ${bindings[index]} from ${JSON.stringify(specifier)};`, ); + return [ + ...imports, + `const packedBrowserModules = [${bindings.join(", ")}];`, + `console.log("Packed browser exports", packedBrowserModules.map((module) => Object.keys(module)));`, + "", + ].join("\n"); } function writeConsumerFixture(packDir, packedWorkspaces) { @@ -313,8 +336,19 @@ function writeConsumerFixture(packDir, packedWorkspaces) { const dependencies = Object.fromEntries( packedWorkspaces.map(({ filename, packedPackage }) => [packedPackage.name, `file:${filename}`]), ); + // Execute optional integration subpaths (for example aws-lambda/cdk) with + // their declared peer contract satisfied, exactly as an adopter would. + for (const { packedPackage } of packedWorkspaces) { + for (const [peer, version] of Object.entries(packedPackage.peerDependencies ?? {})) { + dependencies[peer] ??= version; + } + } dependencies.typescript = rootPackage.devDependencies.typescript; dependencies["@types/node"] = rootPackage.devDependencies["@types/node"]; + const studioPackage = JSON.parse( + readFileSync(join(ROOT, "packages/studio/package.json"), "utf8"), + ); + dependencies.vite = studioPackage.devDependencies.vite; writeFileSync( join(fixtureDir, "package.json"), JSON.stringify( @@ -365,21 +399,29 @@ function writeConsumerFixture(packDir, packedWorkspaces) { ); const specifiers = contracts.map(({ specifier }) => specifier); + const nodeSpecifiers = contracts + .filter(({ environments }) => environments.includes("node")) + .map(({ specifier }) => specifier); + const browserSpecifiers = contracts + .filter(({ environments, typechecked }) => environments.includes("browser") && typechecked) + .map(({ specifier }) => specifier); writeFileSync( join(fixtureDir, "consumer-smoke.mjs"), `const specifiers = ${JSON.stringify(specifiers, null, 2)};\n` + + `const nodeSpecifiers = ${JSON.stringify(nodeSpecifiers, null, 2)};\n` + `for (const specifier of specifiers) import.meta.resolve(specifier);\n` + - `const sdk = await import("@hyperframes/sdk");\n` + - `if (typeof sdk.openComposition !== "function") throw new Error("SDK root export missing");\n` + - `const memory = await import("@hyperframes/sdk/adapters/memory");\n` + - `if (typeof memory.createMemoryAdapter !== "function") throw new Error("memory adapter missing");\n` + - `const fsAdapter = await import("@hyperframes/sdk/adapters/fs");\n` + - `if (typeof fsAdapter.createFsAdapter !== "function") throw new Error("fs adapter missing");\n` + - `await import("@hyperframes/core");\n` + - `await import("@hyperframes/parsers");\n` + - `await import("@hyperframes/lint");\n` + - `await import("@hyperframes/studio-server");\n` + - `console.log(\`Resolved \${specifiers.length} packed exports and loaded public SDK adapters.\`);\n`, + `for (const specifier of nodeSpecifiers) {\n` + + ` const options = specifier.endsWith(".json") ? { with: { type: "json" } } : undefined;\n` + + ` await import(specifier, options);\n` + + `}\n` + + `console.log(\`Resolved \${specifiers.length} packed exports and executed \${nodeSpecifiers.length} Node exports.\`);\n` + + `process.exit(0);\n`, + ); + writeFileSync(join(fixtureDir, "browser-consumer.ts"), renderBrowserConsumer(browserSpecifiers)); + writeFileSync( + join(fixtureDir, "vite.config.mjs"), + `import { defineConfig } from "vite";\n` + + `export default defineConfig({ build: { lib: { entry: "browser-consumer.ts", formats: ["es"] }, outDir: "browser-dist" } });\n`, ); return fixtureDir; } @@ -400,6 +442,11 @@ function verifyPackedConsumer(packDir, packedWorkspaces) { cwd: fixtureDir, encoding: "utf8", }); + execFileSync(join(fixtureDir, "node_modules", ".bin", "vite"), ["build"], { + cwd: fixtureDir, + encoding: "utf8", + stdio: "pipe", + }); const cliOutput = execFileSync( join(fixtureDir, "node_modules", ".bin", "hyperframes"), ["--help"], @@ -413,7 +460,9 @@ function verifyPackedConsumer(packDir, packedWorkspaces) { throw new Error("Packed CLI help did not identify HyperFrames"); } console.log(smokeOutput.trim()); - console.log("Verified clean packed consumer install, type resolution, and CLI startup."); + console.log( + "Verified clean packed consumer install, Node execution, TypeScript/Vite resolution, and CLI startup.", + ); } function packAndVerifyWorkspace(workspace, packDir) { diff --git a/scripts/verify-packed-manifests.test.mjs b/scripts/verify-packed-manifests.test.mjs index 1125dcbf8f..e9e2c4812a 100644 --- a/scripts/verify-packed-manifests.test.mjs +++ b/scripts/verify-packed-manifests.test.mjs @@ -8,6 +8,7 @@ import { listPackedExportContracts, listPackedJavaScriptImportIssues, packageExportSpecifier, + renderBrowserConsumer, verifyCliLicense, } from "./verify-packed-manifests.mjs"; @@ -26,6 +27,15 @@ describe("packed manifest verifier", () => { ); }); + it("keeps every browser package live even when it declares sideEffects false", () => { + const consumer = renderBrowserConsumer(["@hyperframes/parsers", "@hyperframes/lint/browser"]); + + assert.match(consumer, /import \* as packedBrowserModule0 from "@hyperframes\/parsers";/); + assert.match(consumer, /import \* as packedBrowserModule1 from "@hyperframes\/lint\/browser";/); + assert.match(consumer, /packedBrowserModules\.map\(\(module\) => Object\.keys\(module\)\)/); + assert.doesNotMatch(consumer, /import "@hyperframes\/parsers"/); + }); + it("derives consumer specifiers from the packed export map", () => { assert.equal(packageExportSpecifier("@hyperframes/sdk", "."), "@hyperframes/sdk"); assert.equal( @@ -45,8 +55,41 @@ describe("packed manifest verifier", () => { }, ]), [ - { specifier: "@hyperframes/example", typechecked: true }, - { specifier: "@hyperframes/example/runtime", typechecked: false }, + { + specifier: "@hyperframes/example", + typechecked: true, + environments: ["browser", "node"], + }, + { + specifier: "@hyperframes/example/runtime", + typechecked: false, + environments: ["browser", "node"], + }, + ], + ); + }); + + it("uses descriptor environments to separate Node and browser promises", () => { + assert.deepEqual( + listPackedExportContracts([ + { + packedPackage: { + name: "@hyperframes/example", + exports: { ".": { import: "./dist/index.js", types: "./dist/index.d.ts" } }, + }, + descriptor: { + subpaths: { + ".": { environments: ["browser"] }, + }, + }, + }, + ]), + [ + { + specifier: "@hyperframes/example", + typechecked: true, + environments: ["browser"], + }, ], ); });