Skip to content

Automated plugin releases

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.

Prepare the following before starting:

  • A public GitHub repository containing a sandboxed EmDash plugin.
  • A valid emdash-plugin.jsonc with slug, publisher, license, an author, and a security contact. Set repo to the canonical GitHub URL, or enter it 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:

Terminal window
pnpm exec emdash-plugin validate
  1. Sign in to the plugin CLI with the Atmosphere account that owns the package.

    Terminal window
    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.

    Terminal window
    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.

    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.

    The signed-in Atmosphere account becomes the initial approver. The profile also binds the package to the canonical GitHub repository URL and requires verifiable provenance.

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

    Terminal window
    pnpm exec emdash-plugin profile setup

    In a non-interactive terminal, pass --yes to accept the default approval policy. Pass --repository <https-url> when the manifest does not contain repo, 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:

    Terminal window
    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.

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:

.github/workflows/release.yml
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:

.github/workflows/release.yml
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.

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

Terminal window
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:

Terminal window
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.

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.

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.

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.

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:

Terminal window
pnpm exec emdash-plugin profile setup

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

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.

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.

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.

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.

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.