From 6d935f05957b28555279412bf2ea54916fa4e486 Mon Sep 17 00:00:00 2001 From: Sergio Peschiera Date: Thu, 6 Aug 2026 05:03:32 +0100 Subject: [PATCH] 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 --- .github/workflows/package-publish.yml | 46 +++++++++++++++++++ docs/cli.md | 18 ++++++++ .../package-publish-workflow.test.ts | 46 +++++++++++++++++++ 3 files changed, 110 insertions(+) diff --git a/.github/workflows/package-publish.yml b/.github/workflows/package-publish.yml index 4118ede2..df7e69b8 100644 --- a/.github/workflows/package-publish.yml +++ b/.github/workflows/package-publish.yml @@ -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() diff --git a/docs/cli.md b/docs/cli.md index be2c2a3c..fc15ad02 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -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. diff --git a/src/__tests__/package-publish-workflow.test.ts b/src/__tests__/package-publish-workflow.test.ts index 1fa3185f..140cc1ae 100644 --- a/src/__tests__/package-publish-workflow.test.ts +++ b/src/__tests__/package-publish-workflow.test.ts @@ -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; + }; + }; + jobs?: { + publish?: { + steps?: Array<{ name?: string; env?: Record; 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}", ""]`); + } + }); });