Automated plugin releases

On this page

Automated releases build and publish a sandboxed plugin when you push a version tag or start a GitHub Actions workflow manually. Your Atmosphere account continues to own the package profile and release records. GitHub identifies the approved workflow, and the release service verifies the build and writes the release through a narrow delegation. The repository does not store an Atmosphere account credential.

Use emdash-plugin publish for a release started from your computer. Use this guide when GitHub Actions should build and publish releases.

Prerequisites

Prepare the following before starting:

  • A public GitHub repository containing a sandboxed EmDash plugin.
  • @emdash-cms/plugin-cli installed as a development dependency. Plugins created with the CLI already include it.
  • A valid emdash-plugin.jsonc with slug, publisher, license, an author, and a security contact. Set repo to the canonical GitHub URL, or confirm the detected GitHub remote during interactive setup.
  • A version in package.json, or in emdash-plugin.jsonc for a registry-only plugin.
  • The Atmosphere account named by publisher.
  • A browser that supports passkeys. Release approval requires user verification.

Run the manifest check before configuring the workflow:

pnpm exec emdash-plugin validate

Set up automated releases

  1. Sign in to the plugin CLI with the Atmosphere account that owns the package.

    pnpm exec emdash-plugin login alice.example.com

    The CLI stores this local publishing session outside the project. GitHub Actions never receives it.

  2. Prepare the package profile and generate the workflow from the plugin directory.

    pnpm exec emdash-plugin release setup

    The command reads the package metadata from emdash-plugin.jsonc. If the package profile is missing, it offers to create it. If the profile exists without delegated-release settings, it offers to add them while preserving the existing package metadata.

    In a monorepo, run the command inside the plugin package or pass --dir <plugin-directory>. If the manifest does not contain repo, setup detects the GitHub origin remote and pre-fills the repository prompt.

    Setup asks when a release needs approval:

    • When plugin permissions increase is the default. A release waits for approval when its declared access expands relative to the latest release.
    • For every release requires approval for every version.

    Setup also asks whether releases require verifiable provenance. Require provenance is the default for automated releases. Choose Allow releases without provenance only when the same profile must also accept releases published directly from a trusted local environment.

    The signed-in Atmosphere account becomes the initial approver. The profile binds the package to the canonical GitHub repository URL and records the selected provenance policy.

    Run only the profile step when a workflow file already exists:

    pnpm exec emdash-plugin profile setup

    After publishing the package profile, this command shows the manual and GitHub Actions release commands.

    In a non-interactive terminal, pass --yes to accept the default policies. Pass --repository <https-url> when neither the manifest nor Git remote supplies the repository, --provenance optional to permit releases without provenance, and --confirmation always to require approval for every release.

  3. Review and commit the generated workflow.

    The command creates .github/workflows/emdash-release.yml. It does not push the file and does not replace an existing workflow unless you pass --force.

    If the repository contains .changeset/config.json, interactive setup offers Follow Changesets releases. When Changesets releases a package containing emdash-plugin.jsonc, the reusable EmDash workflow publishes the same version. Connect it to the existing Changesets workflow as described below. Otherwise, the generated workflow runs for package tags matching <slug>@<version>. Both variants support manual runs and can be selected explicitly with --trigger changesets|tags|manual.

    The workflow grants each job only its required contents, id-token, and attestations permissions; pins third-party Actions to full commit identifiers; runs the exact plugin CLI version that generated the file; resolves each package from its manifest; builds one plugin bundle; creates GitHub build provenance for those exact bytes; and passes both files to the EmDash release Action.

    The workflow lives at the repository root and is shared by every plugin package in that repository. Running release setup from a nested package still writes .github/workflows/emdash-release.yml at the root.

  4. Open the release-service dashboard and sign in with the same Atmosphere account.

    Select Authorize publishing. Your account provider shows the exact delegated permission. The retained grant can create package release records and upload package or listing-image blobs. It cannot create or edit package profiles, update or delete releases, or write another collection.

  5. Start the release workflow.

    With Changesets, merge the version pull request and let its publish job complete. The Changesets Action passes the packages it released to the reusable EmDash workflow. Ordinary npm packages are ignored; packages containing emdash-plugin.jsonc publish the same version to EmDash.

    With the package-tag trigger, update the package version before creating the version tag. The following commands start a 1.2.3 release:

    git tag gallery@1.2.3
    git push origin gallery@1.2.3

    You can also select Run workflow on the repository’s GitHub Actions page.

  6. Approve each repository ref scope on its first run.

    The service verifies that the initiating package profile names the GitHub repository before creating a connection request. The Action writes a link to the GitHub job summary and waits. Open the link and confirm the repository, workflow file, branch or tag, and environment.

    For a tag-triggered run, choose All package version tags or Only this tag. A manual run requests approval the first time its branch is used. Confirming another tag or branch scope adds it to the repository connection without removing existing scopes. The service stores the GitHub repository and owner IDs as well as the approved refs and environments. Later packages reuse these scopes only when their signed profiles name the same repository.

    Package approvals created by older generated workflows remain limited to their original packages. The first unmatched package or ref requests a repository connection; the service does not widen an existing package approval automatically.

  7. Approve the release when required.

    A release that expands plugin permissions, or a profile configured for every-release confirmation, enters Awaiting approval. Open the approval URL from the Action output or release dashboard. Enrol a passkey if the approving account does not already have one, review the permission change, and approve or reject the release.

    The default Action setting returns successfully when the release reaches Awaiting approval. The service workflow continues waiting for the browser decision and publishes after approval.

Connect a Changesets workflow

The generated .github/workflows/emdash-release.yml accepts the Changesets Action published-package JSON through workflow_call. Add an output to the existing Changesets job, then call the EmDash workflow from a dependent job. Replace release and changesets when the existing job or step uses another ID.

Changesets Action v2 uses the published-packages output. Add the following job output and caller to a workflow using Changesets CLI v3:

jobs:
  release:
    # Keep the existing runner, permissions, and steps.
    outputs:
      published: ${{ steps.changesets.outputs.published }}
      published-packages: ${{ steps.changesets.outputs['published-packages'] }}

  publish-emdash-plugins:
    needs: release
    if: needs.release.outputs.published == 'true'
    uses: ./.github/workflows/emdash-release.yml
    with:
      published-packages: ${{ needs.release.outputs['published-packages'] }}
    permissions:
      contents: read
      id-token: write
      attestations: write

Changesets Action v1 uses the camel-case publishedPackages step output. Use this expression for a workflow using Changesets CLI v2:

jobs:
  release:
    # Keep the existing runner, permissions, and steps.
    outputs:
      published: ${{ steps.changesets.outputs.published }}
      published-packages: ${{ steps.changesets.outputs.publishedPackages }}

  publish-emdash-plugins:
    needs: release
    if: needs.release.outputs.published == 'true'
    uses: ./.github/workflows/emdash-release.yml
    with:
      published-packages: ${{ needs.release.outputs['published-packages'] }}
    permissions:
      contents: read
      id-token: write
      attestations: write

Keep Changesets responsible for its version pull request and package publication. The EmDash caller runs only when Changesets reports published: true. For private EmDash-only packages, set both privatePackages.version and privatePackages.tag to true in .changeset/config.json. Add unrelated private applications and test fixtures to ignore.

Add another package

Prepare the package profile from its source directory. The existing root workflow and repository connection are reused:

pnpm exec emdash-plugin profile setup --dir packages/comments

With Changesets, add the package to a changeset and merge its version pull request. With the package-tag trigger, update the package version and push its tag:

git tag comments@1.0.0
git push origin comments@1.0.0

The workflow resolves comments to one emdash-plugin.jsonc, checks the selected version, and verifies that the signed profile names the connected repository before accepting artifact uploads. Duplicate package IDs and version mismatches fail before attestation.

What the release service verifies

The service completes these checks before it writes a release:

  1. The GitHub OpenID Connect (OIDC) token names an authorised repository, owner, workflow, ref, environment, commit, run, and GitHub-hosted runner.
  2. The package profile exists, is signed by the publisher, contains delegated-release settings, and names the same canonical GitHub repository.
  3. The requested package and version match the built plugin bundle.
  4. The package checksum matches the uploaded bytes.
  5. GitHub provenance covers the same bundle, repository, workflow, commit, and run.
  6. The release record’s declared access matches the bundle manifest.
  7. The version record does not already exist.
  8. Any required passkey approval covers the exact verification result and current profile revision.

The Action requests a fresh GitHub OIDC token for each service call. Bundle and provenance files enter private transient storage only after the workflow is authorised. The service uploads verified package and image bytes to the publisher’s personal data server (PDS), creates the release record there, and exposes the verified provenance through an immutable checksum-addressed URL.

Authority boundaries

Each credential has one job:

CredentialUsed byAuthority
Local CLI OAuth sessionemdash-plugin profile setupCreate or update the publisher-owned package profile after local confirmation.
GitHub OIDC tokenRelease ActionIdentify one GitHub workflow run to the service. It grants no AT Protocol write access.
Release-service delegationRelease serviceCreate package release records and upload the required blobs.
Publisher application sessionRelease dashboardAuthorise workflow connections and revoke delegated publishing.
Approver session and passkeyApproval pageApprove or reject one checksum-bound release verification.
Cloudflare Access identityService operator consoleOperate the hosted service. It does not represent a publisher or approver.

The service stores publisher and approver state separately. Signing in to view your releases does not grant operator access, and an operator identity cannot approve a release as a publisher.

Action behaviour

The generated workflow uses the Action from apps/release-action. The Action accepts either a built bundle plus raw Sigstore provenance, or a compatibility release-file containing checksum-bound HTTPS artifact sources. Do not combine release-file with bundle or provenance inputs.

The standard generated workflow supplies these inputs. They are shown here so you can review the generated file without having to infer what each value authorizes:

InputValue
service-urlRelease-service HTTPS origin.
publisher-didDID that owns the package profile and releases.
bundle-fileThe single tarball produced by emdash-plugin release prepare.
provenance-fileRaw bundle-path output from actions/attest-build-provenance.

The Action returns these outputs:

OutputMeaning
connection-urlBrowser URL for first-run workflow approval.
intent-idRelease intent identifier.
statePublished, terminal, or awaiting_approval state.
approval-urlBrowser URL when passkey approval is required.
release-uriPublished release AT URI.
release-cidPublished release record CID.
reason-codeStable reason for a terminal intent.

See the Action reference for optional inputs, custom URL-source workflows, polling controls, and exact output behavior.

Troubleshooting

PACKAGE_PROFILE_REQUIRED

The package profile is missing, lacks delegated-release settings, uses a non-canonical repository URL, or names a different repository from the GitHub workflow.

Run profile setup locally with the publisher account, then start the workflow again:

pnpm exec emdash-plugin profile setup

This check runs before the service accepts bundle or provenance uploads.

Public repository required

GitHub uses a private Sigstore trust root for private and internal repositories. The release verifier currently trusts only public GitHub provenance. Move the release workflow to a public repository or publish locally with emdash-plugin publish.

WORKLOAD_NOT_ALLOWED

The GitHub repository, owner, workflow file, ref, or environment does not match the approved workflow policy. Open the release dashboard and approve a new workflow connection with the intended scope.

PROFILE_FETCH_FAILED

The service could not verify the profile from the publisher’s PDS. Retry after the account provider is available. Run emdash-plugin profile setup if the profile was removed or changed.

POLL_TIMEOUT

The Action reached timeout-minutes before workflow approval, release approval, or publication completed. Check the release dashboard for the intent state before rerunning. A rerun of the same GitHub Actions run reuses its idempotency key.

Revoke automated publishing

Select Turn off automated publishing in the release dashboard. Revocation clears the retained release delegation. Existing package profiles, releases, moderation labels, installed plugins, and the dashboard login do not change.

Reconnect publishing and approve the workflow again before the next automated release.