Skip to content

Latest commit

 

History

History
208 lines (177 loc) · 11.3 KB

File metadata and controls

208 lines (177 loc) · 11.3 KB

Distribution — Homebrew, the release, and what signs what

Codegraph is installed from one Homebrew tap, defsquare/homebrew-tap — the tap that already carries Defsquare's other tools (datagraph, specy). The app is ONE install and every language extractor is its OWN install, so a user of one language never downloads the runtimes of the others (PLAN.md §15):

brew trust defsquare/tap                                # once: newer Homebrew refuses an untrusted tap
brew install --cask defsquare/tap/codegraph            # Codegraph.app + `codegraph` on PATH
brew install defsquare/tap/codegraph-java               # one per language, as needed
brew install defsquare/tap/codegraph-csharp
brew install defsquare/tap/codegraph-typescript
brew install defsquare/tap/codegraph-elixir

The app finds the extractors that are installed; a folder whose language has none is answered with the line above that fixes it.

What a tagged release produces

Pushing a v* tag runs .github/workflows/ci.yml end to end. The jobs that matter for distribution, and their outputs on the GitHub release:

Job Output on the release
java-native (five runners) codegraph-java-<rid> — the GraalVM native image
csharp-publish (five RIDs from Linux) codegraph-csharp-<rid> — the .NET single-file binary
elixir-native (ubuntu, macos) codegraph-elixir-linux-x64, codegraph-elixir-osx-arm64 — the Burrito binary
sea (five runners) codegraph-<rid> — the single-executable CLI + daemon + frontends
desktop-bundle (two macOS runners) Codegraph-<version>-osx-arm64.dmg, Codegraph-<version>-osx-x64.dmg
npm-publish codegraph-typescript@<version> on npm (not on the release)
release SHA256SUMS over every asset, then the tap rendered and pushed

<rid> is one of linux-x64, linux-arm64, osx-x64, osx-arm64, win-x64; Windows assets end in .exe.

The tap's files, generated

scripts/homebrew/render.mjs renders every file of the tap from the release's SHA256SUMS — never edit them by hand, the next release would overwrite the edit:

node scripts/homebrew/render.mjs --version 0.1.0 --sums release/SHA256SUMS \
     --npm-sha256 <sha256 of the npm tarball> --out tap
  • Casks/codegraph.rb: arch arm:/intel:, one DMG URL and sha256 per architecture, app "Codegraph.app" and ONE binary stanza — the single-executable inside the bundle. auto_updates false, a livecheck on the release tags; brew upgrade is the update path, the Tauri updater is off.
  • Formula/codegraph-java.rb, Formula/codegraph-csharp.rb, Formula/codegraph-elixir.rb: on_macos / on_linux × on_arm / on_intel blocks with the asset URL and sum — only the platforms that extractor's job builds (Elixir: Apple silicon and Linux x64 today; Homebrew refuses the others cleanly) — bin.install renaming the asset to the command, a test do that runs --version and extracts a one-file tree. A prebuilt binary in a formula is legitimate in our own tap (homebrew-core refuses vendored binaries).
  • Formula/codegraph-typescript.rb: the npm package on Homebrew's node (depends_on "node", std_npm_args), rendered only when the npm publish happened for this version.

node --test scripts/homebrew (part of test.sh) pins the asset names the templates reference to the ones the release job writes.

Secrets the workflow reads

All optional: without them the corresponding step is skipped with a notice, never failed, so a fork or a pull request still builds everything unsigned.

Secret Used by What it is
APPLE_CERTIFICATE desktop-bundle the Developer ID Application certificate, a .p12 base64-encoded
APPLE_CERTIFICATE_PASSWORD desktop-bundle its password
APPLE_SIGNING_IDENTITY desktop-bundle Developer ID Application: <name> (<team>) — its presence switches signing on
APPLE_ID, APPLE_PASSWORD, APPLE_TEAM_ID desktop-bundle the Apple ID, an app-specific password, the team — Tauri notarizes with them
HOMEBREW_TAP_DEPLOY_KEY release an SSH deploy key with write access to the tap repository (vars.HOMEBREW_TAP_REPO, default defsquare/homebrew-tap)

The npm publish of codegraph-typescript reads no secret: it uses npm's trusted publishing — npmjs.com trusts the OIDC identity of this repository's ci.yml and issues a short-lived token to that run, with provenance attached. Nothing to rotate.

Wiring npm trusted publishing (once, by a maintainer)

The trusted publisher is configured on the package's page, so the package must exist first: the first version is published by hand, from a checkout with npm login done (pnpm pack in extractors/typescript, then npm publish codegraph-typescript-<version>.tgz --access public). Then, on npmjs.com → the package → Settings → Trusted publishing → GitHub Actions:

Field Value
Organization or user defsquare
Repository codegraph
Workflow filename ci.yml
Environment name leave empty (the job uses none)

Then under Settings → Publishing access choose "Require two-factor authentication and disallow tokens": trusted publishers keep working, a leaked token cannot publish. The job itself needs permissions: id-token: write and npm ≥ 11.5.1 (it upgrades the npm Node 22 ships), both in place. Without a trusted publisher — a fork — the publish step fails and the run continues: the release job renders the tap without the TypeScript formula.

Wiring the tap deploy key (once, by a maintainer)

The release job pushes to the tap over SSH with a deploy key: its public half registered on the tap repository with write access, its private half in this repository's secrets. From a machine logged into gh with access to both:

ssh-keygen -t ed25519 -N "" -C "codegraph release -> defsquare/homebrew-tap" -f tap_deploy_key
gh api -X POST repos/defsquare/homebrew-tap/keys \
  -f title="codegraph release (CI deploy key)" -F key=@tap_deploy_key.pub -F read_only=false
gh secret set HOMEBREW_TAP_DEPLOY_KEY --repo defsquare/codegraph < tap_deploy_key
rm tap_deploy_key tap_deploy_key.pub

The tap's README says its formulas are generated and pushed by each tool's own release process; codegraph follows that rule — its four formulae and its cask are rendered by scripts/homebrew/render.mjs and pushed by the release job, never edited in the tap. Worth adding to the tap's README alongside datagraph and specy: the five install lines above, and that Casks/codegraph.rb and Formula/codegraph-*.rb come from defsquare/codegraph.

Getting the Apple secrets (once, by a maintainer with a Developer Program membership)

  1. The certificate. On a Mac: Keychain Access › Certificate Assistant › Request a Certificate From a Certificate Authority (saved to disk), then at developer.apple.com › Certificates add a Developer ID Application certificate from that CSR, download the .cer and open it so it joins its key. security find-identity -v -p codesigning prints the identity string, of the form Developer ID Application: <name> (<TEAMID>) — that exact string is APPLE_SIGNING_IDENTITY, and the code in parentheses is APPLE_TEAM_ID. Neither belongs in this repository: it is public, and both live only in its secrets.
  2. The .p12. In Keychain Access select the certificate with its private key, File › Export Items, format .p12, choose a password (APPLE_CERTIFICATE_PASSWORD). base64 -i DeveloperID.p12 | tr -d '\n' is APPLE_CERTIFICATE.
  3. Notarization credentials. APPLE_ID is the Apple ID email; APPLE_PASSWORD is an app-specific password from account.apple.com › Sign-In and Security (two-factor authentication must be on), never the account password.
  4. Store them: gh secret set <NAME> --repo defsquare/codegraph for each (with no value it prompts and reads hidden), then delete the local .p12 and its base64 file.
  5. Prove it locally before tagging (optional): export the six variables, ./build.sh --ts --sea (→ packages/cli/dist-sea/<rid>/codegraph), place it as the sidecar (node apps/desktop/scripts/sidecar.mjs copies it to apps/desktop/src-tauri/binaries/codegraph-<triple>), sign that copy as the desktop-bundle job does (codesign --force --options runtime --timestamp --entitlements apps/desktop/src-tauri/entitlements.plist --sign "$APPLE_SIGNING_IDENTITY" apps/desktop/src-tauri/binaries/codegraph-<triple>), then pnpm --filter @codegraph/desktop exec tauri build --bundles app,dmg (the .app is requested too, or the bundler deletes it once the DMG holds it; both land under apps/desktop/target/<triple>/release/bundle/) — NOT tauri:build, which re-runs sidecar.mjs and overwrites the signed copy with the unsigned image. Tauri submits the app for notarization and waits — and check the result as a user's Mac will: codesign --verify --deep --strict, xcrun stapler validate, spctl --assess --type execute on the .app. A rejection's reason is in xcrun notarytool log <submission-id> --apple-id … --password … --team-id ….

Tauri notarizes and staples the .app; the DMG is the container around it, and Gatekeeper checks the app's staple when the cask copies it into /Applications — the shape Homebrew casks expect.

What is signed, and how

  • The sidecar (codegraph, a Node single-executable) is signed by CI itself, before tauri build, with hardened runtime and apps/desktop/src-tauri/entitlements.plist — the JIT trio V8 needs. The plan (§15.5) says not to assume the bundler applies entitlements to a sidecar, so the job does it and verifies the signature.
  • tauri build signs Codegraph.app with the same identity and hardened runtime, then notarizes when the Apple ID variables are present.
  • The job then asserts what §15.5 asks to confirm on the first signed build: codesign --verify --deep --strict on the app, the allow-jit entitlement present on the embedded codegraph, and spctl --assess accepting the app.
  • The extractor binaries are formula downloads, not bundle contents; Homebrew fetches them with curl, which sets no quarantine attribute. arm64 still refuses an unsigned Mach-O, which the GraalVM image and the .NET single-file host satisfy with an ad-hoc signature. Whether a Developer ID signature on them is also needed is decided by the first clean-Mac install, and the answer belongs in the extractor's profile notes.

Status

v0.1.0 (2026-09-22) was the first tagged release: the GitHub release holds every asset of the table above, and the tap received Casks/codegraph.rb and the Java, C# and Elixir formulae from the release workflow. The codegraph-typescript formula was absent from that first push: the npm publish was still token-gated and no token existed, so there was no tarball sum to render. The first version was then published by hand and the job switched to trusted publishing (above); the next tag renders the formula. What remains of the definition of done in PLAN.md §15.6 is the clean-Mac check by hand: the install lines, then a Java, a C# and a TypeScript folder opened from the app.