Repository navigation
feat: deploy on merge to main with GitHub OIDC instead of a stored refresh token - #147
dawsontoth wants to merge 12 commits into
Conversation
…fresh token Every template's workflow now tests pull requests and deploys when a change merges to `main` (or on Run workflow), instead of on a pushed `v*` tag: - only the deploy job may request an identity token (`id-token: write`), in the `production` environment; HARPER_CLI_TARGET is a repository variable, and the step fails if it is empty rather than falling back to a saved login - it uploads the checkout (`harper deploy project=<name> restart=rolling`), with `persist-credentials: false` so the job's token is not in the uploaded `.git`, and waits for the rolling job: a missing job id fails, a failed poll is retried, and a job never found is reported as an unknown outcome - the run summary names the deployment, its certification, and how to go back - Next.js keeps its own copy, which builds with `next build` before uploading - checkout and setup-node move to the v7 pins this repository's CI uses `deploy:setup-ci` (`harper deploy setup=true provider=github-actions`, Harper 5.4+) creates the trust policy, the deploy-only user and the repository variable, so no setup script is copied into projects. `deploy` becomes the same payload deploy for a manual run, and `deploy:setup` remains for private npm registries. Each template's README Deployment section explains how the workflow's `harper` commands authenticate, the one-time setup, certification, going back by deployment_id, staging, refused runs and migration. The root README, `--help` and the closing message point at the same steps. template.test.js now runs in the default suite, and the Studio template build drops the whole Deployment section instead of only its first paragraph. Closes #144 Closes #145 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… reaches shell commands From the pre-push review: - the deploy job skips a run whose commit is no longer the head of main, so a run whose tests finished late can't deploy over a newer release - package scripts and workflows use a new your-component-name-here placeholder, the project name reduced to [A-Za-z0-9_-], so a directory named `my app` or `a; b` can't split arguments or run a second command - the summary says when the rollout did not complete on every node - the README's migration steps install the workflow before the old secret is removed, and say why it can go; a manual deploy returns before the rollout finishes; `deploy:setup` is dropped, since payload deploys need no clone credential and the README sets up a private registry directly - --help names the deploy:setup-ci script without assuming npm Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s with workflow concurrency From the second pre-push review: - the component name comes from the scaffolded directory's name, as `harper deploy` derives one, instead of the project-name argument, which is `.` for `create-harper .` and made every such project deploy as `app` - workflow-level concurrency per ref replaces the stale-run guard: runs on main go one at a time, so an older commit can't deploy after a newer one, without the guard's case where a newer commit's failed tests left nothing deployed - the migration steps say to scaffold with the project's own package manager and to reuse the copied workflow's project= value; the root README points older projects at the same steps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ch tip Re-running failed jobs keeps the run's original commit, so after a newer release deployed it would replace that release with an older one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d name a failed deploy The harper CLI JSON-parses each key=value argument, so a project directory named `2026` or `true` would send a number or boolean as the project. Such names now get an `app-` prefix. The run summary says "failed" when the deploy returned no deployment, rather than implying a partial rollout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request migrates the template deployment workflows to a modern, OIDC-based GitHub Actions deployment model, eliminating the need for stored Harper credentials in GitHub. It introduces a new deploy:setup-ci script across templates, sanitizes project directory names into valid Harper component names, and updates the scaffolding process to guide users through the new deployment setup. Feedback is provided regarding a fragile regular expression used to strip the deployment section in Studio templates, which could fail if it is the last section in the README.
… checkout has CRLF Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…LF line endings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tes' dependencies
The vanilla templates' config.yaml loads @harperfast/schema-codegen, but
their package.json listed it under devDependencies. The cluster installs a
deployed app's dependencies itself and omits devDependencies, so the
package was missing there: a 5.4 cluster's canary rejected the first
deploy of a fresh vanilla project ("Unable to find package
@harperfast/schema-codegen"). The react and vue templates already list it
under dependencies.
A test now checks that every package a template's config.yaml loads is in
its dependencies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no cluster URL given, the scaffolded .env set HARPER_CLI_TARGET to a placeholder. The harper CLI reads .env ahead of the target `harper login` saves, so every command in the project, `deploy:setup-ci` and `deploy` included, went to a host that doesn't exist. The target line is now written commented out, so it still shows where the URL goes. `harper login` run in the project then adds the real HARPER_CLI_TARGET to .env, since the file sets none. Appending to an existing .env that already sets a target leaves the line out, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`deploy:setup-ci` now also grants registration_info, the version check `harper deploy` makes before it uploads (HarperFast/harper#3127). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| LAST="could not be read" | ||
| for attempt in $(seq 60); do | ||
| sleep 10 | ||
| harper get_job id="$JOB_ID" json=true > "$RUNNER_TEMP/job.json" || continue |
There was a problem hiding this comment.
major — polling can miss the node-local rollout job.
There was a problem hiding this comment.
Known limitation, kept on purpose: CI uses the single cluster URL, and per-node URLs would need a trust policy for each node. The job record only exists on the node that received the deploy. If the polls never reach that node, the step fails, reporting that the job's outcome is unknown, and never reports success it can't see. The fix belongs in core: HarperFast/harper#3060 records each node's outcome on the replicated deployment record, and the workflow will switch to polling get_deployment when it lands. Polling through the CLI (HarperFast/harper#3058) would hit the same problem unless it streams the deploy or reads that record.
🤖 Posted by Claude Code
…<id> Pasted into a shell, `deployment_id=<id>` reads `<id>` as input redirection from a file named id. The READMEs and the workflow summary now use DEPLOYMENT_ID, and the README says to put the deployment's id there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
⊙ Problem
create-harper projects deploy on version tags with a refresh token stored as a GitHub secret, which someone has to mint and paste in, and which expires. Closes #144. Closes #145. Part of #143.
❓ Your call: should this wait for Harper 5.4.0?
deploy:setup-ciruns core Harper's new setup command (HarperFast/harper#3127), at the maintainer's direction, instead of #145's script in every template. On an earlier Harper it fails with an unknown provider, so this shouldn't merge before 5.4.0 ships. The alternative was shipping manual setup steps now.💡 Solution
main. The deploy job stores no credential: itsharpercommands authenticate with GitHub's OIDC token.npm run deploy:setup-cisets the cluster up once.main.⚖️ Alternatives
by_ref=true) — needs a clone credential on the cluster and can't carry a build's output, which the Next.js templates need.❓ Your call: is a re-run guard enough to keep an older commit from deploying last?
Runs on
maingo one at a time, and a re-run refuses a commit that is no longer the tip. GitHub doesn't promise push order within the group, so two pushes arriving out of order could still deploy the older one last. A stricter guard brought back a case where nothing deploys.❓ Your call: is a wait that can miss the job acceptable for now?
The rolling job lives only on the node that received the deploy. If the wait's polls never reach that node through a load balancer, the step fails with the outcome unknown, never a false success. HarperFast/harper#3060 fixes this by recording each node's outcome on the replicated deployment record.
🔧 Changes
The shared workflow runs tests on
pull_request, and deploys onpushtomainandworkflow_dispatch. The deploy job:id-token: write;persist-credentials: false;Concurrency orders runs per ref.
actions/checkoutandactions/setup-nodemove to this repository's v7 pins. The Next.js copies add a build step.Every template gets the workflow (fanned out by
applySharedTemplates), package scripts wheredeployis the payload deploy,deploy:setup-ciis new anddeploy:setupis gone, and a README## Deploymentsection. That section covers how the workflow'sharpercommands authenticate, one-time setup, certification, going back bydeployment_id, staging, a refused run, migrating an older project, and private npm registries:nextjs: workflow, package.json, READMEnextjs-ts: workflow, package.json, READMEreact: workflow, package.json, READMEreact-ts: workflow, package.json, READMEreact-ssr: workflow, package.json, READMEreact-ts-ssr: workflow, package.json, READMEvanilla: workflow, package.json, READMEvanilla-ts: workflow, package.json, READMEvue: workflow, package.json, READMEvue-ts: workflow, package.json, READMEvue-ssr: workflow, package.json, READMEvue-ts-ssr: workflow, package.json, READMEThe root README,
--helpand the closing message point at the same steps.lib/pkg/toComponentName.jsand theyour-component-name-hereplaceholder.The Studio template build now drops the whole Deployment section, and
applySharedTemplates' note explains why the Next.js copies stay separate.The vanilla templates list
@harperfast/schema-codegenin dependencies (vanilla-ts), sinceconfig.yamlloads it and the cluster's install skips devDependencies.At the maintainer's direction,
copyFilecomments out a placeholder target in a scaffolded.env, which the CLI would otherwise use ahead of theharper logintarget.harper loginin the project then adds the real one.The setup command is documented in Document
harper deploy setup=true provider=github-actions, and how a workflow uses a trust policy documentation#722.✅ Verification
npm test: 366 tests, including the workflow tests.npm run lint:check,npm run format:checkand commitlint pass.harper deploy setup=true provider=github-actionsharper#3127, with a mock OIDC issuer: a vanilla project scaffolded with no URL,harper login,npm run deploy:setup-ci, then the workflow's own deploy steps. The release wascertified, the wait saw COMPLETE, and the app served.mainwith GitHub OIDC instead of a stored refresh token #144's done-when).What the tests and the end-to-end run cover
template.tests/deployWorkflow.test.js: the trigger,id-token: writeon the deploy job only, no stored credential,persist-credentials: false, the explicit project, the wait loop, concurrency, the re-run guard and the pins, a hostile directory name, numeric ones, and every packageconfig.yamlloads being a dependency.template.tests/template.test.js, now in the default suite, checks the commented-out target and a scaffold with a URL;copyFile's tests cover each placeholder form. The closing-message test covers the deploy note.harper loginwrote the real target into.env; setup ran through a fakegh; the deploy job ran with no stored credential and the cluster logged no refused operation. A token from another branch or environment, or afterdrop_oidc_trust, was refused. The first run found the devDependency and placeholder-target bugs fixed here.🤖 Generated with Claude Code
Related PRs: #146 overlaps (it set
HARPER_CLI_TARGETin the scaffolded.env; this comments out the placeholder it writes when no URL is given)Complexity: medium
Review-Coverage: authored=claude; ran=gemini,codex,cursor-grok,cursor-muse; adjudicated=domain; declined=cursor-composer,cursor-kimi; rounds=5; full=2 @ cfa5b75
Review-Attention: study ~13m (decisions: payload-over-by-reference, guard-on-rerun-only, cluster-wide-deploy-user, app-prefix-for-json-literals, comment-out-placeholder, dotenv-runtime-dependency, do-less-alternative) @ cfa5b75