feat(scripting): discover scoped @punktfunk/plugin-* packages
apple / swift (push) Successful in 1m24s
apple / screenshots (push) Successful in 6m27s
windows-host / package (push) Failing after 15m41s
ci / web (push) Successful in 1m33s
ci / docs-site (push) Successful in 1m26s
android / android (push) Successful in 14m3s
decky / build-publish (push) Successful in 21s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 16s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 15s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
ci / bench (push) Successful in 7m40s
deb / build-publish (push) Successful in 8m58s
deb / build-publish-host (push) Successful in 9m32s
arch / build-publish (push) Successful in 17m35s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m33s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m33s
ci / rust (push) Successful in 28m32s
docker / deploy-docs (push) Failing after 12s

The runner only found unscoped `punktfunk-plugin-*` packages. An unscoped
package can't be split across registries, which forced plugins to bundle their
own copy of @punktfunk/host + effect (the plugin's Gitea registry has no
`effect`; `--registry <gitea>` 404s it). Discovering the scoped
`@punktfunk/plugin-*` convention lets a plugin resolve cleanly from one scope
map and depend on @punktfunk/host + effect as SHARED (hoisted) deps instead of
bundling — no per-plugin duplication. Additive: unscoped names still work.

Bumps @punktfunk/host to 0.1.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-18 12:12:11 +02:00
parent f407f41855
commit 94533bb071
3 changed files with 52 additions and 12 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@punktfunk/host", "name": "@punktfunk/host",
"version": "0.1.0", "version": "0.1.1",
"description": "TypeScript SDK for the punktfunk streaming host: typed management-API client + lifecycle event stream, built on Effect.", "description": "TypeScript SDK for the punktfunk streaming host: typed management-API client + lifecycle event stream, built on Effect.",
"type": "module", "type": "module",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
+29 -8
View File
@@ -92,19 +92,40 @@ export const discoverUnits = (
// no scripts dir — fine // no scripts dir — fine
} }
const modules = path.join(pluginsDir, "node_modules"); const modules = path.join(pluginsDir, "node_modules");
try { // Read a plugin package's manifest (`module`/`main` entry) and add it as a unit.
for (const pkg of fs.readdirSync(modules).sort()) { const addPlugin = (dir: string, name: string): void => {
if (!pkg.startsWith("punktfunk-plugin-")) continue;
try { try {
const manifest = JSON.parse( const manifest = JSON.parse(
fs.readFileSync(path.join(modules, pkg, "package.json"), "utf8"), fs.readFileSync(path.join(dir, "package.json"), "utf8"),
) as { main?: string; module?: string }; ) as { main?: string; module?: string };
const rel = manifest.module ?? manifest.main ?? "index.js"; const rel = manifest.module ?? manifest.main ?? "index.js";
const file = path.join(modules, pkg, rel); const file = path.join(dir, rel);
if (!fileIsSafe(file, log)) continue; if (!fileIsSafe(file, log)) return;
units.push({ name: pkg, file }); units.push({ name, file });
} catch (e) { } catch (e) {
log(`[runner] skipping ${pkg}: unreadable package.json (${e})`); log(`[runner] skipping ${name}: unreadable package.json (${e})`);
}
};
try {
for (const pkg of fs.readdirSync(modules).sort()) {
// Unscoped convention: `punktfunk-plugin-*`.
if (pkg.startsWith("punktfunk-plugin-")) {
addPlugin(path.join(modules, pkg), pkg);
continue;
}
// Scoped convention: `@punktfunk/plugin-*` (first-party). A scoped name resolves cleanly
// from a single registry scope-map, so a plugin can depend on `@punktfunk/host` + `effect`
// as shared (hoisted) deps rather than bundling its own copy of each.
if (pkg === "@punktfunk") {
try {
for (const scoped of fs.readdirSync(path.join(modules, pkg)).sort()) {
if (scoped.startsWith("plugin-")) {
addPlugin(path.join(modules, pkg, scoped), `${pkg}/${scoped}`);
}
}
} catch {
// no @punktfunk scope dir — fine
}
} }
} }
} catch { } catch {
+19
View File
@@ -83,6 +83,25 @@ describe("discovery", () => {
expect(logs.join("\n")).toContain("REFUSING"); expect(logs.join("\n")).toContain("REFUSING");
expect(logs.join("\n")).toContain("evil.ts"); expect(logs.join("\n")).toContain("evil.ts");
}); });
test("discovers scoped @punktfunk/plugin-* packages (not other scoped pkgs)", () => {
const d = mkdirs("discover-scoped");
write(
path.join(d.pluginsDir, "node_modules", "@punktfunk", "plugin-y", "package.json"),
JSON.stringify({ name: "@punktfunk/plugin-y", module: "dist/index.js" }),
);
write(
path.join(d.pluginsDir, "node_modules", "@punktfunk", "plugin-y", "dist", "index.js"),
"export default { name: 'y', main: async () => {} };",
);
// A non-plugin scoped package (e.g. the SDK itself) is ignored.
write(
path.join(d.pluginsDir, "node_modules", "@punktfunk", "host", "package.json"),
JSON.stringify({ name: "@punktfunk/host" }),
);
const units = discoverUnits(d);
expect(units.map((u) => u.name)).toEqual(["@punktfunk/plugin-y"]);
});
}); });
describe("supervision", () => { describe("supervision", () => {