From 74d64dbf73f4ee4c19a5be680ec4267daaf2996e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Sun, 21 Jun 2026 14:24:05 +0000 Subject: [PATCH] fix: publish Node-compatible package entrypoints --- bun.lock | 21 +- packages/core/package.json | 2 +- .../core/scripts/rewrite-esm-extensions.ts | 77 +++++ packages/engine/package.json | 24 ++ packages/studio/package.json | 19 +- .../src/styles/tailwind-preset.shared.js | 34 +++ packages/studio/src/styles/tailwind-preset.ts | 3 + packages/studio/tailwind.config.js | 32 +- packages/studio/tsconfig.lib.json | 10 + packages/studio/tsup.config.ts | 39 +++ scripts/verify-packed-manifests.mjs | 287 +++++++++++++++--- 11 files changed, 457 insertions(+), 91 deletions(-) create mode 100644 packages/core/scripts/rewrite-esm-extensions.ts create mode 100644 packages/studio/src/styles/tailwind-preset.shared.js create mode 100644 packages/studio/src/styles/tailwind-preset.ts create mode 100644 packages/studio/tsconfig.lib.json create mode 100644 packages/studio/tsup.config.ts diff --git a/bun.lock b/bun.lock index 58c8cd63f1..92e55ee580 100644 --- a/bun.lock +++ b/bun.lock @@ -22,7 +22,7 @@ }, "packages/aws-lambda": { "name": "@hyperframes/aws-lambda", - "version": "0.6.113", + "version": "0.6.119", "dependencies": { "@aws-sdk/client-s3": "^3.700.0", "@aws-sdk/client-sfn": "^3.700.0", @@ -54,7 +54,7 @@ }, "packages/cli": { "name": "@hyperframes/cli", - "version": "0.6.113", + "version": "0.6.119", "bin": { "hyperframes": "./dist/cli.js", }, @@ -101,7 +101,7 @@ }, "packages/core": { "name": "@hyperframes/core", - "version": "0.6.113", + "version": "0.6.119", "dependencies": { "@babel/parser": "^7.27.0", "@chenglou/pretext": "^0.0.5", @@ -135,7 +135,7 @@ }, "packages/engine": { "name": "@hyperframes/engine", - "version": "0.6.113", + "version": "0.6.119", "dependencies": { "@hono/node-server": "^1.13.0", "@hyperframes/core": "workspace:^", @@ -153,7 +153,7 @@ }, "packages/gcp-cloud-run": { "name": "@hyperframes/gcp-cloud-run", - "version": "0.6.113", + "version": "0.6.119", "dependencies": { "@google-cloud/storage": "^7.14.0", "@google-cloud/workflows": "^4.2.0", @@ -173,7 +173,7 @@ }, "packages/player": { "name": "@hyperframes/player", - "version": "0.6.113", + "version": "0.6.119", "dependencies": { "@hyperframes/core": "workspace:*", }, @@ -188,7 +188,7 @@ }, "packages/producer": { "name": "@hyperframes/producer", - "version": "0.6.113", + "version": "0.6.119", "dependencies": { "@fontsource/archivo-black": "^5.2.8", "@fontsource/eb-garamond": "^5.2.7", @@ -229,7 +229,7 @@ }, "packages/sdk": { "name": "@hyperframes/sdk", - "version": "0.6.113", + "version": "0.6.119", "dependencies": { "@hyperframes/core": "workspace:*", "linkedom": "^0.18.12", @@ -254,7 +254,7 @@ }, "packages/shader-transitions": { "name": "@hyperframes/shader-transitions", - "version": "0.6.113", + "version": "0.6.119", "dependencies": { "html2canvas": "^1.4.1", }, @@ -266,7 +266,7 @@ }, "packages/studio": { "name": "@hyperframes/studio", - "version": "0.6.113", + "version": "0.6.119", "dependencies": { "@codemirror/autocomplete": "^6.20.1", "@codemirror/commands": "^6.10.3", @@ -297,6 +297,7 @@ "postcss": "^8.4.0", "puppeteer-core": "^24.40.0", "tailwindcss": "^3.4.0", + "tsup": "^8.0.0", "typescript": "^5.0.0", "vite": "^6.4.2", "vitest": "^3.2.4", diff --git a/packages/core/package.json b/packages/core/package.json index cd2bca59e9..eb6c447e3d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -258,7 +258,7 @@ "types": "./dist/index.d.ts" }, "scripts": { - "build": "bun run build:hyperframes-runtime && tsc", + "build": "bun run build:hyperframes-runtime && tsc && tsx scripts/rewrite-esm-extensions.ts", "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", diff --git a/packages/core/scripts/rewrite-esm-extensions.ts b/packages/core/scripts/rewrite-esm-extensions.ts new file mode 100644 index 0000000000..2a5878ad83 --- /dev/null +++ b/packages/core/scripts/rewrite-esm-extensions.ts @@ -0,0 +1,77 @@ +import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; +import { dirname, extname, join, normalize, resolve } from "node:path"; + +const distDir = resolve(import.meta.dirname, "../dist"); +const runtimeExtensions = new Set([".js", ".mjs", ".cjs", ".json", ".wasm", ".node"]); + +function listJavaScriptFiles(dir: string): string[] { + const files: string[] = []; + for (const entry of readdirSync(dir)) { + const fullPath = join(dir, entry); + const stat = statSync(fullPath); + if (stat.isDirectory()) { + files.push(...listJavaScriptFiles(fullPath)); + } else if (entry.endsWith(".js")) { + files.push(fullPath); + } + } + return files; +} + +function hasRuntimeExtension(specifier: string): boolean { + return runtimeExtensions.has(extname(specifier)); +} + +function isRelativeSpecifier(specifier: string): boolean { + return specifier.startsWith("./") || specifier.startsWith("../"); +} + +function resolveExistingJsSpecifier(fromFile: string, specifier: string): string | undefined { + const absolute = resolve(dirname(fromFile), specifier); + const jsFile = `${absolute}.js`; + const indexFile = join(absolute, "index.js"); + + if (existsSync(jsFile)) return `${specifier}.js`; + if (existsSync(indexFile)) return `${specifier}/index.js`; +} + +function resolveRuntimeSpecifier(fromFile: string, specifier: string): string { + if (!isRelativeSpecifier(specifier)) return specifier; + if (hasRuntimeExtension(specifier)) return specifier; + + return resolveExistingJsSpecifier(fromFile, specifier) ?? specifier; +} + +function rewriteSpecifiers(filePath: string, source: string): string { + const patterns = [ + /(from\s+["'])(\.\.?\/[^"']+)(["'])/g, + /(import\s+["'])(\.\.?\/[^"']+)(["'])/g, + /(import\(\s*["'])(\.\.?\/[^"']+)(["']\s*\))/g, + ]; + + return patterns.reduce( + (nextSource, pattern) => + nextSource.replace(pattern, (_match, prefix: string, specifier: string, suffix: string) => { + return `${prefix}${resolveRuntimeSpecifier(filePath, specifier)}${suffix}`; + }), + source, + ); +} + +let changed = 0; +for (const filePath of listJavaScriptFiles(distDir)) { + const source = readFileSync(filePath, "utf8"); + const rewritten = rewriteSpecifiers(filePath, source); + if (rewritten !== source) { + writeFileSync(filePath, rewritten); + changed += 1; + } +} + +console.log( + JSON.stringify({ + event: "core_esm_extensions_rewritten", + distDir: normalize(distDir), + changedFiles: changed, + }), +); diff --git a/packages/engine/package.json b/packages/engine/package.json index 017420b666..50a6b0d1d8 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -7,6 +7,10 @@ "url": "https://github.com/heygen-com/hyperframes", "directory": "packages/engine" }, + "files": [ + "dist", + "README.md" + ], "type": "module", "main": "./src/index.ts", "types": "./src/index.ts", @@ -15,6 +19,26 @@ "./alpha-blit": "./src/utils/alphaBlit.ts", "./shader-transitions": "./src/utils/shaderTransitions.ts" }, + "publishConfig": { + "access": "public", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./alpha-blit": { + "import": "./dist/utils/alphaBlit.js", + "types": "./dist/utils/alphaBlit.d.ts" + }, + "./shader-transitions": { + "import": "./dist/utils/shaderTransitions.js", + "types": "./dist/utils/shaderTransitions.d.ts" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts" + }, "scripts": { "build": "tsc", "test": "vitest run", diff --git a/packages/studio/package.json b/packages/studio/package.json index 8523b80b2b..39c31da265 100644 --- a/packages/studio/package.json +++ b/packages/studio/package.json @@ -19,9 +19,25 @@ "./tailwind-preset": "./src/styles/tailwind-preset.ts", "./package.json": "./package.json" }, + "publishConfig": { + "access": "public", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./tailwind-preset": { + "import": "./dist/styles/tailwind-preset.js", + "types": "./dist/styles/tailwind-preset.d.ts" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts" + }, "scripts": { "dev": "vite", - "build": "vite build", + "build": "vite build && tsup", "typecheck": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest" @@ -56,6 +72,7 @@ "postcss": "^8.4.0", "puppeteer-core": "^24.40.0", "tailwindcss": "^3.4.0", + "tsup": "^8.0.0", "typescript": "^5.0.0", "vite": "^6.4.2", "vitest": "^3.2.4", diff --git a/packages/studio/src/styles/tailwind-preset.shared.js b/packages/studio/src/styles/tailwind-preset.shared.js new file mode 100644 index 0000000000..35142881ea --- /dev/null +++ b/packages/studio/src/styles/tailwind-preset.shared.js @@ -0,0 +1,34 @@ +const studioPreset = { + theme: { + extend: { + colors: { + studio: { + bg: "#0a0a0a", + surface: "#141414", + border: "#262626", + text: "#e5e5e5", + muted: "#737373", + accent: "#3CE6AC", + }, + panel: { + bg: "#0C0C0E", + input: "#161618", + surface: "#18181B", + hover: "#27272A", + border: "#1E1E1E", + "border-input": "#27272A", + "text-1": "#E4E4E7", + "text-2": "#A1A1AA", + "text-3": "#71717A", + "text-4": "#52525B", + "text-5": "#3F3F46", + accent: "#3CE6AC", + danger: "#EF4444", + }, + }, + }, + }, + plugins: [], +}; + +export default studioPreset; diff --git a/packages/studio/src/styles/tailwind-preset.ts b/packages/studio/src/styles/tailwind-preset.ts new file mode 100644 index 0000000000..1115a569ca --- /dev/null +++ b/packages/studio/src/styles/tailwind-preset.ts @@ -0,0 +1,3 @@ +import studioPreset from "./tailwind-preset.shared.js"; + +export default studioPreset; diff --git a/packages/studio/tailwind.config.js b/packages/studio/tailwind.config.js index 3c9488b5e7..b7bdcd075a 100644 --- a/packages/studio/tailwind.config.js +++ b/packages/studio/tailwind.config.js @@ -1,39 +1,11 @@ import { resolve, dirname } from "path"; import { fileURLToPath } from "url"; +import studioPreset from "./src/styles/tailwind-preset.shared.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); /** @type {import('tailwindcss').Config} */ export default { content: [resolve(__dirname, "./src/**/*.{ts,tsx}"), resolve(__dirname, "./index.html")], - theme: { - extend: { - colors: { - studio: { - bg: "#0a0a0a", - surface: "#141414", - border: "#262626", - text: "#e5e5e5", - muted: "#737373", - accent: "#3CE6AC", - }, - panel: { - bg: "#0C0C0E", - input: "#161618", - surface: "#18181B", - hover: "#27272A", - border: "#1E1E1E", - "border-input": "#27272A", - "text-1": "#E4E4E7", - "text-2": "#A1A1AA", - "text-3": "#71717A", - "text-4": "#52525B", - "text-5": "#3F3F46", - accent: "#3CE6AC", - danger: "#EF4444", - }, - }, - }, - }, - plugins: [], + ...studioPreset, }; diff --git a/packages/studio/tsconfig.lib.json b/packages/studio/tsconfig.lib.json new file mode 100644 index 0000000000..fb1e6cc6c7 --- /dev/null +++ b/packages/studio/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "incremental": false, + "noEmit": false, + "paths": {}, + "rootDir": "src" + }, + "exclude": ["dist", "node_modules", "src/**/*.test.ts", "src/test-setup.ts"] +} diff --git a/packages/studio/tsup.config.ts b/packages/studio/tsup.config.ts new file mode 100644 index 0000000000..c889ed4331 --- /dev/null +++ b/packages/studio/tsup.config.ts @@ -0,0 +1,39 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + tsconfig: "tsconfig.lib.json", + entry: { + index: "src/index.ts", + "styles/tailwind-preset": "src/styles/tailwind-preset.ts", + }, + format: ["esm"], + dts: true, + sourcemap: true, + clean: false, + splitting: true, + external: [ + "@codemirror/autocomplete", + "@codemirror/commands", + "@codemirror/lang-css", + "@codemirror/lang-html", + "@codemirror/lang-javascript", + "@codemirror/lang-markdown", + "@codemirror/language", + "@codemirror/search", + "@codemirror/state", + "@codemirror/theme-one-dark", + "@codemirror/view", + "@hyperframes/core", + "@hyperframes/player", + "@hyperframes/sdk", + "@phosphor-icons/react", + "bpm-detective", + "dompurify", + "marked", + "mediabunny", + "react", + "react-dom", + "react/jsx-runtime", + "zustand", + ], +}); diff --git a/scripts/verify-packed-manifests.mjs b/scripts/verify-packed-manifests.mjs index fcaf19cec4..d423ecc1e1 100644 --- a/scripts/verify-packed-manifests.mjs +++ b/scripts/verify-packed-manifests.mjs @@ -2,12 +2,13 @@ import { execFileSync } from "node:child_process"; import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs"; -import { join } from "node:path"; +import { extname, join } from "node:path"; import { tmpdir } from "node:os"; const ROOT = join(import.meta.dirname, ".."); const PACKAGES_DIR = join(ROOT, "packages"); const DEP_FIELDS = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]; +const RUNTIME_IMPORT_EXTENSIONS = new Set([".js", ".mjs", ".cjs", ".json", ".wasm", ".node"]); function listWorkspacePackageDirs() { return readdirSync(PACKAGES_DIR) @@ -16,23 +17,187 @@ function listWorkspacePackageDirs() { } function listWorkspaceRefs(pkg) { - const refs = []; + return DEP_FIELDS.flatMap((field) => + Object.entries(pkg[field] || {}) + .filter(([, spec]) => String(spec).startsWith("workspace:")) + .map(([depName, spec]) => `${field}:${depName}=${spec}`), + ); +} + +function listMissingPublishedExports(pkg) { + if (!pkg.exports || !pkg.publishConfig?.exports) return []; + + return Object.keys(pkg.exports).filter((exportKey) => !(exportKey in pkg.publishConfig.exports)); +} + +function normalizePackagePath(path) { + return path.replace(/^\.\//, ""); +} + +function isPackageLocalPath(path) { + return path.startsWith("./") || path.startsWith("dist/") || path.startsWith("src/"); +} + +function isPublishedSourceEntrypoint(path) { + return /^src\/.*\.(?:ts|tsx|mts|cts)$/.test(normalizePackagePath(path)); +} + +function appendManifestEntry(entries, trail, path) { + entries.push({ field: trail.join(".") || "", path }); +} + +function collectStringEntrypoint(value, trail, entries) { + appendManifestEntry(entries, trail, value); + return entries; +} + +function collectArrayEntrypoints(value, trail, entries) { + value.forEach((item, index) => + collectManifestEntrypoints(item, [...trail, String(index)], entries), + ); + return entries; +} + +function collectObjectEntrypoints(value, trail, entries) { + Object.entries(value).forEach(([key, nested]) => + collectManifestEntrypoints(nested, [...trail, key], entries), + ); + return entries; +} + +function isStringEntrypoint(value) { + return typeof value === "string"; +} - for (const field of DEP_FIELDS) { - for (const [depName, spec] of Object.entries(pkg[field] || {})) { - if (String(spec).startsWith("workspace:")) { - refs.push(`${field}:${depName}=${spec}`); - } +function isArrayEntrypoint(value) { + return Array.isArray(value); +} + +function isObjectEntrypoint(value) { + return Boolean(value) && typeof value === "object"; +} + +const MANIFEST_ENTRY_COLLECTORS = [ + [isStringEntrypoint, collectStringEntrypoint], + [isArrayEntrypoint, collectArrayEntrypoints], + [isObjectEntrypoint, collectObjectEntrypoints], +]; + +function collectManifestEntrypoints(value, trail = [], entries = []) { + const collector = MANIFEST_ENTRY_COLLECTORS.find(([matches]) => matches(value)); + return collector ? collector[1](value, trail, entries) : entries; +} + +function listPackedEntrypoints(pkg) { + const entries = []; + + for (const field of ["main", "module", "types", "typings"]) { + if (typeof pkg[field] === "string") { + entries.push({ field, path: pkg[field] }); } } - return refs; + if (pkg.exports != null) { + entries.push(...collectManifestEntrypoints(pkg.exports, ["exports"])); + } + + return entries.filter((entry) => isPackageLocalPath(entry.path)); } -function listMissingPublishedExports(pkg) { - if (!pkg.exports || !pkg.publishConfig?.exports) return []; +function listPackedFiles(filename) { + const output = execFileSync("tar", ["-tf", filename], { + cwd: ROOT, + encoding: "utf8", + }); - return Object.keys(pkg.exports).filter((exportKey) => !(exportKey in pkg.publishConfig.exports)); + return new Set( + output + .split("\n") + .filter(Boolean) + .map((path) => path.replace(/^package\//, "")), + ); +} + +function stripSpecifierQuery(specifier) { + return specifier.replace(/[?#].*$/, ""); +} + +function hasExplicitRuntimeExtension(specifier) { + return RUNTIME_IMPORT_EXTENSIONS.has(extname(stripSpecifierQuery(specifier))); +} + +function listRelativeImportSpecifiers(source) { + const patterns = [ + /^\s*import\s+["'](\.\.?\/[^"']+)["']/gm, + /^\s*(?:import|export)\s+[^;]*?\s+from\s+["'](\.\.?\/[^"']+)["']/gm, + ]; + const specifiers = []; + + for (const pattern of patterns) { + for (const match of source.matchAll(pattern)) { + specifiers.push({ index: match.index, specifier: match[1] }); + } + } + + return specifiers; +} + +function lineNumberAt(source, index) { + return source.slice(0, index).split("\n").length; +} + +function readPackedFile(filename, file) { + return execFileSync("tar", ["-xOf", filename, `package/${file}`], { + cwd: ROOT, + encoding: "utf8", + maxBuffer: 64 * 1024 * 1024, + }); +} + +function verifyPackedEntrypoints(workspace, packedPackage, packedFiles) { + const entries = listPackedEntrypoints(packedPackage); + const sourceEntries = entries.filter((entry) => isPublishedSourceEntrypoint(entry.path)); + if (sourceEntries.length > 0) { + throw new Error( + `Packed manifest for ${workspace} exposes source TypeScript entrypoints: ` + + sourceEntries.map((entry) => `${entry.field}:${entry.path}`).join(", "), + ); + } + + const missingEntries = entries.filter((entry) => { + const normalized = normalizePackagePath(entry.path); + return !normalized.includes("*") && !packedFiles.has(normalized); + }); + + if (missingEntries.length > 0) { + throw new Error( + `Packed manifest for ${workspace} points at missing files: ` + + missingEntries.map((entry) => `${entry.field}:${entry.path}`).join(", "), + ); + } +} + +function listJavaScriptImportIssues(filename, file) { + const source = readPackedFile(filename, file); + return listRelativeImportSpecifiers(source) + .filter(({ specifier }) => !hasExplicitRuntimeExtension(specifier)) + .map(({ index, specifier }) => `${file}:${lineNumberAt(source, index)} imports ${specifier}`); +} + +function listPackedJavaScriptImportIssues(filename, packedFiles) { + return [...packedFiles] + .filter((file) => file.endsWith(".js")) + .flatMap((file) => listJavaScriptImportIssues(filename, file)); +} + +function verifyPackedJavaScriptImports(workspace, filename, packedFiles) { + const importIssues = listPackedJavaScriptImportIssues(filename, packedFiles); + if (importIssues.length > 0) { + throw new Error( + `Packed JavaScript for ${workspace} contains Node-incompatible relative imports: ` + + importIssues.slice(0, 10).join(", "), + ); + } } function parsePackJson(output, workspace) { @@ -44,47 +209,71 @@ function parsePackJson(output, workspace) { } } -function main() { - for (const workspace of listWorkspacePackageDirs()) { - const sourcePackageJson = JSON.parse( - readFileSync(join(ROOT, workspace, "package.json"), "utf8"), - ); - if (sourcePackageJson.private) continue; +function readWorkspacePackage(workspace) { + return JSON.parse(readFileSync(join(ROOT, workspace, "package.json"), "utf8")); +} - const missingPublishedExports = listMissingPublishedExports(sourcePackageJson); - if (missingPublishedExports.length > 0) { - throw new Error( - `${workspace} publishConfig.exports is missing source exports: ${missingPublishedExports.join(", ")}`, - ); - } +function assertPublishedExportsMatchSource(workspace, sourcePackageJson) { + const missingPublishedExports = listMissingPublishedExports(sourcePackageJson); + if (missingPublishedExports.length === 0) return; - if (listWorkspaceRefs(sourcePackageJson).length === 0) continue; - - const packDir = mkdtempSync(join(tmpdir(), "hyperframes-pack-")); - const packOutput = execFileSync("pnpm", ["pack", "--json", "--pack-destination", packDir], { - cwd: join(ROOT, workspace), - encoding: "utf8", - }); - const [{ filename }] = parsePackJson(packOutput, workspace); - - try { - const packedPackageJson = execFileSync("tar", ["-xOf", filename, "package/package.json"], { - cwd: ROOT, - encoding: "utf8", - }); - const packedRefs = listWorkspaceRefs(JSON.parse(packedPackageJson)); - - if (packedRefs.length > 0) { - throw new Error( - `Packed manifest for ${workspace} still contains workspace refs: ${packedRefs.join(", ")}`, - ); - } - - console.log(`Verified ${workspace}: packed manifest is publish-safe.`); - } finally { - rmSync(packDir, { force: true, recursive: true }); - } + throw new Error( + `${workspace} publishConfig.exports is missing source exports: ${missingPublishedExports.join(", ")}`, + ); +} + +function packWorkspace(workspace, packDir) { + const packOutput = execFileSync("pnpm", ["pack", "--json", "--pack-destination", packDir], { + cwd: join(ROOT, workspace), + encoding: "utf8", + }); + const [{ filename }] = parsePackJson(packOutput, workspace); + return filename; +} + +function readPackedPackage(filename) { + const packedPackageJson = execFileSync("tar", ["-xOf", filename, "package/package.json"], { + cwd: ROOT, + encoding: "utf8", + }); + return JSON.parse(packedPackageJson); +} + +function assertNoWorkspaceRefs(workspace, packedPackage) { + const packedRefs = listWorkspaceRefs(packedPackage); + if (packedRefs.length === 0) return; + + throw new Error( + `Packed manifest for ${workspace} still contains workspace refs: ${packedRefs.join(", ")}`, + ); +} + +function verifyPackedWorkspace(workspace, filename) { + const packedPackage = readPackedPackage(filename); + const packedFiles = listPackedFiles(filename); + + assertNoWorkspaceRefs(workspace, packedPackage); + verifyPackedEntrypoints(workspace, packedPackage, packedFiles); + verifyPackedJavaScriptImports(workspace, filename, packedFiles); +} + +function verifyWorkspace(workspace) { + const sourcePackageJson = readWorkspacePackage(workspace); + if (sourcePackageJson.private) return; + + assertPublishedExportsMatchSource(workspace, sourcePackageJson); + + const packDir = mkdtempSync(join(tmpdir(), "hyperframes-pack-")); + try { + verifyPackedWorkspace(workspace, packWorkspace(workspace, packDir)); + console.log(`Verified ${workspace}: packed manifest is publish-safe.`); + } finally { + rmSync(packDir, { force: true, recursive: true }); } } +function main() { + listWorkspacePackageDirs().forEach(verifyWorkspace); +} + main();