Skip to content

Make the release pipeline actually publish, ship the real icon, and consume dmx from pub.dev - #1

Merged
MelbourneDeveloper merged 11 commits into
mainfrom
release-0.2.0
Aug 15, 2026
Merged

MelbourneDeveloper merged 11 commits into
mainfrom
release-0.2.0

Conversation

@MelbourneDeveloper

Copy link
Copy Markdown
Collaborator

TLDR

Everything that had to be fixed to make a tag actually publish — six defects in a release pipeline that had never once executed — plus the real dmx icon, the install CTAs, and the examples now consuming dmx from pub.dev instead of the copy beside them.

Details

The release pipeline could not start, let alone publish. No tag had ever existed in this repository, so every step below failed the first time it ran:

  • verify calls ci.yml granting contents: read, but ci.yml's security job declares pull-requests: write. A called workflow may not exceed its caller's grant and GitHub checks that statically, before any job exists — so every tag died as startup_failure with zero jobs. The if: that would have skipped security never got a say.
  • vsix-universal packages into src/dmx/target/, but unlike vsix it carries no binary, builds nothing, and so never creates that directory. vsce opens the --out path rather than creating it: ENOENT, after the bundle was already assembled.
  • The alpine-x64 binary segfaulted on its own --version (exit 139). Rust ships a musl libc and links that target self-contained; overriding CARGO_TARGET_..._LINKER with Ubuntu's musl-gcc produced a broken x86_64 static-PIE. CC_<triple> is still set — tree-sitter is C — but only a genuine cross-link (mingw) now names a linker.
  • The Marketplace publish minted an access token and exported it as VSCE_PAT. vsce 3.x accepts --azure-credential and consumes the Entra session azure/login already established from the job's OIDC token, so nothing token-shaped is handled at all. Microsoft retires Marketplace PATs on 1 December 2026.
  • pub.dev's --dry-run ran before stamping, on the reasoning that publishability does not depend on the version string. It does: pub validates the changelog against the current version, so the 0.0.0 placeholder always produced CHANGELOG.md doesn't mention current version, and once 0.1.0 existed, also your version is earlier than that. --dry-run exits non-zero on any warning, so the step could never have passed. It now runs after the stamp, with the stamp committed into the throwaway checkout so the uncommitted-changes warning cannot fire — which also means it validates the archive actually being published rather than a differently-versioned one.
  • Preflight now refuses a release whose publishing credentials are absent, and runs in the release environment so it can actually read them. Environment secrets are visible only to a job declaring that environment; the guard's first version read them from a job that had none and refused a release whose secrets were set correctly.

Intel macOS is no longer a deployment target. The darwin-x64 leg and its bundle are gone, and the docs no longer claim a macOS x64 build.

The extension shipped an icon matching nothing else the project uses. Rebuilt from website/src/assets/images/dmx-mark.svg at 256×256 on the brand ground, in the proportions apple-touch-icon.png already establishes.

The Marketplace URL was written with the publisher lowercase in six places. The gallery API accepts that form; the web page 404s on it. It is Nimblesite.dmx.

The site and docs now offer the install itselfvscode:extension/Nimblesite.dmx opens the extension in the editor — alongside the Marketplace page, since the vscode: link is inert for a reader without VS Code. No new CSS: both CTAs reuse the existing button classes and the budget stays at 1983 of 2000 lines.

The examples, golden corpus root, and the corpus pubspec the Makefile generates all depended on src/dart_packages/dmx by path, so nothing here ever exercised the package as published. They now resolve dmx ^0.3.0 from pub.dev — what a reader copying an example actually gets.

examples/storefront/.dart_tool was tracked despite .dart_tool/ being gitignored, carrying 48 absolute paths under a developer's home directory into a public repository. pub regenerates it, and the switch to a hosted dependency would have written a pub-cache path into every future diff.

How Do The Automated Tests Prove It Works?

The pipeline itself is the test, and it has now published twice from these commits:

  • v0.2.0 and v0.3.0 both completed with every job greenPreflight, all seven Package legs, Package universal, Verify / CI, Security gate across actions/rust/javascript-typescript, Publish, and Publish to pub.dev.
  • Package alpine-x64 executes the binary it just built and asserts dmx <version>; that step is what caught the SIGSEGV and is what proves the musl fix. It passed on two consecutive tags.
  • Publish to pub.dev succeeded twice with the reordered stamp-then-prove path, so that fix is reproduced rather than lucky.
  • Published artefacts verified independently of the run: pub.dev serves dmx 0.3.0; the Marketplace gallery API returns Nimblesite.dmx at 0.3.0 across universal, win32-x64, alpine-x64, alpine-arm64, linux-x64, linux-arm64, darwin-arm64 — and no darwin-x64; the icon asset resolves under dmx/0.3.0/, so it is the new mark being served, not a cached one.
  • For the dependency switch: make corpus regenerates all 11 golden samples against pub.dev dmx 0.3.0 and dart analyze --fatal-infos reports No issues found; make example runs the storefront suite — 179 tests, all passing — against the published package.
  • For the website change: make website builds and all 24 Playwright e2e specs pass, including navigation.spec.ts assertions on the shared prose classes and the mobile navigation.
  • The fix for the pub.dev ordering was reproduced in a clean clone of the tag before being written: pre-stamp gives 1 warning + 1 hint (exit 65), post-stamp-uncommitted gives 2 warnings, stamp-then-commit gives Package has 0 warnings.

For AI

main was truncated to a single parentless root commit and the repository was deleted and recreated to purge refs/pull/*, which is read-only and cannot be removed any other way. Two consequences worth carrying forward:

  1. GitHub derives an ID-bearing OIDC subject prefix for newly created repositories. dmx presents repo:Nimblesite@178527684/dmx@1334980307:environment:release; older repos present repo:Nimblesite/Deslop. Both report use_default: true / use_immutable_subject: false, so this is not a toggle — the prefix is assigned. The Entra app's flexible credential matches repo:Nimblesite/*:environment:release and cannot match the ID form; Entra also rejects @ in the org segment of a claimsMatchingExpression for the GitHub issuer, so an exact-subject federated credential (github-dmx-release) was added. Any Nimblesite repo recreated from here will hit this same wall.
  2. AZURE_CLIENT_ID / AZURE_TENANT_ID live as environment secrets on release, not repo or org secrets. They are the app's public appId and the tenant GUID — identifiers, not credentials — readable from az.

The ## Unreleased heading is renamed by the release into its own checkout and never committed back, so shipped notes must be promoted to a real heading by hand or the next tag republishes them. This PR promotes 0.2.0's and opens a fresh ## Unreleased carrying 0.3.0's.

dbb34e5 "config" is an unrelated editor-settings commit swept in by a local auto-commit hook, not part of this work.

A called workflow may not request more than its caller grants, and GitHub
enforces it statically: ci.yml's `security` job declares `pull-requests:
write`, the `verify` job granted only `contents: read`, and every tag push
died as `startup_failure` before a job existed. No tag had ever been pushed,
so nothing caught it.
`vsce` opens the --out path but does not create its directory. `vsix` gets
that directory from `build`; `vsix-universal` carries no binary, builds
nothing, and so died with ENOENT on every fresh checkout — which is what a
release runner always is. Reproduced by packaging into a missing directory.
Intel macs are no longer a deployment target: the darwin-x64 leg and its
bundle are gone.

The alpine-x64 leg built a binary that segfaulted (exit 139) on its own
--version. Rust ships a musl libc and links that target self-contained;
overriding CARGO_TARGET_..._LINKER with Ubuntu's musl-gcc produced a broken
x86_64 static-PIE. CC_<triple> is still set — tree-sitter is C — but only a
real cross-link (mingw) now names a linker.
publish and publish-pub are held to the same gates but run in parallel, so a
missing Entra credential surfaces only after pub.dev has already published a
version that cannot be withdrawn. Checked in preflight before the checkout
instead, where being wrong costs a red tag. Presence only — no value is ever
printed or compared.
…d token

vsce 3.x takes --azure-credential and uses the session azure/login already
established from this job's OIDC token, so the release no longer mints an
access token, masks it, and exports it as VSCE_PAT. Nothing token-shaped is
handled by the workflow at all.
Environment secrets are readable only by a job that declares the environment.
The credentials guard sat in preflight, which declared none, so it read both
as empty and refused a release whose secrets were set correctly.
The dry run ran on the unstamped tree, on the reasoning that publishability
does not depend on the version string. It does: pub validates the changelog
against the current version, so the 0.0.0 placeholder produced 'CHANGELOG.md
doesn't mention current version' and, once 0.1.0 existed, 'your version is
earlier than that'. --dry-run exits non-zero on any warning, so the step could
never once have passed.

Stamped first now, committed into the throwaway checkout so the uncommitted-
changes warning cannot fire, then proven — which validates the archive
actually being published rather than a differently-versioned one. Reproduced
in a clean clone of the tag: 0 warnings.
The extension carried an older mark matching nothing else the project uses.
Rebuilt from website/src/assets/images/dmx-mark.svg at 256x256 on the brand
ground, in the proportions the apple-touch-icon already establishes.

The Marketplace URL was written with the publisher in lowercase in six places.
The gallery API accepts that form; the web page 404s on it. It is Nimblesite.dmx.

The site and docs now offer the install itself — vscode:extension/Nimblesite.dmx
opens the extension in the editor — alongside the Marketplace page, since the
vscode: link is inert for a reader without VS Code. No new CSS: both CTAs reuse
the existing button classes, and the budget stays at 1983 of 2000 lines.

The docs claimed a macOS x64 bundle that 0.2.0 stopped publishing.
The examples, the golden corpus root and the corpus pubspec the Makefile
generates all depended on src/dart_packages/dmx by path, so nothing in this
repository ever exercised the package as published. They now resolve dmx ^0.3.0
from pub.dev, which is what a reader copying an example gets.

examples/storefront/.dart_tool was tracked despite .dart_tool/ being ignored,
and carried 48 absolute paths into a public repository. pub regenerates it, and
switching to a hosted dependency would have written a pub-cache path under a
developer's home directory into every future diff.
@MelbourneDeveloper
MelbourneDeveloper enabled auto-merge (squash) August 15, 2026 21:56
@MelbourneDeveloper
MelbourneDeveloper merged commit cae740e into main Aug 15, 2026
7 checks passed
@MelbourneDeveloper
MelbourneDeveloper deleted the release-0.2.0 branch August 15, 2026 22:04
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.

1 participant