Publish from CI
Goal: ship a new version of your app to the marketplace from a GitHub Actions (or any) CI run on every push to a release branch, without human intervention.
Prerequisites:
- A locally working
orion publishflow. - A release branch convention (e.g. push to
release/*triggers publish). - An OAuth client allowed to publish from headless contexts. The standard
orion loginflow uses OAuth2 PKCE with an interactive browser callback, which doesn't work in CI. The pragmatic workaround is to capture an existing token (access + refresh) on your dev machine and inject it into CI as a secret. This works but is fragile — tokens rotate and the secret has to be rotated with them. If your tenant administrator can issue a service-account credential for you, that's a cleaner long-term option; check with them.
Steps
Capture a token locally. Run
orion loginon your dev machine, then read the saved token from your platform's Conf directory. On macOS this is~/Library/Preferences/orion-cli-nodejs/config.json. The CLI nests credentials under anauthkey —auth.accessToken,auth.refreshToken,auth.expiresAt, andauth.scope. Copy all four values.Add the credentials as CI secrets. In GitHub Actions: repo Settings → Secrets and variables → Actions → New repository secret. Add
ORION_ACCESS_TOKEN,ORION_REFRESH_TOKEN,ORION_TOKEN_EXPIRES_AT, andORION_TOKEN_SCOPE.expiresAtis stored as epoch milliseconds (a number); copy it verbatim.Reconstitute the token at the start of the workflow. The CLI reads from the Conf directory, so your CI job must write a
config.jsonto the right path before running anyorioncommand. The credentials must be nested under theauthkey — a flat top-level shape will be silently ignored by theConfschema. On a Linux runner that's~/.config/orion-cli-nodejs/:yaml- name: Restore Orion CLI credentials run: | mkdir -p ~/.config/orion-cli-nodejs cat > ~/.config/orion-cli-nodejs/config.json <<EOF { "auth": { "accessToken": "${{ secrets.ORION_ACCESS_TOKEN }}", "refreshToken": "${{ secrets.ORION_REFRESH_TOKEN }}", "expiresAt": ${{ secrets.ORION_TOKEN_EXPIRES_AT }}, "scope": "${{ secrets.ORION_TOKEN_SCOPE }}" } } EOFBump the version in
orion-app.json(semver). The marketplace rejects re-uploads of the same version. If your manifest version is wired topackage.json, usenpm version patch --no-git-tag-version. Otherwise, ajqone-liner works:bashjq '.version = "1.2.3"' orion-app.json > orion-app.json.tmp && mv orion-app.json.tmp orion-app.jsonRun the build and publish.
orion publishrunsnpm run buildfor you by default, so a single command is enough. Use-c, --changelogto skip the interactive prompt:bashorion publish --changelog "$(git log -1 --pretty=%B)"If your CI job already ran the build in a prior step (e.g. to share the bundle with a test job), pass
--no-buildto skip the redundant rebuild. Note:--no-buildrequires adist/.orion-build-stampwritten by an earlierorion publishrun in the same workflow; the CLI refuses to publish if your source has moved on since then. The simplest CI pattern isorion publish --dry-run(writes the stamp + validates) followed byorion publish --no-build(reuses it):bashnpm ci orion publish --dry-run # writes dist/.orion-build-stamp orion publish --no-build --changelog "$(git log -1 --pretty=%B)" # reuses itUse
--dry-runon PR builds. A separate workflow that runs on pull requests can useorion publish --dry-runto verify the upload would succeed without consuming a marketplace version slot. Dry-run still authenticates, so the token-restoration step is still required.
Caveats
- Tokens rotate. If CI fails with
Auth failed (401), regenerate locally and update the secrets. There is no programmatic refresh path that bypasses the original PKCE flow. - Tokens are tied to a single developer account. If that developer leaves the team, every CI workflow using their token breaks.
- Long-term, ask your tenant admin about service-account credentials — they're the right fit for headless workflows.
Verify
A clean run prints Uploaded for review! followed by the new version's status (e.g. submitted or pending_review). The app becomes live in the marketplace only after a reviewer approves it.