Files
James Russo 696cbdbbd0 chore(skills): package Codex plugin upload (#2668)
* chore(skills): package Codex plugin upload

* chore(skills): harden Codex plugin content

* fix(skills): satisfy plugin quality gates

* fix(skills): address plugin packaging review

* fix(plugin): simplify asset validation

* fix(skills): correct embedded-captions catalog count to 35 after nightcity removal

The nightcity theme removal left SKILL.md claiming 36 identities in
four places, including the frontmatter description the router reads.
The catalog now has 35 entries (10 classic + 25 themed).

---------

Co-authored-by: Miao Yang <miao.yang@heygen.com>
2026-07-22 00:41:40 +08:00

68 lines
2.0 KiB
JavaScript

#!/usr/bin/env node
import { execFileSync, spawnSync } from "node:child_process";
import { mkdirSync, readFileSync, rmSync, statSync } from "node:fs";
import { join } from "node:path";
const REPO_ROOT = join(import.meta.dirname, "..");
const OUTPUT = join(REPO_ROOT, "dist", "hyperframes-plugin.zip");
// Codex reports its upload limit in decimal MB.
const MAX_UPLOAD_BYTES = 100 * 1_000_000;
const pluginManifest = JSON.parse(readFileSync(join(REPO_ROOT, ".codex-plugin", "plugin.json")));
const assetPaths = [
...new Set(
Object.values(pluginManifest.interface)
.filter((value) => typeof value === "string")
.map((value) => value.replace(/^\.\//, ""))
.filter((value) => value.startsWith("assets/")),
),
].sort();
if (assetPaths.some((assetPath) => assetPath.split("/").includes(".."))) {
throw new Error("Plugin manifest contains an unsafe asset path.");
}
const PLUGIN_PATHS = [".codex-plugin", ...assetPaths, "skills"];
for (const pluginPath of PLUGIN_PATHS) {
execFileSync("git", ["cat-file", "-e", `HEAD:${pluginPath}`], {
cwd: REPO_ROOT,
stdio: "inherit",
});
}
const dirtyCheck = spawnSync("git", ["diff", "--quiet", "HEAD", "--", ...PLUGIN_PATHS], {
cwd: REPO_ROOT,
});
if (dirtyCheck.error || (dirtyCheck.status !== 0 && dirtyCheck.status !== 1)) {
throw dirtyCheck.error ?? new Error("Unable to inspect the plugin working tree state.");
}
if (dirtyCheck.status === 1) {
console.warn("Warning: packaging committed HEAD; uncommitted plugin changes are excluded.");
}
mkdirSync(join(REPO_ROOT, "dist"), { recursive: true });
execFileSync(
"git",
[
"archive",
"--format=zip",
"--prefix=hyperframes/",
"--output",
OUTPUT,
"HEAD",
"--",
...PLUGIN_PATHS,
],
{ cwd: REPO_ROOT, stdio: "inherit" },
);
const bytes = statSync(OUTPUT).size;
if (bytes > MAX_UPLOAD_BYTES) {
rmSync(OUTPUT);
throw new Error(
`Codex plugin archive is ${(bytes / 1_000_000).toFixed(1)} MB; the upload limit is 100 MB.`,
);
}
console.log(`Wrote ${OUTPUT} (${(bytes / 1_000_000).toFixed(1)} MB).`);