feat: forward package catalog metadata in publish workflow (#3074)

* feat: forward package catalog metadata in publish workflow

* docs: make package publish metadata example event-safe

* fix: preserve package metadata clearing

---------

Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
This commit is contained in:
Sergio Peschiera
2026-08-05 21:03:32 -07:00
committed by GitHub
co-authored by Patrick Erichsen
parent 31729d314c
commit 6d935f0595
3 changed files with 110 additions and 0 deletions
+46
View File
@@ -59,6 +59,28 @@ on:
required: false
type: string
default: latest
changelog:
description: Optional release changelog shown on ClawHub.
required: false
type: string
categories:
description: Optional comma-separated plugin category slugs.
required: false
type: string
clear_categories:
description: Clear existing plugin categories. Cannot be combined with categories.
required: false
type: boolean
default: false
topics:
description: Optional comma-separated catalog topics.
required: false
type: string
clear_topics:
description: Clear existing catalog topics. Cannot be combined with topics.
required: false
type: boolean
default: false
source_repo:
description: Optional source repo override for local-folder publishes.
required: false
@@ -312,6 +334,11 @@ jobs:
INPUT_FAMILY: ${{ inputs.family }}
INPUT_VERSION: ${{ inputs.version }}
INPUT_TAGS: ${{ inputs.tags }}
INPUT_CHANGELOG: ${{ inputs.changelog }}
INPUT_CATEGORIES: ${{ inputs.categories }}
INPUT_CLEAR_CATEGORIES: ${{ inputs.clear_categories }}
INPUT_TOPICS: ${{ inputs.topics }}
INPUT_CLEAR_TOPICS: ${{ inputs.clear_topics }}
INPUT_SOURCE_REPO: ${{ inputs.source_repo }}
INPUT_SOURCE_COMMIT: ${{ inputs.source_commit }}
INPUT_SOURCE_REF: ${{ inputs.source_ref }}
@@ -478,6 +505,15 @@ jobs:
family = os.environ["INPUT_FAMILY"].strip()
version = os.environ["INPUT_VERSION"].strip()
tags = os.environ["INPUT_TAGS"].strip()
changelog = os.environ["INPUT_CHANGELOG"].strip()
categories = os.environ["INPUT_CATEGORIES"].strip()
clear_categories = os.environ["INPUT_CLEAR_CATEGORIES"].strip().lower() == "true"
topics = os.environ["INPUT_TOPICS"].strip()
clear_topics = os.environ["INPUT_CLEAR_TOPICS"].strip().lower() == "true"
if categories and clear_categories:
raise SystemExit("categories and clear_categories cannot be combined")
if topics and clear_topics:
raise SystemExit("topics and clear_topics cannot be combined")
if owner:
cmd += ["--owner", owner]
if family:
@@ -491,6 +527,16 @@ jobs:
cmd += ["--version", version]
if tags:
cmd += ["--tags", tags]
if changelog:
cmd += ["--changelog", changelog]
if categories:
cmd += ["--categories", categories]
elif clear_categories:
cmd += ["--categories", ""]
if topics:
cmd += ["--topics", topics]
elif clear_topics:
cmd += ["--topics", ""]
source_repo = os.environ["INPUT_SOURCE_REPO"].strip()
source_commit = os.environ["INPUT_SOURCE_COMMIT"].strip()
source_ref = os.environ["INPUT_SOURCE_REF"].strip()
+18
View File
@@ -797,11 +797,29 @@ jobs:
clawhub_token: ${{ secrets.CLAWHUB_TOKEN }}
```
To attach release and catalog metadata, add the matching CLI values to the
job's existing `with` block. Keep `dry_run: true` on pull-request jobs; use
`dry_run: false` only on the trusted publish job shown above.
```yaml
with:
changelog: "Describe the changes in this release."
categories: "tools"
topics: "automation,productivity"
```
Notes:
- The reusable workflow defaults `source` to the caller repo.
- For monorepos, pass `source_path` so the workflow publishes the plugin
package folder, for example `source_path: extensions/codex`.
- `changelog`, `categories`, and `topics` are optional. When present, the
workflow forwards them to the matching package publish CLI flags. Categories
and topics use comma-separated values; omitting them preserves the existing
workflow behavior.
- To remove previously declared metadata, set `clear_categories: true` or
`clear_topics: true`. A clear input cannot be combined with its matching
value input.
- Pin the reusable workflow to a stable tag or full commit SHA. Do not run release publishing from `@main`.
- `pull_request` should use `dry_run: true` so CI stays non-polluting.
- Real publishes should be limited to trusted events such as `workflow_dispatch` or tag pushes.
@@ -186,4 +186,50 @@ describe("package publish workflow", () => {
expect(workflow).not.toContain("Prebuilt artifact mode does not accept source_path");
expect(workflow).toContain('cmd += ["--source-path", source_path]');
});
it("forwards optional catalog metadata and changelog inputs", () => {
const workflowText = readFileSync(resolve(".github/workflows/package-publish.yml"), "utf8");
const workflow = parseYaml(workflowText) as {
on?: {
workflow_call?: {
inputs?: Record<string, { required?: boolean; type?: string }>;
};
};
jobs?: {
publish?: {
steps?: Array<{ name?: string; env?: Record<string, string>; run?: string }>;
};
};
};
const inputs = workflow.on?.workflow_call?.inputs;
const resolveStep = workflow.jobs?.publish?.steps?.find(
(step) => step.name === "Resolve publish command",
);
for (const name of ["changelog", "categories", "topics"]) {
const envName = `INPUT_${name.toUpperCase()}`;
expect(inputs?.[name]).toMatchObject({ required: false, type: "string" });
expect(resolveStep?.env?.[envName]).toBe(`\${{ inputs.${name} }}`);
expect(resolveStep?.run).toContain(`${name} = os.environ["${envName}"].strip()`);
expect(resolveStep?.run).toContain(`if ${name}:\n cmd += ["--${name}", ${name}]`);
}
for (const name of ["categories", "topics"]) {
const clearName = `clear_${name}`;
const envName = `INPUT_CLEAR_${name.toUpperCase()}`;
expect(inputs?.[clearName]).toMatchObject({
required: false,
type: "boolean",
default: false,
});
expect(resolveStep?.env?.[envName]).toBe(`\${{ inputs.${clearName} }}`);
expect(resolveStep?.run).toContain(
`${clearName} = os.environ["${envName}"].strip().lower() == "true"`,
);
expect(resolveStep?.run).toContain(
`if ${name} and ${clearName}:\n raise SystemExit("${name} and ${clearName} cannot be combined")`,
);
expect(resolveStep?.run).toContain(`elif ${clearName}:\n cmd += ["--${name}", ""]`);
}
});
});