Skip to content

Repo standardization + Shipwright NativeAOT release pipeline#17

Merged
MelbourneDeveloper merged 48 commits into
mainfrom
repo-standardization
Jun 3, 2026
Merged

Repo standardization + Shipwright NativeAOT release pipeline#17
MelbourneDeveloper merged 48 commits into
mainfrom
repo-standardization

Conversation

@MelbourneDeveloper
Copy link
Copy Markdown
Collaborator

TLDR

Repo standardization plus a Shipwright-compliant release pipeline that ships napper as a self-contained NativeAOT native binary (zero .NET runtime for end users) via GitHub Releases / Homebrew / Scoop and bundled inside a per-platform VSIX for all 6 targets, with build-time version stamping and a network-free release gate.

Details

Deployment / release (Shipwright)

  • release.yml rebuilt: tag-triggered → CI gate (lint + build + manifest validate + version-contract tests on 0.0.0-dev source) → per-platform NativeAOT build on native runners (macos-15, macos-15-intel, ubuntu-latest, ubuntu-24.04-arm, windows-latest, windows-11-arm) → per-platform VSIX (vsce package --target) with content verification → package-cli (archives + per-archive .sha256 + combined checksums) → GitHub Release + Marketplace + Homebrew + Scoop. Downstream jobs use if: !cancelled() && needs.build.result != 'skipped' so one flaky leg can't block shipping the rest.
  • Removed the dotnet publish --self-contained path; the shipped artifact is the same NativeAOT binary the Makefile builds. A clean-room step proves the binary runs with no .NET runtime present (pristine ubuntu:24.04 container / empty env).
  • Source versions set to 0.0.0-dev; new first-class structured-parser stamper scripts/stamp-version.fsx (XDocument + System.Text.Json) rewrites Directory.Build.props, the extension package.json, and shipwright.json (product.version + every expectedVersion) from the tag. make stamp target added.
  • shipwright.json: expectedVersion0.0.0-dev; sources cascade fixed to user-setting → env → bundled (dropped forbidden path / dotnet-tool startup sources + the dotnetTool block) per [SWR-IDE-RESOLUTION].
  • dotnet tool NuGet kept as a secondary, non-blocking channel (publish-nuget, continue-on-error, not a dependency of any release job).
  • Vendored schemas/shipwright.schema.json; CI + release validate the manifest with @nimblesite/shipwright-validate-manifest --schema.
  • Extension bug fix: DEFAULT_CLI_PATH now '' to match the napper.cliPath default so getCliPath() falls through to the Shipwright-resolved bundled binary instead of returning an empty path.

LSP / core / website: LSP protocol layer rebuilt (Protocol.fs, Server.fs, wire/driver test harnesses), Types.td typeDiagram migration, website scripting docs.

How Do The Automated Tests Prove It Works?

  • VersionContractTests.fs (4 black-box tests, all pass): napper --version → exactly napper 0.0.0-dev + exit 0; --version --json conforms to the version-manifest schema; the stamper rewrites every carrier from a tag; dry-run is a no-op and bad input exits non-zero.
  • shipwrightManifest.test.ts (4) + cliConfig.test.ts (DEFAULT_CLI_PATH matches napper.cliPath default) pass — 135 TS unit tests green.
  • make package-vsix verified end-to-end locally: the napper-darwin-arm64 VSIX contains the real 9.5 MB Mach-O arm64 NativeAOT binary at extension/bin/darwin-arm64/napper + the stamped shipwright.json (bin/darwin-arm64/napper: OK).
  • Manifest validates against the vendored schema (source and post-stamp). F# build is -warnaserror clean; Fantomas / ESLint / cargo-fmt clean. actionlint clean (runner labels valid).

…uild

The v0.12.0 release failed at the CI gate with FS0225: Types.Generated.fs is
gitignored and rebuilt from Types.td, but the release gate + AOT build legs never
regenerated it. Add a cross-platform scripts/generate-types.sh and a typeDiagram
generate step to the gate, all 6 build legs, and publish-nuget so the F# build/
publish has the generated source on a fresh checkout.
…packaging

Two release blockers found shipping v0.12.0:

1. NativeAOT binary fail-fasts (exit 139) on any host without ICU/libicu —
   e.g. a pristine ubuntu:24.04 — because the first log line formats a date via
   CurrentCulture. A binary that needs libicu pre-installed is not the
   zero-dependency native binary the deployment contract requires. Compile with
   InvariantGlobalization=true (correct + deterministic for an HTTP/protocol
   tool). Proven: linux-x64 AOT now runs --version + --version --json in bare
   ubuntu:24.04 with zero .NET runtime and zero ICU.

2. The win32-arm64 leg failed packaging its VSIX: @vscode/vsce-sign ships no
   win32-arm build, so `npm ci` aborts on a Windows-ARM runner. vsce packaging
   only zips the staged native binary + manifest (it never executes it), so move
   all per-platform VSIX packaging off the native runners into a single Linux
   matrix that consumes the uploaded raw binaries. Also kills the Windows EPERM
   npm flakiness. Each leg stamps the version, stages only its own platform, and
   verifies VSIX contents. Marketplace publish now fails fast with an actionable
   message if the VSCE_PAT secret is absent (instead of an opaque TF400813).
The extension's binary resolver (bundledBinaryPath / Shipwright) looks for the
bundled native CLI at bin/<process.platform>-<process.arch>/napper — the same
layout the shipped per-platform VSIX uses. But scripts/build-cli.sh staged it at
a FLAT bin/napper, so in local dev and the e2e test run the extension (and the
LSP it spawns) could not find the bundled binary. That surfaced as 6 failing
e2e tests: 3 OpenAPI "generate" tests calling execFile('') (the test's own
resolver returned the now-empty DEFAULT_CLI_PATH) and 3 "Copy as Curl" tests
whose LSP-backed command had no binary to spawn.

- build-cli.sh: stage the binary under bin/<node-platform>/napper (mapping the
  .NET RID to the node platform-arch string, incl. linux-arm64), matching the
  resolver and the real VSIX. Keep a flat bin/napper copy so the CI Shipwright
  version-contract gate (which adds bin/ to PATH) still resolves `napper`.
- openApiImport.e2e.test.ts: resolve the CLI via the real bundledBinaryPath
  instead of the empty DEFAULT_CLI_PATH, so the test exercises the deployed
  resolution path ([SWR-IDE-RESOLUTION]) rather than a removed PATH fallback.

Proven locally: e2e goes 91→97 passing. The only remaining failure is
"downloadSpec follows redirects", which depends on httpbin.org (currently 503) —
an external outage, not a regression.
@MelbourneDeveloper MelbourneDeveloper merged commit 1a8642c into main Jun 3, 2026
4 of 5 checks passed
@MelbourneDeveloper MelbourneDeveloper deleted the repo-standardization branch June 3, 2026 12:18
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