Fix/plugin audit findings (#2031)

* fix(plugins): enforce manifest apiVersion at install and activation

* test(plugins): exercise the absent-apiVersion default

* feat(plugins): generate the core-event catalog into host-facts

* docs(plugins): explain the packing.listBags write-permission binding

* test(plugins): cover apiVersion upload refusal; tidy docblocks and generator header

* fix(plugin-sdk): validate settings options and oauth like the host

* fix(plugin-sdk): model the full manifest shape and stop dropping fields

* fix(plugin-sdk): drop non-string routeProfiles icons like the host

* docs(plugin-sdk): document host limits, oauth contract, and ignored asUserId

* feat(plugin-sdk): enforce daily budgets and meta quotas in the mock host

* feat(plugin-sdk): expose the generated core-event catalog

* feat(plugin-sdk): scaffold trek range >=3.4.0 <5.0.0 ahead of TREK 4.0

* feat(plugin-sdk): scaffold the oauth broker settings for oauth:client plugins

* chore(plugin-sdk): fix stale mirror docs and the colliding permissions export

* docs(plugin-sdk): add the rate-limit string; restore trek docblock; match host meta sizing

* feat(plugin-sdk): raise the scaffold trek floor to 4.0.0

* docs(wiki): bring the plugin pages in line with the current runtime

* docs: bring the workspace CLAUDE.md guides up to date and make them committable

* feat(plugin-sdk): roll back failed publishes, add unrelease, sync the fork, retro-sign updates

* docs(wiki): document the publish rollback, unrelease, fork sync, and retro-signing

* chore(plugin-sdk): 1.6.0
This commit is contained in:
jubnl
2026-08-24 23:48:36 +02:00
committed by GitHub
parent 4bac447ac8
commit d110b3bb6d
48 changed files with 1895 additions and 169 deletions
+27 -2
View File
@@ -11,12 +11,13 @@ import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { readJsonFile } from './json.js';
import { needsRetroSign, retroSignVersions } from './retro-sign.js';
const DEFAULT_REGISTRY = 'liketrek/TREK-Plugins';
interface EntryLike {
id: string; name?: string; authorPublicKey?: string;
versions: { version: string }[];
versions: { version: string; downloadUrl: string; sha256: string; signature?: string }[];
}
function git(cwd: string, ...a: string[]): string {
@@ -42,7 +43,7 @@ function mergeOnto(existing: EntryLike, fresh: EntryLike): EntryLike {
return merged;
}
export function submitEntry(entry: EntryLike, opts: { registry?: string; branch?: string; draft?: boolean; keep?: boolean } = {}): { prUrl: string } {
export async function submitEntry(entry: EntryLike, opts: { registry?: string; branch?: string; draft?: boolean; keep?: boolean; signKeyPath?: string } = {}): Promise<{ prUrl: string }> {
const registry = opts.registry || DEFAULT_REGISTRY;
const name = registry.split('/')[1];
const login = gh('api', 'user', '--jq', '.login');
@@ -51,6 +52,17 @@ export function submitEntry(entry: EntryLike, opts: { registry?: string; branch?
// Ensure the fork exists (idempotent — prints "already exists" if it does).
try { gh('repo', 'fork', registry, '--clone=false'); } catch { /* already forked */ }
// Fast-forward the fork from the registry. The PR branch is based on upstream/main either
// way, but a fork left far behind has broken pushes and PRs in the wild — and costs nothing
// to keep current. Best-effort: a diverged fork can't be fast-forwarded, and that alone
// must not sink the submit.
try { gh('repo', 'sync', `${login}/${name}`, '--source', registry); } catch {
console.error(
`! could not fast-forward your fork ${login}/${name} (diverged?) — continuing, the PR branch is based on the registry's main.\n` +
` If the push or PR fails, reset the fork with: gh repo sync ${login}/${name} --force`,
);
}
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'trek-submit-'));
try {
// Clone the fork (pushable via gh auth), then base a branch on the CURRENT upstream main.
@@ -77,6 +89,19 @@ export function submitEntry(entry: EntryLike, opts: { registry?: string; branch?
toWrite = mergeOnto(existing, entry);
action = 'update';
}
// First signed update onto an unsigned history: the registry refuses a mixed entry (key
// present, older versions unsigned) — see retro-sign.ts for why. Sign the older versions
// with the same key, or say exactly what is missing.
if (needsRetroSign(toWrite)) {
if (!opts.signKeyPath) {
throw new Error(
`this update adds your signing key, but ${toWrite.versions.filter((v) => !v.signature).length} older version(s) are unsigned — ` +
'the registry requires every version signed once a key is present. Re-run with --sign so they can be signed for you.',
);
}
console.error(' older versions are unsigned — signing them with your key so the registry accepts the update');
await retroSignVersions(toWrite, opts.signKeyPath, (line) => console.error(line));
}
fs.mkdirSync(path.dirname(abs), { recursive: true });
fs.writeFileSync(abs, JSON.stringify(toWrite, null, 2) + '\n');