Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions .github/workflows/publish-action-repo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
name: Publish GitHub Action Repository

on:
release:
types: [published, edited]
workflow_dispatch:
inputs:
release_tag:
description: "Release tag to publish from the source repository"
required: false
default: ""
action_repository:
description: "Dedicated public repository that hosts the Marketplace action"
required: false
default: "hashgraph-online/hol-codex-plugin-scanner-action"
create_repository:
description: "Create the dedicated action repository if it does not exist"
required: false
default: true
type: boolean

permissions:
contents: read

concurrency:
group: codex-plugin-scanner-action-publish-${{ github.event.release.tag_name || inputs.release_tag || github.ref }}
cancel-in-progress: false

jobs:
publish-action-repo:
name: Publish GitHub Action Repository
if: secrets.ACTION_REPO_TOKEN != ''
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.ACTION_REPO_TOKEN }}
ACTION_REPOSITORY: ${{ inputs.action_repository != '' && inputs.action_repository || vars.ACTION_REPOSITORY != '' && vars.ACTION_REPOSITORY || 'hashgraph-online/hol-codex-plugin-scanner-action' }}
RELEASE_TAG: ${{ github.event.release.tag_name || inputs.release_tag }}
CREATE_REPOSITORY: ${{ github.event_name == 'workflow_dispatch' && inputs.create_repository || 'true' }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: CREATE_REPOSITORY expression always evaluates to 'true' — the create_repository input has no effect

In GitHub Actions expressions, false && <expr> || 'true' always returns 'true' because the && returns false (falsy), then || falls through to the string 'true'. When triggered via workflow_dispatch with create_repository: false:

  • github.event_name == 'workflow_dispatch'true
  • true && inputs.create_repositoryfalse (boolean input is falsy)
  • false || 'true''true' (the fallback always wins)

This means the "Create the dedicated action repository if it does not exist" toggle is non-functional — the workflow will always attempt to create the repo.

To fix, coerce the boolean to a string before the || fallback:

Suggested change
CREATE_REPOSITORY: ${{ github.event_name == 'workflow_dispatch' && inputs.create_repository || 'true' }}
CREATE_REPOSITORY: ${{ github.event_name == 'workflow_dispatch' && inputs.create_repository || false || 'true' }}

A cleaner alternative: use fromJSON() or a ternary-style pattern like ${{ inputs.create_repository == false && 'false' || 'true' }}.

SOURCE_REPOSITORY: ${{ github.repository }}
SOURCE_SERVER_URL: ${{ github.server_url }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ github.event.release.tag_name || inputs.release_tag || github.ref }}
fetch-depth: 0

- name: Resolve version
id: version
run: |
TAG="${RELEASE_TAG}"
if [ -z "${TAG}" ]; then
TAG=$(python3 -c "import tomllib; p=tomllib.load(open('pyproject.toml','rb')); print('v' + p['project']['version'])")
fi
VERSION="${TAG#v}"
echo "tag=${TAG}" >> "${GITHUB_OUTPUT}"
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"

- name: Ensure action repository exists
run: |
if gh repo view "${ACTION_REPOSITORY}" >/dev/null 2>&1; then
exit 0
fi
if [ "${CREATE_REPOSITORY}" != "true" ]; then
echo "Action repository ${ACTION_REPOSITORY} does not exist and automatic creation is disabled." >&2
exit 1
fi
gh repo create "${ACTION_REPOSITORY}" \
--public \
--description "HOL Codex Plugin Scanner GitHub Action" \
--homepage "${SOURCE_SERVER_URL}/${SOURCE_REPOSITORY}" \
--disable-wiki

- name: Sync action repository contents
run: |
VERSION="${{ steps.version.outputs.version }}"
TAG="${{ steps.version.outputs.tag }}"
WORKDIR="${RUNNER_TEMP}/action-repository"
git clone "https://x-access-token:${GH_TOKEN}@github.com/${ACTION_REPOSITORY}.git" "${WORKDIR}"
cd "${WORKDIR}"

if git ls-remote --exit-code origin refs/heads/main >/dev/null 2>&1; then
git fetch origin main
git switch -C main origin/main
else
git switch --orphan main
fi

find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} +

cp "${GITHUB_WORKSPACE}/action/action.yml" action.yml
cp "${GITHUB_WORKSPACE}/action/README.md" README.md
cp "${GITHUB_WORKSPACE}/LICENSE" LICENSE
cp "${GITHUB_WORKSPACE}/SECURITY.md" SECURITY.md

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add action.yml README.md LICENSE SECURITY.md

HAS_HEAD=true
git rev-parse --verify HEAD >/dev/null 2>&1 || HAS_HEAD=false

if [ "${HAS_HEAD}" = "true" ] && git diff --cached --quiet; then
echo "No action repository content changes detected."
else
git commit -m "chore: publish action bundle ${TAG}"
git push origin HEAD:main
fi

git tag -f "${TAG}"
git push origin "refs/tags/${TAG}" --force
git tag -f v1
git push origin refs/tags/v1 --force

- name: Create or update action repository release
run: |
VERSION="${{ steps.version.outputs.version }}"
TAG="${{ steps.version.outputs.tag }}"
NOTES="Published automatically from ${SOURCE_SERVER_URL}/${SOURCE_REPOSITORY}/releases/tag/${TAG}"

if gh release view "${TAG}" --repo "${ACTION_REPOSITORY}" >/dev/null 2>&1; then
gh release edit "${TAG}" \
--repo "${ACTION_REPOSITORY}" \
--title "${TAG}" \
--notes "${NOTES}"
else
gh release create "${TAG}" \
--repo "${ACTION_REPOSITORY}" \
--title "${TAG}" \
--notes "${NOTES}"
fi
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![CI](https://github.com/hashgraph-online/codex-plugin-scanner/actions/workflows/ci.yml/badge.svg)](https://github.com/hashgraph-online/codex-plugin-scanner/actions/workflows/ci.yml)
[![Publish](https://github.com/hashgraph-online/codex-plugin-scanner/actions/workflows/publish.yml/badge.svg)](https://github.com/hashgraph-online/codex-plugin-scanner/actions/workflows/publish.yml)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/hashgraph-online/codex-plugin-scanner/badge)](https://scorecard.dev/viewer/?uri=github.com/hashgraph-online/codex-plugin-scanner)
[![License](https://img.shields.io/github/license/hashgraph-online/codex-plugin-scanner)](./LICENSE)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE)
[![GitHub Stars](https://img.shields.io/github/stars/hashgraph-online/codex-plugin-scanner?style=social)](https://github.com/hashgraph-online/codex-plugin-scanner/stargazers)
[![Lint: ruff](https://img.shields.io/badge/lint-ruff-D7FF64.svg)](https://github.com/astral-sh/ruff)

Expand Down Expand Up @@ -177,9 +177,30 @@ GitHub Marketplace has two important constraints for actions:
- the published action must live in a dedicated public repository with a single root `action.yml`
- that repository cannot contain workflow files

Because the scanner repository itself contains CI and release workflows, the Marketplace listing should be published from a separate action-only repository. The scanner release workflow now emits a root-ready bundle zip for that repository on every tagged release.
Because the scanner repository itself contains CI and release workflows, the Marketplace listing should be published from a separate action-only repository.

The source README for that dedicated action repository lives in [action/README.md](action/README.md), and the full publication guide lives in [docs/github-action-marketplace.md](docs/github-action-marketplace.md).
The dedicated action-repository guide now lives directly in [action/README.md](action/README.md).

### Automated Action Publication

The source repository can publish the GitHub Action automatically into a dedicated public action repository.

Configure:

- repository secret `ACTION_REPO_TOKEN`
It should be a token that can create or update repositories and releases in the target repository.
- optional repository variable `ACTION_REPOSITORY`
Defaults to `hashgraph-online/hol-codex-plugin-scanner-action`.

When a tagged release is published, [publish-action-repo.yml](./.github/workflows/publish-action-repo.yml) will:

- create the dedicated action repository if it does not already exist
- sync the root-ready `action.yml`, `README.md`, `LICENSE`, and `SECURITY.md`
- push the immutable release tag such as `v1.2.0`
- move the floating `v1` tag
- create or update the corresponding release in the action repository

GitHub Marketplace still requires the one-time listing publication step in the dedicated action repository UI, but after that this repository can keep the action repository current automatically.

### Plugin Author Submission Flow

Expand Down
2 changes: 2 additions & 0 deletions action/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ Use a fine-grained token with `issues:write` on `hashgraph-online/awesome-codex-
- Publish immutable releases such as `v1.2.0`.
- Move the floating major tag `v1` to the latest compatible release.
- Keep this action in its own public repository for GitHub Marketplace publication.
- Configure `ACTION_REPO_TOKEN` in the source repository so `publish-action-repo.yml` can sync this root-ready bundle automatically.
- Optionally set `ACTION_REPOSITORY` in the source repository if the target repository should not be `hashgraph-online/hol-codex-plugin-scanner-action`.

## Source Of Truth

Expand Down
33 changes: 27 additions & 6 deletions tests/test_action_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ def test_action_metadata_includes_marketplace_branding_and_fallback_install() ->
assert 'pip install "$LOCAL_SOURCE"' in action_text
assert "submission_enabled:" in action_text
assert "submission_issue_urls:" in action_text
assert "actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065" in action_text
assert "python3 -m codex_plugin_scanner.action_runner" in action_text


Expand All @@ -29,12 +28,34 @@ def test_publish_workflow_attaches_marketplace_action_bundle() -> None:
assert 'cp action/action.yml "${BUNDLE_ROOT}/action.yml"' in workflow_text


def test_action_bundle_has_root_ready_readme_and_marketplace_guide() -> None:
def test_publish_action_repo_workflow_syncs_action_repository() -> None:
workflow_text = (ROOT / ".github" / "workflows" / "publish-action-repo.yml").read_text(encoding="utf-8")

assert "Publish GitHub Action Repository" in workflow_text
assert "ACTION_REPO_TOKEN" in workflow_text
assert "hashgraph-online/hol-codex-plugin-scanner-action" in workflow_text
assert 'gh repo create "${ACTION_REPOSITORY}"' in workflow_text
assert 'cp "${GITHUB_WORKSPACE}/action/action.yml" action.yml' in workflow_text
assert 'git push origin refs/tags/v1 --force' in workflow_text
assert 'gh release create "${TAG}"' in workflow_text


def test_action_bundle_docs_live_in_action_readme() -> None:
action_readme = (ROOT / "action" / "README.md").read_text(encoding="utf-8")
guide = (ROOT / "docs" / "github-action-marketplace.md").read_text(encoding="utf-8")

assert "single root `action.yml`" in action_readme
assert "must not contain any workflow files" in guide
assert "dedicated public action repository" in guide
assert "no workflow files" in action_readme
assert "dedicated Marketplace repository" in action_readme
assert "Source Of Truth" in action_readme
assert "submission issue" in action_readme
assert "awesome-codex-plugins" in guide
assert "awesome-codex-plugins" in action_readme
assert "publish-action-repo.yml" in action_readme


def test_readme_uses_stable_apache_license_badge() -> None:
readme = (ROOT / "README.md").read_text(encoding="utf-8")

assert "https://img.shields.io/badge/license-Apache--2.0-blue.svg" in readme
assert "https://img.shields.io/github/license/hashgraph-online/codex-plugin-scanner" not in readme
assert "publish-action-repo.yml" in readme
assert "docs/github-action-marketplace.md" not in readme
Loading