add local script to sign and notarize imessage-cli#70
Conversation
First step toward AINFRA-2351 (sign and notarize `imessage-cli` in CI).
Validates entitlements, signing identity, and notarytool credentials end
to end on a workstation before wiring the same flow into
`release-imessage-cli.yml`.
Entitlements are minimal — just `com.apple.security.automation.apple-events`,
required so hardened runtime lets the CLI drive Messages.app. TCC governs
Accessibility / Contacts / Full Disk Access, so no entitlements there.
The script reads App Store Connect API key creds from team-keyed env vars
(`APP_STORE_CONNECT_API_KEY_<TEAM>_{KEY_ID,ISSUER_ID,KEY}`), defaulting
to team `PZYM8XX95Q`.
Gotchas worth remembering when porting to CI:
- Env-var PEM stores `\n` as literal backslash-n; need `printf '%b'` to
decode, not `printf '%s'`.
- notarytool's PEM parser rejects `-----END PRIVATE KEY-----` without a
trailing newline as `invalidPEMDocument`. OpenSSL accepts it. Always
emit a final `\n`.
- `eval printf '%s' "$VAR"` word-splits on whitespace and `printf`
concatenates without separators — `BEGIN PRIVATE KEY` becomes
`BEGINPRIVATEKEY`. Use bash indirect expansion `${!var}` instead.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`strip` invalidates an existing signature, so it has to happen *before* `codesign`, not after. Halves the release artifact (10.6MB -> 5.3MB on arm64) — matches the intent of the bare `strip` step in `release-imessage-cli.yml`, which currently runs unsigned. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the universal/x86 half of the open todo on `todos.md:28`. The script now builds both arm64 and x86_64 slices, lipos them into one fat binary, and signs/notarizes the result with a single signature that covers both arches. Adds `--arch arm64|x86_64|universal` and `--team-id TEAM` flags; defaults are universal + `PZYM8XX95Q`. Single-arch is still available for shaking out signing/credential issues without paying the second build (~3min on a clean cache). Wall-clock on a clean cache ended up ~6:51 (arm64 218s + x86_64 173s + sign/zip/notary ~30-90s). Incremental relink is sub-10s for both slices combined. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📜 Recent review details🔇 Additional comments (4)
📝 WalkthroughSummary by CodeRabbitRelease Notes
WalkthroughAdds an entitlements plist enabling Apple Events automation and a new signing/notarization shell script that builds imessage-cli (single-arch or universal), codesigns it with entitlements, and submits the package to Apple Notary using App Store Connect API credentials, handling logs and exit status. ChangesSigning & Notarization Flow
Sequence DiagramsequenceDiagram
autonumber
participant Dev as Developer
participant Repo as Repository (scripts)
participant Build as Swift Build System
participant Keychain as Codesign / Keychain
participant Notary as Apple Notary (notarytool)
Dev->>Repo: invoke `scripts/sign-and-notarize-cli --arch --team-id`
Repo->>Build: run `swift build` per-arch (arm64/x86_64) or single-arch
Build-->>Repo: produced binary(ies)
Repo->>Keychain: resolve identity, strip symbols, codesign binary with entitlements
Keychain-->>Repo: verified signed binary
Repo->>Repo: write PEM from env, zip signed binary
Repo->>Notary: submit ZIP via `notarytool` (key-id, issuer, PEM)
Notary-->>Repo: return submission JSON (status, id)
alt status != Accepted
Repo->>Notary: fetch notarization log
Notary-->>Repo: log -> Repo exits non-zero
else status == Accepted
Repo-->>Dev: report notarization id and signed binary path
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/sign-and-notarize-cli`:
- Around line 10-16: Guard the --arch and --team-id case blocks against missing
values by checking the argument count before reading $2; in the case labels for
--arch and --team-id (the branches that set arch="$2" and team_id="$2") add an
if [ $# -lt 2 ] check that prints the script usage/help and exits non‑zero,
otherwise proceed to assign and shift 2, so invoking the script with those flags
but no value does not access an unbound $2 when set -u is enabled.
- Around line 14-16: The --team-id flag currently only sets team_id but codesign
still uses a hardcoded certificate subject, so non-Automattic teams fail; update
the script so the supplied value either (a) accepts a full signing identity
string and assigns it to the SIGNING_IDENTITY variable used by the codesign
invocation, or (b) uses the provided team_id to look up the correct identity via
the macOS keychain (security find-identity / security find-certificate) and set
SIGNING_IDENTITY before the codesign call; ensure the help text matches the
chosen behavior and replace the hardcoded certificate subject in the codesign
invocation with the SIGNING_IDENTITY variable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 12a29851-6ecb-4d34-a44a-2e55558aef67
📒 Files selected for processing (2)
scripts/imessage-cli.entitlementsscripts/sign-and-notarize-cli
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (1)
scripts/imessage-cli.entitlements (1)
5-6: Scoped entitlement looks right.Keeping this to just the Apple Events entitlement is the right shape for a hardened-runtime CLI that needs Messages automation.
Without the guard, `set -u` makes a bare `--arch` or `--team-id` blow up on `$2` with a cryptic "unbound variable" instead of printing usage. CodeRabbit on PR #70. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hardcoded `Developer ID Application: Automattic, Inc. (...)` only worked for Automattic certs, so `--team-id` against a non-Automattic team would silently fall back to a misleading codesign error. Look up the full identity string from `security find-identity` instead, matching by team id, with `IDENTITY` env override for edge cases. CodeRabbit on PR #70. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed both CodeRabbit comments.
Re-ran the script end-to-end with Posted by Claude Code (Opus 4.7, 1M context) on behalf of @mokagio with approval. |
|
It looks like you're not a member of this organization on Indent. To use Indent, visit app.indent.com to get started. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
scripts/sign-and-notarize-cli (1)
158-159: 💤 Low valueConsider consolidating JSON parsing into a single python3 call.
Minor efficiency improvement: you could extract both values in one invocation to avoid parsing the JSON twice.
♻️ Optional: single-pass JSON extraction
-status="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["status"])' "$submit_json")" -submission_id="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["id"])' "$submit_json")" +read -r status submission_id < <(python3 -c ' +import json, sys +d = json.load(open(sys.argv[1])) +print(d["status"], d["id"]) +' "$submit_json")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/sign-and-notarize-cli` around lines 158 - 159, Replace the two separate python3 calls that set status and submission_id by a single python3 invocation that parses "$submit_json" once and prints both values (e.g., space- or newline-separated), then capture those two outputs into the shell variables status and submission_id with a single read; update the lines that reference status and submission_id accordingly so they now come from that single parse operation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@scripts/sign-and-notarize-cli`:
- Around line 158-159: Replace the two separate python3 calls that set status
and submission_id by a single python3 invocation that parses "$submit_json" once
and prints both values (e.g., space- or newline-separated), then capture those
two outputs into the shell variables status and submission_id with a single
read; update the lines that reference status and submission_id accordingly so
they now come from that single parse operation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 064485a9-033b-4f4e-b9e6-a4da2cdb002b
📒 Files selected for processing (1)
scripts/sign-and-notarize-cli
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (7)
scripts/sign-and-notarize-cli (7)
1-11: LGTM!Good use of
set -euo pipefailfor strict error handling. Defaults and usage function are clean.
12-42: Argument parsing guards are now in place.The
$# -lt 2checks before accessing$2prevent unbound variable errors underset -u. This addresses the previous review feedback.
44-63: LGTM!Architecture validation is correct, and the indirect variable expansion pattern (
${!var_name-}) safely retrieves team-keyed environment variables while remaining compatible withset -u.
68-86: Identity resolution from keychain is now implemented.The script correctly resolves the codesigning identity by team ID using
security find-identity, with anIDENTITYenv var escape hatch. This addresses the previous review feedback about the hardcoded certificate subject.
88-130: Build and signing flow is well-structured.The ordering of strip → codesign is correct and documented. The codesign flags (
--options runtime,--timestamp,--entitlements) are appropriate for hardened runtime signing required by notarization.
132-144: LGTM!Good security hygiene: temp directory with cleanup trap,
chmod 600on the PEM key file, and clear documentation of the\ndecoding requirement.
161-169: LGTM!Good error handling: fetching the notarization log on failure provides actionable debugging information.
Reads `APP_STORE_CONNECT_API_KEY_{KEY_ID,ISSUER_ID,KEY}` first (the
canonical Fastlane convention, and what the Buildkite secrets surface
to the agent), then falls back to the existing team-id-keyed names
(`APP_STORE_CONNECT_API_KEY_<TEAM>_{...}`) so local shells holding
creds for multiple teams in parallel keep working.
The error message now lists both forms so future-me doesn't waste
time wondering which env var the script wants.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
That fallback existed because my local shell holds creds for multiple
teams under `APP_STORE_CONNECT_API_KEY_<TEAM>_{...}` names, but it's
a quirk of my setup, not something the script should carry weight for.
CI uses the canonical `APP_STORE_CONNECT_API_KEY_{KEY_ID,ISSUER_ID,KEY}`
names directly. If a multi-team workflow becomes a real need later,
we can reintroduce it then.
`--team-id` is still used for the keychain identity lookup.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a local, end-to-end macOS signing + notarization workflow for the imessage-cli Swift executable, intended to be exercised on a developer workstation before wiring the same flow into CI/release automation.
Changes:
- Introduces a
scripts/sign-and-notarize-cliBash script to build (optionally universal), strip, Developer ID sign (hardened runtime), and notarizeimessage-clivianotarytool. - Adds an entitlements plist enabling Apple Events automation for the signed CLI binary.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| scripts/sign-and-notarize-cli | New local build/sign/notarize script with arch selection and App Store Connect API key handling. |
| scripts/imessage-cli.entitlements | New entitlements plist (Apple Events automation) used during codesigning. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
* add local sign+notarize script for cli
First step toward AINFRA-2351 (sign and notarize `imessage-cli` in CI).
Validates entitlements, signing identity, and notarytool credentials end
to end on a workstation before wiring the same flow into
`release-imessage-cli.yml`.
Entitlements are minimal — just `com.apple.security.automation.apple-events`,
required so hardened runtime lets the CLI drive Messages.app. TCC governs
Accessibility / Contacts / Full Disk Access, so no entitlements there.
The script reads App Store Connect API key creds from team-keyed env vars
(`APP_STORE_CONNECT_API_KEY_<TEAM>_{KEY_ID,ISSUER_ID,KEY}`), defaulting
to team `PZYM8XX95Q`.
Gotchas worth remembering when porting to CI:
- Env-var PEM stores `\n` as literal backslash-n; need `printf '%b'` to
decode, not `printf '%s'`.
- notarytool's PEM parser rejects `-----END PRIVATE KEY-----` without a
trailing newline as `invalidPEMDocument`. OpenSSL accepts it. Always
emit a final `\n`.
- `eval printf '%s' "$VAR"` word-splits on whitespace and `printf`
concatenates without separators — `BEGIN PRIVATE KEY` becomes
`BEGINPRIVATEKEY`. Use bash indirect expansion `${!var}` instead.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* strip cli before codesign
`strip` invalidates an existing signature, so it has to happen *before*
`codesign`, not after. Halves the release artifact (10.6MB -> 5.3MB on
arm64) — matches the intent of the bare `strip` step in
`release-imessage-cli.yml`, which currently runs unsigned.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* default sign+notarize to universal binary
Closes the universal/x86 half of the open todo on `todos.md:28`. The
script now builds both arm64 and x86_64 slices, lipos them into one
fat binary, and signs/notarizes the result with a single signature
that covers both arches.
Adds `--arch arm64|x86_64|universal` and `--team-id TEAM` flags;
defaults are universal + `PZYM8XX95Q`. Single-arch is still available
for shaking out signing/credential issues without paying the second
build (~3min on a clean cache).
Wall-clock on a clean cache ended up ~6:51 (arm64 218s + x86_64 173s
+ sign/zip/notary ~30-90s). Incremental relink is sub-10s for both
slices combined.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* guard --arch and --team-id against missing values
Without the guard, `set -u` makes a bare `--arch` or `--team-id` blow
up on `$2` with a cryptic "unbound variable" instead of printing
usage. CodeRabbit on PR #70.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* resolve codesign identity from keychain by team id
The hardcoded `Developer ID Application: Automattic, Inc. (...)` only
worked for Automattic certs, so `--team-id` against a non-Automattic
team would silently fall back to a misleading codesign error. Look
up the full identity string from `security find-identity` instead,
matching by team id, with `IDENTITY` env override for edge cases.
CodeRabbit on PR #70.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* prefer canonical asc api key env vars
Reads `APP_STORE_CONNECT_API_KEY_{KEY_ID,ISSUER_ID,KEY}` first (the
canonical Fastlane convention, and what the Buildkite secrets surface
to the agent), then falls back to the existing team-id-keyed names
(`APP_STORE_CONNECT_API_KEY_<TEAM>_{...}`) so local shells holding
creds for multiple teams in parallel keep working.
The error message now lists both forms so future-me doesn't waste
time wondering which env var the script wants.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* add buildkite pipeline for cli release
Wires the `platform-imessage` Buildkite pipeline (provisioned via
[Automattic/buildkite-ci#848](Automattic/buildkite-ci#848))
to actually do something, so the macOS CLI release can move off
GitHub Actions and onto Automattic Buildkite — keeping the Developer
ID p12 and ASC API key alongside the rest of the org's Apple
distribution material in `a8c-secrets` rather than as GitHub-side
secrets.
Trigger model:
- PR / `main` push → compile-check only (`swift build -c release`).
- `v*` tag push → `.buildkite/commands/release-cli.sh` runs the local
`scripts/sign-and-notarize-cli` (universal binary, hardened runtime,
Apple-notarized) and publishes the tarball + sha256 to a GitHub
Release via `gh`.
The release script is idempotent: if the release already exists for
the tag it just uploads with `--clobber`, so re-runs from a Buildkite
retry don't error.
`shared-pipeline-vars` mirrors the convention from
`pocket-casts-desktop` / `Automattic-Tracks-iOS`: pin the
`a8c-ci-toolkit` plugin and read the Xcode image id from
`.xcode-version`.
Follow-up: once a tag build proves green end-to-end on Buildkite,
remove `.github/workflows/release-imessage-cli.yml` so we don't have
two workflows publishing different assets to the same release.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Use Xcode 26.4.1 in Buildkite CI
* move artifact upload to pipeline.yml
`artifact_paths` runs at end-of-step regardless of step exit code, so
an explicit `buildkite-agent artifact upload` in the script only fires
on success — exactly when we need the artifact least. Switching to the
pipeline DSL means we still get the tarball + sha256 stashed even when
codesign, notarize, or `gh release` fails partway through.
The script now writes to `dist/` (already gitignored) instead of a
mktemp dir, so the glob in `artifact_paths` has a stable target.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* always sign+notarize; publish only on tag
Collapse the two pipeline steps into one that runs on every CI build
(PR, `main`, tag) and produces a signed+notarized universal-binary
tarball, uploaded as a Buildkite artifact via `artifact_paths`. Non-tag
builds skip the `gh release` step; tag builds do the full publish.
Lets reviewers grab a fully usable CLI from any PR's BK build instead
of having to clone and run the script themselves.
Non-tag asset names are versioned `${package.json version}-${shortsha}`
so each build's artifact is uniquely named, matching the BK artifact
naming convention.
Tradeoff: every PR/`main` push hits Apple's notary service (~30-90s
each). Well under the 75/hr/team cap, but worth knowing if traffic
spikes.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fetch developer id cert via fastlane match
The Buildkite Mac agent doesn't carry our Developer ID cert in its
keychain by default, so without this the script fails with `no
Developer ID Application identity for team PZYM8XX95Q in keychain`.
`set_up_signing` lane mirrors the `snelectron` / `matticspace-mobile`
pattern: `sync_code_signing(type: 'developer_id', ...)` against the
shared `a8c-fastlane-match` S3 bucket. Cert is already provisioned
there for our team. `app_identifier: []` works for `developer_id`
since the cert isn't tied to a specific app bundle.
Env-var presence is enforced via release-toolkit's `EnvManager` so we
fail fast with a clear message if `MATCH_S3_*` / `MATCH_PASSWORD` are
missing on the agent, rather than getting an opaque `match` error.
`Gemfile` carries only the two top-level gems (fastlane,
wpmreleasetoolkit); everything else is transitive.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Vendor gems locally
CI expects it.
* Downgrade to Xcode 26.3 because of nokogiri issue
https://buildkite.com/automattic/platform-imessage/builds/6/canvas?sid=019dfbb5-02e5-48df-8939-c3487f3960ee&tab=output
* retire gha release workflow
Buildkite now builds, signs (Developer ID, hardened runtime),
notarizes, and publishes the universal CLI to GitHub Releases on every
`v*` tag — verified end to end on the BK pipeline branch (build #7
produced a notarized artifact with CDHashes matching Apple's notary
ticket). Keeping the GHA workflow live alongside it would race the BK
publish on every tag and attach two assets — one signed and notarized,
one unsigned and arm64-only — to the same release.
Asset names line up: GHA produced
`imessage-cli-${version}-macos-arm64.tar.gz`, BK produces
`imessage-cli-${version}-macos-universal.tar.gz`. Same shape, just the
arch suffix differs. `.sha256` companion file and tar contents (a bare
`imessage-cli` at the root) are byte-compatible.
`.github/workflows/ci.yml` (tests + npm publish to GH Packages) is
intentionally untouched — that path serves a different purpose and
isn't replaced by Buildkite.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* port GHA ci workflow to buildkite
`.buildkite/commands/ci.sh` mirrors what `.github/workflows/ci.yml` did:
inject `BEEPER_DEPS_TOKEN` for private deps, install JS deps, run JS +
Swift tests, then build (or `npm publish` to GH Packages on `main`
when `package.json`'s version is new).
A `PUBLISHING=true` build env var is the equivalent of GHA's
`workflow_dispatch publishing=true` override.
Slotted next to the existing CLI release step in `pipeline.yml` — the
two run in parallel, hit different `.build/` paths, and report
independent GitHub statuses. The release step's `key` is renamed
`build` → `release-cli` so the two lanes don't clash.
GHA's `Run TypeScript tests` step ran `yarn test`, which per
`package.json` is `yarn test:swift && yarn test:js` — meaning the
follow-up `Run Swift tests` step ran the Swift suite a second time.
The port runs each suite once; net behavior is unchanged.
`actions/cache` for `.turbo` isn't ported — the BK Mac queue uses
persistent VMs, so incremental Turbo state stays on disk between
builds on the same agent.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* drop unused beeper deps token rewrite
The only `github.com/beeper/*` dependency in the tree is
`BetterSwiftAX` (`Package.swift:22`), which is public — verified
locally that `yarn install --immutable` resolves without the rewrite
and SwiftPM clones it anonymously.
The rewrite was inherited from the GHA workflow as defensive plumbing;
re-add it (or a case-insensitive variant covering `Beeper/`) the moment
a private beeper-org dep gets introduced.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Enable corepack so yarn is on PATH in CI
The BK Mac agents ship Node with corepack disabled, so the bare `yarn`
invocations in `ci.sh` exit 127. Enabling corepack activates the version
pinned by `packageManager` in `package.json` (yarn 4.14.1).
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Use nvm plugin to put Node on PATH in CI
The Buildkite Mac agents don't ship Node on the non-interactive PATH,
so `corepack enable` (and therefore `yarn`) failed with command-not-found.
The a8c convention is to activate Node via the `automattic/nvm` plugin,
which reads `.nvmrc` for the version. Pin to Node 20 to match the direct
`@types/node@20.17.19` dep in `package.json`.
---
Generated with the help of Claude Code, https://claude.ai/code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Remove comments referencing GHA in Buildkite
Co-authored-by: Gio Lodi <giovanni.lodi42@gmail.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rationale
First half of AINFRA-2351 — adds the local automation that builds, signs (Developer ID, hardened runtime), and notarizes
imessage-cliend to end.CI integration is intentionally out of scope here so the signing pipeline can be exercised on a workstation before being wired into a release pipeline. See #71 and #72
Gotchas
striphappens beforecodesign— stripping a signed binary invalidates the signature.--arch arm64|x86_64|universalflag for single-arch (default takes ~7min cold; single-arch ~4min).invalidPEMDocument(OpenSSL accepts it without).How to test
Produces a Developer ID-signed, hardened-runtime, Apple-notarized universal binary at
.build/universal/release/imessage-cli.End-to-end was validated locally during development (notarization id
5a725b81-b2e7-4212-b0a3-cc8dfe794e97).Posted by Claude Code (Opus 4.7, 1M context) on behalf of @mokagio with approval.