Skip to content

chore: back-merge main -> staging (bootstrap new pipeline on staging) - #396

Merged
codewizdave merged 50 commits into
stagingfrom
ci/backmerge-bootstrap
Aug 6, 2026
Merged

chore: back-merge main -> staging (bootstrap new pipeline on staging)#396
codewizdave merged 50 commits into
stagingfrom
ci/backmerge-bootstrap

Conversation

@martyy-code

Copy link
Copy Markdown
Contributor

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

main has the new release pipeline (PR #394, merged 2026-08-06). staging was last touched 2026-08-05 (before #394 landed) and still has the legacy pipeline: a single publish.yml with three parallel jobs (release, hotfix, canary) using an entrypoint pattern that never landed, plus an outdated changesets-version.yml (typo, missing 's') and a 4-job ci.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:

  • The new changeset-check job runs on PRs against staging
  • changesets-version.yml opens the Version Packages PR after each merge to staging
  • backmerge.yml keeps 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 (adds backmerge.yml, changesets-version.yml, wiki-gh skill, new ci.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:

  • Visit https://github.com/deessejs/fp/tree/staging to confirm the file count
  • Check Settings -> Branches to confirm staging still has changeset-check as a required status check
  • Optionally open a dummy PR (PR chore: dummy e2e test of the new release pipeline #395 was closed because it was branched off the old staging) to verify the pipeline end-to-end

🤖 Generated with Claude Code

martyy-code and others added 30 commits August 3, 2026 14:20
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
github-actions Bot and others added 20 commits August 6, 2026 11:08
…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.
@codewizdave
codewizdave merged commit f86ac7a into staging Aug 6, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants