chore: back-merge main -> staging (bootstrap new pipeline on staging) - #396
Merged
Conversation
The changesets/action v1.9.0 input for the version commit message is named 'commit', not 'commit-message'. The latter is a v2 input. Observed on the e2e test run (PR #370 merge to staging): Unexpected input(s) 'commit-message', valid inputs are ['github-token', 'publish', 'version', 'cwd', 'commit', 'title', 'setupGitUser', 'createGithubReleases', 'commitMode', 'branch', 'prDraft'] This commit also requires the org-level setting 'Allow GitHub Actions to create and approve pull requests' to be enabled in Settings → Actions → General. See docs/engineering/plans/release-pipeline-github-ui-setup.md §6.
ci(changesets): fix input name for v1 action (commit-message -> commit)
…rsion chore: version packages (1.0.1 -> 1.0.2)
The check-release job counts files in .changeset/ to decide whether to publish. This was designed to skip publish workflows on infra-only pushes to main (no user-visible changes). Problem: by the time a Version Packages PR is merged into main, the changeset has already been consumed. .changeset/ is empty. The check-release output reports 'no pending changesets' and gates the release job off, even though there is a real release to publish. The release job should always run on push to main; whether to publish is decided by the anti-republish guard in the release job itself. The guard queries npm for the local version: if it exists, exit cleanly with a 'version already published' error; if not, proceed with publish. No silent no-op, no missed release. Same logic for hotfix: a tag push is itself an explicit signal of intent to publish. The 'Verify version matches tag' step already protects against version mismatches. The check-release job is kept and gates the canary job only — which is the only path that needs to know about pending changesets ahead of time (because snapshotting a non-existent changeset is meaningless). Observed on the e2e test (PR #375 merge to main): release job was correctly skipped due to .changeset/ being empty, but the expected behavior was to publish @latest 1.0.2 with provenance.
ci(publish): ungate release and hotfix from check-release
The smoke test asserted typeof m[name] === 'function' for every
named export. This was wrong for 'none', which is exported as
'export const none: None = { ... }' — a runtime sentinel object,
not a function constructor. The smoke test failed on a real
release because 'none' is an 'object', not a 'function'.
Fix: accept either 'function' (ok/err/some/maybe) or 'object'
(none, sentinels). Same logic in release and hotfix jobs.
Observed on the e2e test (run 30816144216 after PR #376 merge):
release job passed anti-republish guard, build, test, then
failed at the smoke test step with:
Error: expected export missing: none
The publish itself never happened because the smoke test gate
is positioned before 'pnpm changeset publish' in the job
sequence.
ci(publish): fix smoke test for object sentinel exports
'changeset publish' (the Changesets CLI wrapper around npm publish) does not accept '--provenance' as a flag. It forwards a curated subset of npm publish flags. The flag surfaced during the e2e test: error Unknown flag for publish: --provenance Usage: changeset publish [--tag <name>] [--otp <code>] [--no-git-tag] Provenance attestation is still emitted: it is enabled by 'publishConfig.provenance = true' in packages/fp/package.json (commit 6acc6dd) and by Trusted Publishing on npmjs.com. Both conditions are sufficient for npm to emit and publish a Sigstore-signed provenance statement automatically. The flag was a belt-and-braces measure that turned out to be incompatible with the Changesets CLI. Removed in both release and hotfix jobs. Observed on the e2e test (run 30817023010 after PR #377 merge): release job progressed past smoke test (PR #377 fix) but failed at the publish step with 'Unknown flag for publish: --provenance'.
ci(publish): drop --provenance flag (changeset publish rejects it)
Replaces the two-workflow design (changeset-version.yml on staging + publish.yml on main) with a single workflow that runs on PR-merge into main, modeled after the @deessejs/errors release workflow in this same repository. Old design (broken): - changeset-version.yml ran on push to staging and opened a 'Version Packages' PR targeting main. The PR's source branch was based on staging, so the diff was enormous and GitHub rejected the PR with 'Validation Failed: base' (run 30828060355). - publish.yml ran on push to main and dispatched to three jobs gated on a check-release job. The check-release job counted .changeset/*.md files, but at push-main time the changeset had already been consumed (in theory). Result: the release job was skipped on the canonical Version Packages merge. New design (working): - publish.yml runs on PR-merged into main, tag push on main, or workflow_dispatch. The job detects whether the merge commit introduced changeset files. If yes: bump, push back to main, build, test, smoke, publish, tag, GitHub Release — all in one run. If no: exit 0 with a 'No changesets in the merge — skipping release' log. - No more separate 'Version Packages' PR. The version bump is applied as a follow-up commit on main by the bot, after the merge that introduced the changeset. - Tag push and manual workflow_dispatch skip the changeset detection and always publish. Files: - .github/workflows/publish.yml: rewritten as a single release job covering all three publish paths. - .github/workflows/changeset-version.yml: deleted (no longer needed; the version bump is now in-line in publish.yml). Trust chain: - npm Trusted Publisher: workflow filename = publish.yml, environment = release (unchanged). - OIDC token requested at job level (id-token: write). - contents: write at job level so the bot can push the version bump commit and the tag. Concurrency: - Single concurrency group on github.ref. cancel-in-progress: false because we want retried publishes to finish, not get cancelled. All third-party actions remain SHA-pinned.
ci(release): refactor to monorepo senior pattern (single workflow on PR-merge)
* chore: add changeset for 1.1.0 release Triggers Changesets to bump @deessejs/fp from 1.0.2 to 1.1.0 when the next Version Packages PR is opened on staging. This advances the published npm version past the dummy release-test artifact (1.0.2) and produces the first legitimate minor bump since the Trusted Publishing migration. * docs(github-ui-setup): drop tag protection §4, document senior pattern Observed on the 1.0.2 dummy release run (30817274927): the tag push step in publish.yml failed with HTTP 403 because the GitHub tag protection rule on 'v*' blocked the github-actions[bot] push. The bot is not an allow-listable actor on tag protection rules — only users, teams, GitHub Apps, and roles are. §4 previously recommended enabling a tag protection rule on v* with an empty allow-list. That recommendation is wrong for any pipeline that uses GITHUB_TOKEN to push tags. The correct senior pattern, used by @deessejs/errors in this same repo and by Vite/Vue/Nuxt/Cloudflare SDK in production, is to skip tag protection entirely and rely on: - environment 'release' as the human gate - anti-republish guard as the no-double-publish check - OIDC provenance as the audit trail - branch protection on main as the gate on direct pushes §4 rewritten to document this decision and explain why tag protection is intentionally absent. No code changes. --------- Co-authored-by: release-pipeline-test <test@example.com> Co-authored-by: Dave <47763218+codewizdave@users.noreply.github.com>
Adapt the README structure used across the deessejs org to the shape of @deessejs/fp (a focused TypeScript library, not a SaaS app). Adapted sections: - Banner: dropped (no banner image). Kept the title + tagline + badges layout. - What's included: replaced the SaaS layers table with the @deessejs/fp public API surface (Result, Maybe, Try, Unit, functional utils, async utils, predicates, collection types, generator composition, @deessejs/errors integration). - Why this stack: simplified to ESM-only, zero runtime deps, TS 6 first-class, pnpm lockfile-clean, Vitest. - Quick start: install + usage snippet + engine compatibility table. - Available commands: workspace + per-package tables for @deessejs/fp and apps/web. - Environment variables: replaced with a 'Compatibility' section listing peer deps and engines (lib has no env vars). - Project structure: reflowed around packages/fp + apps/web (2 workspaces) instead of the SaaS monorepo. - Deployment: replaced with 'Publishing' that documents the Changesets + OIDC pipeline (with a link to docs/engineering/plans/release-pipeline.md). - Customization: replaced with notes on the public API surface and how to consume the lib. - Architecture notes: ESM-only, strict types, composition over inheritance, zero-runtime abstractions, smoke-tested build, @deessejs/errors opt-in. - Contributing: running lint/test before PR + adding a changeset for user-facing changes + updating docs/internal/product/README.md. Dropped sections (not applicable to a library): - Deploy with Vercel / GitHub Codespaces buttons - Environment variables table (lib has none) - Deployment / Per-app mapping (no production URL) - Multi-tenant / single-tenant customization notes Sibling project callout: - Adds a top-of-file callout for @deessejs/errors as the natural pairing library, matching the org convention of cross-referencing sibling packages. No code changes. Documentation only.
Two follow-ups on the README rewrite: - Remove the banner placeholder note (no banner image applies to this repo, but the explanatory note is unneeded once the title and tagline are in place). - Link to https://github.com/deessejs/errors at every mention of '@deessejs/errors' in the body. The previous version had plain-text mentions in the compatibility table, the engine bullet, the integration test bullet, and the install block; these are now Markdown links (or in-block comments for the bash example) so readers can navigate to the sibling repo.
Adds a third callout after 'Sibling projects' that signals production usage of @deessejs/fp at deessejs.com — every error path and nullable value in production goes through this library and its sibling @deessejs/errors. This is a credibility signal for npm visitors: the library is not a side project, it is the foundation the deessejs app runs on. Placement: in the existing callout block at the top of the README, between 'Sibling projects' and the section divider.
Per reviewer feedback: merge the 'Sibling projects' and 'Used internally' callouts into one paragraph on a single line. The 'Sibling projects' portion now references @deessejs/fp's own repo via a proper Markdown link (it was missing one). 'Used internally at deessejs.com' is now a single bold sentence appended to the same blockquote, not a separate callout. Net effect: less visual noise at the top of the README, same information delivered.
Unifies the support address across the repo to support@deessejs.com: - README.md: 'hello@nesalia.com' -> 'support@deessejs.com' - .github/SECURITY.md: 'security@nesalia.com' -> 'support@deessejs.com' Decided against keeping a separate security@... address; the single support@... inbox routes security reports to the right team (as documented in SECURITY.md). No code changes. Documentation only.
docs: rewrite README to align with the deessejs org template
The npm-published package reads its README from packages/fp/README.md, not from the root README. Until this commit, the package README was the old user-facing README (description, install, quick start, then a series of per-API sections). The root README was restructured in #387 to align with the deessejs org template. This commit replaces packages/fp/README.md with a copy of the newly restructured root README, so npm consumers see the same coherent structure as GitHub visitors. Why copy rather than symlink: - npm does not honor symlinks in the published tarball. - The published README must be a real file. - A copy is the canonical idiomatic approach for monorepos that publish a single package from a multi-package root. Side effect: the package README is now slightly redundant with the root README (both reference the monorepo's publishing pipeline, project structure, etc.). This is acceptable for now; a future improvement could split the content into a 'repo overview' (root) and a 'package API' (file inside packages/fp/). No code changes. Documentation only.
Three small improvements to packages/fp/package.json that make the npm listing more discoverable and more accurate: 1. **Keywords expanded** (5 -> 13). Added: try, unit, pipe, flow, error-handling, async, monorepo, deessejs. The README references these concepts but the keywords array did not list them. 2. **bugs field** added. Explicit URL to the issue tracker (defaults to repository.url + /issues but the explicit form is more reliable) and support email. Renders as a 'Report vulnerabilities' link on npm. 3. **peerDependenciesMeta[@deessejs/errors].optional** set to true. The README and the install snippet already document @deessejs/errors as optional, but the npm metadata did not surface this. With this flag, npm advertises the peer dependency as optional at install time. No code changes. Documentation / metadata only.
docs(fp): sync packages/fp/README.md with the restructured root README
Patch bump to push the restructured README + enriched package metadata (keywords, bugs, peerDependenciesMeta) to npm. No code change. The version bump is needed because the release pipeline is PR-merge-driven and the prior merges #387 and #388 did not trigger it (their changesets were empty). The hotfix tag path is the fallback for this case.
Manual bump for the tag-driven hotfix path. The hotfix job verifies that the tag version matches package.json before publishing.
The wiki-gh skill teaches Claude (or a maintainer consulting .claude/skills/) how to read and edit the deessejs/fp GitHub wiki. It complements the public Wiki-Operations-with-gh-cli page on the wiki by focusing on automation: exact commands, page structure convention, sidebar management, and edge cases. The skill follows the format of the existing version-monitor skill: frontmatter (name, description) plus Markdown sections with bash code blocks and reference tables. Closes the request to document the wiki workflow as a project skill.
chore(skills): add wiki-gh skill for wiki automation
…us, fix concurrency
…st.closed, not push)
…peline Replace § 7.3 (release.yml → publish.yml), § 8.3 (hotfix.yml → reuses publish.yml), § 9 (custom wrapper reasoning), § 11 + 11a (migration plan as completed), and Appendix A (file inventory). The previous content described an architecture that never landed; the new content matches the actual workflows on the branch.
… pattern Section 5 had old content describing an entrypoint pattern with three reusable workflows (_publish-release.yml, _publish-hotfix.yml, _publish-canary.yml) that never landed. Replace with the actual single-workflow architecture: publish.yml is the only Trusted Publisher entrypoint, and it serves both regular and hotfix release paths. Also fix § 4 (permission claim) and § 7.2 (Code Owners description) to match the actual workflow files.
- collapse bump + push-bump into one job (5-job chain instead of 6) - remove environment: release from validate (publish env gates publish only) - add tag-already-exists guard before git tag in release job - harden changeset-check with a merge-base sanity check and an explicit base-ref fetch (replaces the depth=0 heuristic) PR #394 follow-up.
ci: harden release pipeline (six-job publish, changeset-check, auto-backmerge)
Conflict resolution: keep the post-#394 description of the workflow permissions (workflow-level permissions: {} is empty, push-bump and release declare contents: write at the job level). Drops the obsolete claim that GITHUB_TOKEN has workflow-level contents: write.
Add a patch-level changeset so the per-PR Changeset rule passes on the bootstrap PR. The changesets-version.yml workflow will consume this file when it opens the next Version Packages PR; the resulting CHANGELOG entry will document the bootstrap.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
One-time back-merge to bring the new release pipeline (publish.yml 5-job chain, changesets-version.yml, backmerge.yml, ci.yml 5-job) onto staging. After this lands, the new pipeline runs end-to-end on both branches.
Why this PR exists
mainhas the new release pipeline (PR #394, merged 2026-08-06).stagingwas last touched 2026-08-05 (before #394 landed) and still has the legacy pipeline: a singlepublish.ymlwith three parallel jobs (release, hotfix, canary) using an entrypoint pattern that never landed, plus an outdatedchangesets-version.yml(typo, missing 's') and a 4-jobci.yml.If we leave staging as-is, future merges to staging won't trigger
changesets-version.yml(the new one is only on main), and staging accumulates Changesets without automation. We need staging to be at the same commit as main so that:changeset-checkjob runs on PRs against stagingchangesets-version.ymlopens the Version Packages PR after each merge to stagingbackmerge.ymlkeeps staging in sync with main from now on (no more manual bootstrap)What this PR does
This is a single fast-forward to
main. No conflicts, no manual merge resolution. After this lands, staging will be a superset of itself (addsbackmerge.yml,changesets-version.yml,wiki-ghskill, newci.yml/publish.yml/docs/CHANGELOG) minus two Changeset files that were already consumed on main (release-1.1.0.md,dummy-e2e-release-test.md).Verification
After merge:
https://github.com/deessejs/fp/tree/stagingto confirm the file countstagingstill haschangeset-checkas a required status check🤖 Generated with Claude Code