Skip to content

feat(release): package and distribute macOS builds via GitHub Releases and Homebrew - #17

Merged
amondnet merged 4 commits into
mainfrom
feat/release-packaging-macos
Jul 28, 2026
Merged

amondnet merged 4 commits into
mainfrom
feat/release-packaging-macos

Conversation

@amondnet

@amondnet amondnet commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

openhwp shipped v0.1.0 with no downloadable artifact. deno desktop had only ever been run locally, so there was nothing for users to install and nothing a Homebrew cask could point at. This adds the packaging pipeline and wires up distribution.

Triggered by the GitHub Release that release-please publishes. That event only fires because release-please now runs with an app token (#16) — under GITHUB_TOKEN it would not have, so this is a direct dependency on that fix.

Job Does
build-macos matrix build (arm64 + x86_64), sign, notarize, staple, upload artifact
release-assets checksums → SHA256SUMS, attach all assets to the release
update-homebrew-cask rewrite Casks/openhwp.rb in pleaseai/homebrew-tap
publish-jsr namespace reservation — gated off, see below

Signing is simpler than expected

deno desktop signs macOS bundles itself. With desktop.macos.codesignIdentity set it signs the app including the CEF framework and its five helper apps, and applies Hardened Runtime plus a secure timestamp.

I verified this against a real local build rather than trusting the docs — helper identifiers come out correctly derived (dev.pleaseai.openhwp.helper, dev.pleaseai.openhwp.helper.gpu), so no hand-rolled inside-out codesign pass is needed. Only notarization is manual (notarytool + stapler).

The identity is injected in CI, not committed: with codesignIdentity in deno.json, any contributor without the certificate would fail to build.

Two build details worth knowing

  • No universal-binary flag. arm64 and x86_64 build on separate runners (macos-14, macos-15-intel) and the cask carries both.
  • CFBundleName follows the --output filename. I hit this locally: -o OpenHWP-probe.app produced an app literally named "OpenHWP-probe". So builds go to a per-arch directory and are renamed after, keeping the app named OpenHWP.

Signing degrades honestly rather than silently: with no certificate the build warns and produces an ad-hoc bundle, and the verify step fails if a certificate was supplied but the output is still ad-hoc.

Required before this can produce a distributable build

Secrets/variables on the repo or org:

Name Kind
APPLE_CERTIFICATE_P12_BASE64, APPLE_CERTIFICATE_PASSWORD secrets
APPLE_SIGNING_IDENTITY variable — e.g. Developer ID Application: … (TEAMID)
APPLE_API_KEY_ID, APPLE_API_ISSUER_ID, APPLE_API_KEY_P8_BASE64 secrets (App Store Connect key for notarytool)

APP_ID and PRIVATE_KEY (tap token) already resolve for this repo.

JSR is deliberately gated off

jsr.io/@openhwp currently 404s, and the package would not be runnable anyway: apps/desktop is the shell entrypoint, and the studio bundle it requires is gitignored and therefore absent from the package — deno run jsr:@openhwp/desktop would exit with "studio bundle not found".

So the job is documented as namespace reservation and stays disabled behind JSR_PUBLISH_ENABLED. Create the scope, link this repo for OIDC, then set the variable. Shipping it ungated would fail every release.

Verification

  • Workflow YAML parses; job graph and permissions checked per job.
  • All six actions pinned to full 40-char SHAs (OSS policy).
  • Cask reference copy passes ruby -c.
  • deno fmt --check / deno lint pass.
  • Local deno desktop build confirmed the bundle layout, ad-hoc signature, CEF framework + 5 helpers, and identifier derivation.
  • Not verifiable pre-merge: the workflow only runs on release: published. workflow_dispatch with a tag is provided for a dry run against an existing release.

Checklist

  • PR title follows Conventional Commits
  • Tests added or updated — n/a, CI/packaging config
  • Lint/format pass
  • Documentation updated — rationale in the workflow header; cask copy under packaging/homebrew/
  • No breaking change

Summary by cubic

Build and ship signed, notarized macOS installers on every published release, then update the Homebrew cask so users can install via downloads or brew install pleaseai/tap/openhwp. Runs after release-please creates a GitHub Release (enabled by the app-token fix in #16).

  • New Features

    • Release workflow builds OpenHWP.dmg for darwin-arm64 and darwin-x64, verifies the .app signature, signs the .dmg with a timestamp, notarizes via notarytool, staples, uploads, and SHA256SUMS; hard-fails if signing or notary credentials are missing.
    • Attaches artifacts to the GitHub Release and rewrites pleaseai/homebrew-tap Casks/openhwp.rb with version and per-arch checksums; cask template passes brew audit (drops verified: on the url).
    • Adds gated JSR publish step (namespace reservation only) behind JSR_PUBLISH_ENABLED.
  • Migration

    • Add secrets: APPLE_CERTIFICATE_P12_BASE64, APPLE_CERTIFICATE_PASSWORD, APPLE_API_KEY_ID, APPLE_API_ISSUER_ID, APPLE_API_KEY_P8_BASE64.
    • Add variable: APPLE_SIGNING_IDENTITY (e.g., Developer ID string).

Written for commit 6072a8e. Summary will update on new commits.

…s and Homebrew

openhwp shipped v0.1.0 with no downloadable artifact. `deno desktop` was only
ever run locally, so there was nothing for users to install and nothing a
Homebrew cask could point at.

Add a Release workflow, triggered by the GitHub Release that release-please
publishes. That event only fires because release-please now runs with an app
token (#16); under GITHUB_TOKEN it would not have.

macOS signing is handled by `deno desktop` itself. With
desktop.macos.codesignIdentity set it signs the bundle — including the CEF
framework and the five helper apps — and applies Hardened Runtime and a secure
timestamp. Verified against a local build: helper identifiers are derived
correctly (dev.pleaseai.openhwp.helper, .helper.gpu), so no hand-rolled
inside-out codesign pass is needed. Only notarization is manual, via notarytool
with an App Store Connect API key, followed by stapling.

The identity is injected in CI rather than committed: with codesignIdentity in
deno.json, any contributor without the certificate would fail to build.

Two details the build depends on. deno desktop has no universal-binary flag, so
arm64 and x86_64 are built on separate runners and the cask carries both. And
CFBundleName is derived from the --output filename, so builds land in a
per-arch directory and are renamed afterwards — `-o OpenHWP-darwin-arm64.dmg`
would otherwise name the application that.

Signing degrades honestly: with no certificate configured the build emits a
warning and produces an ad-hoc bundle, and the verify step fails if a
certificate was supplied but the result is still ad-hoc.

The cask is the tap's first (it holds CLI formulae today), so the job creates
Casks/. packaging/homebrew/openhwp.rb is the reviewable copy of the generated
template, mirroring the convention in pleaseai/shunt.

JSR publishing is present but gated off. @openhwp does not exist on jsr.io yet,
and the package would not be runnable regardless: it is the shell entrypoint,
and the studio bundle it needs is gitignored and absent from the package. The
job is documented as namespace reservation and stays disabled until the scope
is created and JSR_PUBLISH_ENABLED is set.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a new Homebrew cask configuration file (openhwp.rb) for the OpenHWP desktop editor. The review feedback suggests two improvements: removing a relative directional reference ("below") in the comments to prevent future dangling references, and removing the redundant verified parameter from the url stanza since the download URL host matches the homepage host.

Comment thread packaging/homebrew/openhwp.rb Resolved
Comment thread packaging/homebrew/openhwp.rb Resolved
@amondnet amondnet self-assigned this Jul 28, 2026
@amondnet
amondnet marked this pull request as ready for review July 28, 2026 11:58
…mage

`deno desktop` signs the .app it emits but leaves the .dmg container
unsigned, so `codesign -dv` on the .dmg reports "code object is not signed
at all" regardless of how the app was signed — the ad-hoc guard could never
fire. Verify the .app instead (rejecting ad-hoc signatures and a missing Team
ID), then sign the .dmg with a secure timestamp before notarizing it, which is
what Apple expects of a stapled disk image.
Comment thread .github/workflows/release.yml Resolved
@greptile-apps

greptile-apps Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

Greptile Summary

The PR adds an automated macOS release pipeline.

  • Builds architecture-specific signed and notarized disk images.
  • Uploads release assets and checksums.
  • Updates the Homebrew tap cask.
  • Adds a disabled-by-default JSR publishing job and a reference cask template.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the workflow now exits before building or publishing when the signing certificate is absent.

Important Files Changed

Filename Overview
.github/workflows/release.yml Adds fail-closed signing, notarization, release-asset upload, Homebrew cask updating, and gated JSR publishing; the previously reported unsigned-publication path is closed by rejecting absent signing credentials.
packaging/homebrew/openhwp.rb Adds a reviewable reference copy of the generated architecture-aware Homebrew cask.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Release published or manual dispatch] --> B[Build arm64]
  A --> C[Build x86_64]
  B --> D[Sign and notarize DMG]
  C --> D
  D --> E[Upload artifacts and checksums]
  E --> F[Update Homebrew cask]
  E --> G{JSR publishing enabled?}
  G -->|Yes| H[Publish namespace package]
  G -->|No| I[Skip JSR publishing]
Loading

Reviews (3): Last reviewed commit: "fix(release): fail the build when signin..." | Re-trigger Greptile

amondnet added 2 commits July 28, 2026 21:03
Drop the redundant `verified:` from the cask url — its host matches the
homepage host, so `brew audit` flags it — and remove a relative directional
reference from the placeholder comment. Applied to both the reviewable copy and
the authoritative heredoc template in the release workflow.
… are missing

The certificate step previously fell back to an ad-hoc signature with only a
warning. Every trigger for this workflow — a published release or a manual
dispatch — attaches its artifacts to a GitHub Release and publishes them
through the Homebrew tap, so that fallback would ship a build Gatekeeper
refuses to open on any machine but the runner that produced it. Treat a missing
certificate, signing identity, or App Store Connect key as a hard error, which
also removes the now-redundant `signed` output and its conditionals.
@sonarqubecloud

Copy link
Copy Markdown

@amondnet
amondnet merged commit 15bf08a into main Jul 28, 2026
11 checks passed
@amondnet
amondnet deleted the feat/release-packaging-macos branch July 28, 2026 12:10
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