Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,12 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
- run: corepack enable
- run: corepack prepare pnpm@10.17.1 --activate
- uses: ./.github/actions/prepare-ffmpeg-bin
- run: bun install --frozen-lockfile
- run: bun run build
- run: bun run verify:packed-manifests

lint:
name: Lint
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"player:perf": "bun run --filter @hyperframes/player perf",
"format:check": "oxfmt --check .",
"knip": "knip",
"test:scripts": "node --import tsx --test scripts/validate-release-channel.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts",
"test:scripts": "node --import tsx --test scripts/validate-release-channel.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/verify-packed-manifests.test.mjs",
"test:skills": "node --test 'skills/**/*.test.mjs'",
"generate:previews": "tsx scripts/generate-template-previews.ts",
"generate:catalog-previews": "tsx scripts/generate-catalog-previews.ts",
Expand Down
1 change: 0 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"dist/**/*.d.ts.map",
"dist/**/*.js.map",
"!dist/hyperframe.runtime.mjs",
"!dist/generated/runtime-inline.js",
"docs",
"schemas",
"README.md"
Expand Down
39 changes: 28 additions & 11 deletions scripts/verify-packed-manifests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import { execFileSync } from "node:child_process";
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
import { extname, join } from "node:path";
import { extname, join, posix } from "node:path";
import { tmpdir } from "node:os";
import { fileURLToPath } from "node:url";

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"]);
const PACKED_JAVASCRIPT_FILE_PATTERN = /\.(?:js|mjs|cjs)$/;

function listWorkspacePackageDirs() {
return readdirSync(PACKAGES_DIR)
Expand Down Expand Up @@ -129,7 +131,9 @@ function hasExplicitRuntimeExtension(specifier) {
function listRelativeImportSpecifiers(source) {
const patterns = [
/^\s*import\s+["'](\.\.?\/[^"']+)["']/gm,
/^\s*(?:import|export)\s+[^;]*?\s+from\s+["'](\.\.?\/[^"']+)["']/gm,
/^\s*(?:import|export)\b(?:(?!;)[\s\S])*?\s+from\s+["'](\.\.?\/[^"']+)["']/gm,
/\bimport\s*\(\s*["'](\.\.?\/[^"']+)["']\s*\)/gm,
/\brequire\s*\(\s*["'](\.\.?\/[^"']+)["']\s*\)/gm,
];
const specifiers = [];

Expand Down Expand Up @@ -177,17 +181,28 @@ function verifyPackedEntrypoints(workspace, packedPackage, packedFiles) {
}
}

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 resolvePackedRelativeImport(fromFile, specifier) {
return posix.normalize(posix.join(posix.dirname(fromFile), stripSpecifierQuery(specifier)));
}

function listPackedJavaScriptImportIssues(filename, packedFiles) {
export function listPackedJavaScriptImportIssues(filename, packedFiles) {
return [...packedFiles]
.filter((file) => file.endsWith(".js"))
.flatMap((file) => listJavaScriptImportIssues(filename, file));
.filter((file) => PACKED_JAVASCRIPT_FILE_PATTERN.test(file))
.flatMap((file) => {
const source = readPackedFile(filename, file);
return listRelativeImportSpecifiers(source).flatMap(({ index, specifier }) => {
if (!hasExplicitRuntimeExtension(specifier)) {
return [`${file}:${lineNumberAt(source, index)} imports ${specifier}`];
}

const target = resolvePackedRelativeImport(file, specifier);
if (!packedFiles.has(target)) {
return [`${file}:${lineNumberAt(source, index)} imports missing ${specifier}`];
}

return [];
});
});
}

function verifyPackedJavaScriptImports(workspace, filename, packedFiles) {
Expand Down Expand Up @@ -276,4 +291,6 @@ function main() {
listWorkspacePackageDirs().forEach(verifyWorkspace);
}

main();
if (process.argv[1] === fileURLToPath(import.meta.url)) {
main();
}
100 changes: 100 additions & 0 deletions scripts/verify-packed-manifests.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { describe, it } from "node:test";
import { listPackedJavaScriptImportIssues } from "./verify-packed-manifests.mjs";

describe("packed manifest verifier", () => {
function withPackedFiles(files, packedFiles, callback) {
const dir = mkdtempSync(join(tmpdir(), "hyperframes-pack-test-"));
try {
const packageDir = join(dir, "package");
mkdirSync(packageDir, { recursive: true });
for (const [file, source] of Object.entries(files)) {
mkdirSync(dirname(join(packageDir, file)), { recursive: true });
writeFileSync(join(packageDir, file), source, "utf8");
}

const tarball = join(dir, "package.tgz");
execFileSync("tar", ["-czf", tarball, "-C", dir, "package"]);
callback(tarball, new Set(packedFiles));
} finally {
rmSync(dir, { recursive: true, force: true });
}
}

it("passes explicit relative JavaScript imports whose target is packed", () => {
withPackedFiles(
{
"dist/index.js": 'import { runtime } from "./generated/runtime-inline.js";\n',
"dist/generated/runtime-inline.js": "export const runtime = 'ok';\n",
},
["dist/index.js", "dist/generated/runtime-inline.js"],
(tarball, packedFiles) => {
assert.deepEqual(listPackedJavaScriptImportIssues(tarball, packedFiles), []);
},
);
});

it("reports explicit relative JavaScript imports whose target is missing from the tarball", () => {
withPackedFiles(
{
"dist/index.js": 'import { runtime } from "./generated/runtime-inline.js";\n',
},
["dist/index.js"],
(tarball, packedFiles) => {
assert.deepEqual(listPackedJavaScriptImportIssues(tarball, packedFiles), [
"dist/index.js:1 imports missing ./generated/runtime-inline.js",
]);
},
);
});

it("checks export-from, dynamic import, require, and mjs/cjs files", () => {
withPackedFiles(
{
"dist/index.js": 'export * from "./generated/exports.js";\n',
"dist/dynamic.mjs": 'await import("./generated/dynamic.js");\n',
"dist/require.cjs": 'require("./generated/require.js");\n',
},
["dist/index.js", "dist/dynamic.mjs", "dist/require.cjs"],
(tarball, packedFiles) => {
assert.deepEqual(listPackedJavaScriptImportIssues(tarball, packedFiles), [
"dist/index.js:1 imports missing ./generated/exports.js",
"dist/dynamic.mjs:1 imports missing ./generated/dynamic.js",
"dist/require.cjs:1 imports missing ./generated/require.js",
]);
},
);
});

it("reports extensionless relative imports", () => {
withPackedFiles(
{
"dist/index.js": 'export {\n runtime\n} from "./generated/runtime-inline";\n',
},
["dist/index.js"],
(tarball, packedFiles) => {
assert.deepEqual(listPackedJavaScriptImportIssues(tarball, packedFiles), [
"dist/index.js:1 imports ./generated/runtime-inline",
]);
},
);
});

it("reports side-effect imports whose target is missing from the tarball", () => {
withPackedFiles(
{
"index.js": 'import "./missing.js";\n',
},
["index.js"],
(tarball, packedFiles) => {
assert.deepEqual(listPackedJavaScriptImportIssues(tarball, packedFiles), [
"index.js:1 imports missing ./missing.js",
]);
},
);
});
});
Loading