Skip to content

feat(APP-963): modular release system — design + modules - #5

Open
tyhonchik wants to merge 6 commits into
mainfrom
app-963-study-and-design-the-modules-structure-to-release
Open

feat(APP-963): modular release system — design + modules#5
tyhonchik wants to merge 6 commits into
mainfrom
app-963-study-and-design-the-modules-structure-to-release

Conversation

@tyhonchik

@tyhonchik tyhonchik commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What & why

APP-963 (SEAL best-practices; blocks SREDO-695/697/698). Every Aragon repo re-implements a near-identical release pipeline. This design-first PR standardises them into loadable modules here, so secrets are handled the same way, the broad-permission Vercel token has one place it can be read, and a fix patches everywhere via one bump.

Deliverables

  • docs/release-design.md — the study/design: scenarios A–D (app / library / backend / indexer), the Build → Test → Release → Deploy spine, the module catalog, the explicit-paths credential model, the security contract, and distribution/versioning. (primary deliverable)
  • steps/ — 12 new composite actions incl. compute-version (the pluggable changesets | semantic-release seam). Scripts are co-located and called via $GITHUB_ACTION_PATH so the actions work cross-repo; generateReleaseSummary no longer hardcodes a repo.
  • .github/workflows/ — reusable workflows: release-start, release-finalize, deploy-vercel, deploy-docker, e2e, release-self.
  • examples/ — per-scenario caller workflows. Plus .github/dependabot.yml and a _selftest smoke workflow.

Key decisions

  • Two version engines stay; the divergence is one pluggable action.
  • Credentials: each repo passes its own op:// paths; one scoped OP_SERVICE_ACCOUNT_TOKEN; no secrets: inherit; inputs validated; third-party actions SHA-pinned.
  • Distribution: semver + moving major (release-self); consumers pin SHAs + Dependabot.

Safety

Additive onlysteps/credential-retrieval (v0.4, in use by app / app-backend / gov-ui-kit) is untouched, so existing @v0.4 SHA pins keep working. Migrating consumers is downstream (SREDO tickets).

Validation

  • actionlint clean; all workflow/action YAML parses; node --check on all scripts.
  • _selftest.yml (manual dispatch) smoke-tests the leaf actions in-repo.

Reviewer notes

  • Self-references inside reusable workflows use aragon/github-templates/steps/*@main during bootstrap (relative ./steps/* would resolve against the caller repo). Pin these to the cut tag/SHA when releasing v0.5.
  • Final SHA-pinning of third-party actions + cutting v0.5 / v1 is for the DevOps owner (CODEOWNER).

Update 2026-08-03: monorepo support

This PR was written before aragon/app became a pnpm monorepo. The app repo has since grown improved copies of several modules (scope-based changeset versioning, @aragon/app@x.y.z tags, workspace-aware Vercel deploys). This update ports those improvements back and parameterizes every release/deploy module so both repo shapes work from the same files: single-package repos use the defaults, monorepos pass inputs (package-dir, tag-prefix, scope, release-branch-prefix, workspace). gov-ui-kit and aragon-domain can adopt the modules now and migrate to monorepos later by changing inputs only.

  • compute-version absorbs app's changeset-version: a scope input names an entry in the consumer's .github/release-scopes.yml and is inverted into changeset version --ignore flags against the live workspace list (new packages fail safe). Version and tag come from package-dir; new tag output.
  • generate-release-summary merges the app script: tag-glob boundary (v* or @aragon/app@*), optional path filtering via .github/filters.yml, --first-parent, merge-commit title resolution.
  • New steps: generate-version-summary (per-package PR body), changesets-guard (pending changesets at the release commit, scope-aware), gh-pr-get-body / gh-pr-edit-body.
  • New lib/ with a vendored strict flat-YAML parser, so the two mapper files parse without consumer node_modules. Unit tests run via node --test (17 tests) and a new ci.yml on every PR.
  • Bug fixes found while porting: release-start now stages the deleted .changeset/*.md (before, consumed changesets merged back to main and re-released next cycle); git identity is configured when no GPG key is passed; push --force became --force-with-lease; credential-retrieval metadata used workflow-only type:/options: keys, which composite actions reject (actionlint is clean again).
  • release-finalize: merge-sha renamed to sha; the caller picks what to tag (tested head SHA, recommended, or merge commit).
  • deploy-vercel unified with app's shared-deploy.yml: workspace, runtime env lifting, optional Sentry source maps, and the missing vercel pull step.
  • New example examples/release-changesets-monorepo.yml; the design doc gains seam 3 (package location & repo shape).

Correction to "Safety" above: steps/credential-retrieval IS modified (byref mode + this update's metadata fix), but @v0.4 SHA pins are immutable and unaffected.

Validation: node --test 17/17, actionlint clean, node --check on all scripts. Both shapes simulated locally: a scratch single-package repo end-to-end (changeset → version → the staging includes the changeset deletion) and the app checkout (scope inversion produces the same --ignore flags as app's current changeset-version, version read from apps/app).

@tyhonchik
tyhonchik requested a review from jjavieralv June 23, 2026 17:04
@tyhonchik
tyhonchik force-pushed the app-963-study-and-design-the-modules-structure-to-release branch from e4e9d5d to f2322ed Compare August 3, 2026 16:23
tyhonchik and others added 4 commits August 3, 2026 20:35
…flows)

Design-first deliverable for APP-963: standardise releases across repos around the
Build → Test → Release → Deploy spine, with loadable modules in github-templates.

- docs/release-design.md: scenarios A–D, the 4-step spine, module catalog, the
  explicit-paths credential model + security contract, distribution/versioning.
- steps/: composite actions (setup, compute-version[changesets|semantic-release],
  slack-notify, extract-slack-ts, read-changelog, build-release-notes,
  generate-release-summary, parse-playwright-results, gh-ensure-{pr,tag,release},
  git-ensure-branch). Scripts co-located + referenced via $GITHUB_ACTION_PATH so the
  actions are self-contained across repos; generateReleaseSummary genericised (no
  hardcoded repo).
- .github/workflows/: reusable workflows (release-start, release-finalize,
  deploy-vercel, deploy-docker, e2e, release-self). VERCEL_TOKEN resolved only inside
  deploy-vercel. op:// inputs validated; no secrets: inherit; third-party actions
  SHA-pinned.
- examples/: per-scenario caller workflows. .github/dependabot.yml + _selftest.yml.

Additive only: steps/credential-retrieval (v0.4, live) is untouched, so existing SHA
pins keep working. Consumer migrations are downstream (SREDO-695/697/698).
… and pnpm monorepos

Port the improvements the aragon/app monorepo migration produced back into the shared
modules, and parameterize every release/deploy module so the monorepo shape is an input
diff (package-dir / tag-prefix / scope / release-branch-prefix / workspace), never a fork:

- compute-version absorbs app's changeset-version: release scopes from the consumer's
  .github/release-scopes.yml inverted into 'changeset version --ignore' flags, version/tag
  read from package-dir, prettier over every touched CHANGELOG, new 'tag' output.
- generate-release-summary merges app's script: per-package tag boundary (tag-glob),
  path filtering via .github/filters.yml (path-filter), --first-parent log, merge-commit
  title resolution, release-commit dropping after resolution.
- New steps: generate-version-summary (per-package PR body), changesets-guard (pending
  changesets at the release commit, scope-aware), gh-pr-get-body / gh-pr-edit-body.
- New lib/ with vendored strict flat-YAML parser (mapper files parse without consumer
  node_modules), releaseScopes, changelog, output helpers — unit-tested (node --test),
  wired into a new ci.yml and the extended _selftest.
- release-start: stages the consumed .changeset/*.md deletions (double-release fix),
  git identity without GPG, --force-with-lease, per-lineage concurrency, on-active-release
  skip mode, summary-mode history|packages, ceremony steps continue-on-error.
- release-finalize: 'sha' replaces 'merge-sha' (caller picks tested-head vs merge-commit
  tagging), package-dir version/changelog resolution, optional changesets-guard,
  URL-encoded tag in the Slack release link.
- deploy-vercel unified with app's shared-deploy: workspace, env-overrides, runtime-env(-keys),
  sentry source maps, patch-root-directory, missing 'vercel pull' added.
- e2e: working-directory + install/test-command inputs, no direct input interpolation.
- credential-retrieval: drop workflow-only 'type:'/'options:' keys from action metadata
  (invalid in composite actions; actionlint now clean)
generate-release-summary now fetches each referenced Linear ticket's state and prepends a '⚠️ Open tickets' section for tickets that are not completed/canceled, with their current status (warn only, never a gate; no Linear token → no section). New reusable release-pr-refresh.yml regenerates the history summary on every release-PR push and rewrites the PR body, preserving the slack_ts marker; history mode only — packages summaries detect bumps via dirty files and cannot be re-derived after the release commit. Caller examples added to both changesets examples.
@tyhonchik
tyhonchik force-pushed the app-963-study-and-design-the-modules-structure-to-release branch from 6404125 to 5f1f2d6 Compare August 3, 2026 18:49
Optional inputs (all default off — the plain single-package flow needs none of them)
so app / app-backend / gov-ui-kit can migrate without duplicating behavior in their
own workflows:

- release-pr-refresh: edit the Slack head message in place (chat.update) on every
  push, matching the PR-body refresh; op-slack-* paths optional.
- release-start + release-pr-refresh: 'op-slack-codeowners-group-path' (<!subteam^…>
  ping in the head message), 'body-extras' (caller-computed markdown — e.g. a
  DB-migration warning — carried into the PR body and Slack message, preserved on
  refresh); the start head message now carries the generated summary like the
  consumers' originals.
- release-start: 'require-version-above-latest-tag' — the stale-lineage guard for
  linear-history/back-merge repos, checked right after compute-version.
- e2e: generics for wallet/extension suites ('env-secret-refs' + optional
  OP_SERVICE_ACCOUNT_TOKEN secret, 'pre-test-command' with EXTRA_CACHE_HIT,
  'use-xvfb', 'extra-cache-path'/'extra-cache-key-files'); bv artifacts get their
  own names so smoke+bv can share one run.
- deploy-docker: optional Slack gate/result ceremony (notify-gate runs as a sibling
  of deploy so it can never block it; 'gate-ts' output for rollback threading) and
  'op-env-vault' env-file materialization before the sync.
- release-finalize: per-lineage concurrency group (no same-tag races between two
  near-simultaneous merges).
- ci: fix wrong actions/checkout + actions/setup-node SHA pins (did not resolve).

Docs and examples updated accordingly.
@tyhonchik
tyhonchik force-pushed the app-963-study-and-design-the-modules-structure-to-release branch from 3564724 to 84ba37a Compare August 4, 2026 10:02
…ow-ups

read-changelog and generate-version-summary share lib/changelog.js, which only
recognised the changesets heading form ('## 1.2.0') and matched the requested
version as a prefix. Two defects followed:

- conventional-changelog (the semantic-release path) writes
  '## [0.33.0](…/compare/v0.32.0...v0.33.0) (2026-07-27)', and '# [2.0.0](…)' for
  a major. Neither matched, so extraction returned null and release-finalize
  published "No changes." as the GitHub Release body for every semantic-release
  repo. Verified against app-backend's real CHANGELOG.
- a prefix match let '## 1.0.10' answer a lookup for '1.0.1' (returning the wrong
  release's notes, with the stray '0' left where the heading was stripped), and
  '## 1.0.0-rc.1' answer one for '1.0.0' — prerelease versions compute-version
  already accepts.

Headings are now matched at a line start with the version optionally wrapped in a
link, bounded so it must end at the requested version, and the rest of the heading
line is stripped so a compare link and date don't lead the notes. Verified against
the real changelogs of app, app-backend and gov-ui-kit, plus the _selftest fixture.

Also notes the Node >= 22.5 floor that path.matchesGlob imposes on
generate-release-summary's path filtering.

Review follow-ups on the module drop:

- e2e.yml's 'extra-cache-key-files' documented comma-separated patterns, but the
  input is passed to hashFiles() as ONE argument. The runner joins hashFiles
  arguments with a newline and @actions/glob splits on newlines, so a comma-joined
  string is a single glob that matches nothing: hashFiles returns an empty string
  and the cache key becomes a constant that never invalidates. The input is now
  documented as newline-separated, and a guard fails the run when the patterns
  match no files instead of caching a stale asset forever. Consumers must not port
  app's two-argument hashFiles('a', 'b') key as "a,b".
- e2e.yml interpolated 'pre-test-command' / 'test-command' straight into `run:`,
  which §5.3 of the design forbids. Both now travel via env: and execute through
  `bash -euo pipefail -c`, which also makes an `&&` chain work under xvfb-run —
  the caveat the removed comment used to push onto callers.
- one bash helper (lib/gha.sh) replaces three spellings of "append a multiline
  value with a random heredoc delimiter" and three copies of the per-line
  ::add-mask:: loop. Composite actions source it via $GITHUB_ACTION_PATH; a
  reusable workflow cannot (it runs in the caller's checkout), so e2e.yml's single
  call site keeps the inline form with a comment saying why. ci.yml now bash -n's
  lib/**.sh alongside node --check.
- compute-version dropped the ${ARR[@]+"${ARR[@]}"} guard around an empty array:
  it is only needed below bash 4.4, and runners ship bash 5.
- git-ensure-branch is called by no workflow here, which read as dead code. It is
  a consumer-facing leaf for the flows that stay in the consumer repo (hotfix
  start) — labelled as such in the action and the README rather than dropped, so
  migrating repos don't have to keep a per-repo copy.
- trimmed comments that narrated the refactor ("now lives there too", "no longer
  needs", "replaces calling load-secrets-action") down to the constraint they
  were wrapped around.
@tyhonchik
tyhonchik force-pushed the app-963-study-and-design-the-modules-structure-to-release branch from 84ba37a to bde710b Compare August 4, 2026 11:19
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