Skip to content

Add CI workflow that packs tarballs and installer artifacts#11

Merged
webzherd merged 13 commits into
mainfrom
ci/pack-tarballs-workflow
May 6, 2026
Merged

Add CI workflow that packs tarballs and installer artifacts#11
webzherd merged 13 commits into
mainfrom
ci/pack-tarballs-workflow

Conversation

@webzherd
Copy link
Copy Markdown
Contributor

@webzherd webzherd commented May 4, 2026

Summary

  • Adds .github/workflows/pack.yml — on workflow_dispatch and v* tag pushes, builds:

    • Tarballs for linux-x64, linux-arm64, darwin-x64, darwin-arm64, win32-x64 (.tar.gz + .tar.xz, bundled Node 20)
    • macOS .pkg installers for darwin-x64 and darwin-arm64 (built on a macos-latest runner using native pkgbuild — Ubuntu 24.04 dropped both xar and bomutils from its repos)
    • Windows .exe installer (NSIS) for win32-x64

    All artifacts upload as workflow artifacts under two names — bitmovin-cli-tarballs-and-windows and bitmovin-cli-macos. 30-day retention. Internal testing only: zip-wrapped (GitHub limitation) and unsigned. Code signing + GitHub Release publishing + npm publishing will land in subsequent PRs.

  • Guards the prepare script with a dist-presence check: [ -d dist ] || tsc -b. oclif pack runs an internal npm install --production in a temp workspace, which previously triggered an unconditional tsc -b even though TypeScript is a devDependency. The new form runs the build only when dist/ is missing — fails loud if tsc is then unavailable, silently skips when dist/ is already present (oclif's tmp workspace, npm install from the shipped tarball).

Test plan

  • Tag-pushed test run on a throwaway v0.0.0-pack-test.x tag — both jobs green
  • Linux artifact: 5 tarballs + 1 .exe, ~576 MB total. Linux-x64 tarball extracted locally, bin/bitmovin --version / --help / config show --json all work with bundled Node 20 + correct secret masking.
  • macOS artifact: 2 .pkg files (darwin-x64, darwin-arm64), ~272 MB total
  • Workflow re-verified after revert of the release job

Out of scope (follow-ups)

  1. Code signing — Apple Developer ID + notarization for macOS, Authenticode (EV) for Windows. Has to land before any user-facing Release publishing to avoid Gatekeeper / SmartScreen warnings undercutting the install experience.
  2. GitHub Release publishing — attach signed installers as individual download assets on v* tag push. Bundle with Bump lodash from 4.17.23 to 4.18.1 #1.
  3. npm publish on tag — needs NPM_TOKEN secret. README install instructions.
  4. Debian .deboclif pack deb, straightforward addition.
  5. NSIS compression — current .exe is 303 MB; LZMA/SOLID compression should bring it down significantly.
  6. S3 hosting + autoupdate channels (stable, beta) via oclif upload.
  7. Homebrew tap (bitmovin/homebrew-tap).
  8. Prerelease channelnpm publish --tag next for -rc/-beta tags.

🤖 Generated with Claude Code

Builds standalone tarballs (linux-x64, linux-arm64, darwin-x64,
darwin-arm64, win32-x64) via `oclif pack tarballs` on workflow_dispatch
and on `v*` tag pushes, and uploads them as a workflow artifact.
Tarballs bundle Node so users without Node installed can run the CLI.

Publishing to GitHub Releases / npm is intentionally deferred to a
follow-up so we can verify the artifact build first.

Also guards the `prepare` script: `oclif pack` runs an internal
`npm install --production` that triggered the previous unconditional
`tsc -b` even though TypeScript is a devDependency. The new guard
runs the build only when tsc is present, preserving the
install-from-git build path while letting the production install
inside oclif pack succeed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@webzherd webzherd marked this pull request as draft May 4, 2026 11:20
@webzherd webzherd requested a review from lukaskroepfl May 5, 2026 08:25
@lukaskroepfl
Copy link
Copy Markdown
Member

The prepare shim silently skips on failure. Original tsc -b failed loud; the new if [ -x ./node_modules/.bin/tsc ]; then tsc -b; fi returns 0 whether tsc ran or not — so a future install path that produces a half-broken state where tsc is missing won't surface. It's also treating a symptom (oclif pack's temp npm install --production triggers the prepare hook) when the cause might be better fixed at the oclif invocation — e.g. skipping lifecycle scripts in oclif's temp workspace, or pre-building and configuring oclif not to re-install. Worth checking which fix is structurally right before shipping the shim.

Installs nsis (Windows installer) and bomutils + xar (macOS package
tooling) on the runner, then runs `oclif pack macos` and
`oclif pack win` after the existing tarball pack. Uploads everything
under a single `bitmovin-cli-installers` artifact.

The installers are unsigned for now — they build and run, but macOS
Gatekeeper and Windows SmartScreen will warn users until we add
Apple Developer ID notarization and Authenticode signing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@webzherd webzherd changed the title Add CI workflow that packs standalone tarballs Add CI workflow that packs standalone tarballs and installers May 5, 2026
webzherd and others added 5 commits May 5, 2026 11:23
Ubuntu 24.04 dropped both `xar` and `bomutils` from its package
repos, so building a `.pkg` on a Linux runner without source-building
those tools is no longer feasible. Move the macOS package build to a
`macos-latest` runner that has native `pkgbuild` / `productbuild`,
and keep tarballs + Windows `.exe` (NSIS) on the Linux runner.

Outputs split across two artifacts: `bitmovin-cli-tarballs-and-windows`
and `bitmovin-cli-macos`. Public repo, so macOS runner minutes are free.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`oclif pack macos` requires `oclif.macos.identifier` in package.json
to set the macOS package identifier. Use the reverse-DNS form
`com.bitmovin.cli`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`oclif pack win` defaults to building x64, x86 and arm64 .exe
installers in parallel — each ~200 MB — which pushed the Linux
artifact bundle to 1.2 GB. Restrict to win32-x64 to match the
tarball target list and bring the artifact back under ~500 MB.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Workflow artifacts are always zip-wrapped by GitHub, which makes
.pkg/.exe download-and-run unfriendly. Add a third job that runs
after both pack jobs, downloads their artifacts, and creates a
draft GitHub Release with each installer file (tarballs, .exe, .pkg)
as its own asset.

Draft releases are invisible to anyone without write access. After
verifying the assets, a maintainer publishes via the Releases UI
("Publish release" button); the tag stays the same, only the
visibility flips. Workflow artifacts are kept too for shorter-term
inspection (auto-expire in 30 days).

The release job is gated on tag pushes (`startsWith(github.ref,
'refs/tags/v')`), so workflow_dispatch runs still produce artifacts
without trying to create a release object.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When upload-artifact gets multiple `path:` entries with a common
prefix (`dist/`), it preserves any path components below that
prefix. So `dist/*.tar.gz` lands at the artifact root but
`dist/win32/*.exe` lands under `win32/` inside the artifact.
Update the release glob to match the actual download layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lukaskroepfl
Copy link
Copy Markdown
Member

Two things on this expanded version:

  1. prepare shim is unchanged — the silent-skip concern from my earlier comment still applies. Worth confirming this is the structurally right fix vs. addressing it at the oclif invocation, especially before this PR also lands Release publishing where a half-broken state would ship to users.

  2. Scope expansion contradicts the staging the PR opened with. The original test plan was "inspect actual artifacts before anything reaches users" with publishing deferred to a follow-up. Now it's tarballs + macOS .pkg + Windows .exe + draft Release on tag, all in one PR. That's a lot more surface to revert if any one piece misbehaves, and the draft-Release job in particular only runs on v* tags so it can't be exercised from workflow_dispatch before merging. Strong vote for splitting the Release job back out — the original two-step plan was sounder.

  3. Unsigned .pkg and .exe ship through the Release. Apple Gatekeeper will block the .pkg unless users right-click → Open; Windows SmartScreen will warn. Acknowledging this in CHANGELOG is good, but if the goal is "users can install without Node," telling them "and now disable Gatekeeper" undercuts that. Worth either keeping installers as workflow artifacts (internal testing) until signing/notarization lands, or being explicit in the Release notes about the install ceremony required.

Two changes in response to PR feedback:

1. Revert the draft-release job. Restore the original PR scope of
   "build artifacts only, defer publishing." Tarballs, .pkg, and .exe
   continue to upload as workflow artifacts (zip-wrapped, internal
   testing only). GitHub Release publishing will land in a follow-up
   that also addresses code signing — shipping unsigned installers
   through a Release surfaces Gatekeeper / SmartScreen warnings that
   undercut the "no Node required" promise.

2. Make `prepare` fail loud when build is actually needed. The
   previous shim `if [ -x ./node_modules/.bin/tsc ]; then tsc -b; fi`
   silently no-op'd whenever tsc was missing, hiding broken-state
   installs. New form `[ -d dist ] || tsc -b` only skips when dist
   already exists (oclif pack's tmp workspace, npm install from a
   shipped tarball) and otherwise runs `tsc -b` — which fails loud
   if tsc isn't available. The three install paths still work:

   - `npm install <git-url>`: dist absent, devDeps present  → builds
   - `npm install` from npm tarball:    dist already shipped → skip
   - `oclif pack` tmp `npm install --production`: dist copied → skip

   Considered Lukas's suggestion of skipping lifecycle scripts at
   the oclif invocation level instead — oclif v4 doesn't expose a
   knob for this in `pack`, so the dist-presence guard remains the
   cleanest available fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@webzherd webzherd changed the title Add CI workflow that packs standalone tarballs and installers Add CI workflow that packs tarballs and installer artifacts May 5, 2026
webzherd and others added 2 commits May 5, 2026 15:32
Adds an "Import Developer ID Installer cert into temp keychain" step
to the pack-macos job that decodes a P12 from the
APPLE_INSTALLER_CERT_P12_BASE64 / APPLE_INSTALLER_CERT_PASSWORD
secrets, imports it into a per-job temp keychain (random unlock
password, 6h timeout), and authorizes productsign / productbuild
against the imported identity.

oclif.macos.sign in package.json is set to the cert's full Common
Name; oclif's pack:macos invokes productsign with that identity at
the end of pkg build. Resulting .pkg passes pkgutil --check-signature
and Gatekeeper installer trust.

Notarization is not wired yet — that needs a Developer ID Application
codesign of bundled Mach-O binaries plus xcrun notarytool against an
App Store Connect API key. Without notarization, Gatekeeper still
shows the standard "from an identified developer" allow flow on
first install. Notarization will follow.

The workflow will fail at the import step until both APPLE_INSTALLER_CERT_*
secrets are configured on the repo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
oclif passes the sign string straight to a /bin/sh -c invocation
without quoting, so any shell-special characters break it. The
common name has spaces, a colon, and the team-id parens — pkgbuild
on the runner died with `syntax error near unexpected token '('`.

pkgbuild --sign accepts either the CN or the certificate's SHA-1
hash (per `man pkgbuild`). Switching to the hex-only SHA-1 sidesteps
oclif's quoting bug entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@lukaskroepfl lukaskroepfl left a comment

Choose a reason for hiding this comment

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

All three points addressed cleanly:

  1. Release job reverted — back to artifact-only, matches the staged plan in the original PR description.
  2. prepare shim[ -d dist ] || tsc -b is the right shape. When dist/ is missing it runs tsc -b and fails loud if tsc isn't there; when dist/ already exists (the oclif pack temp-workspace case) it short-circuits without trying to build. Preserves the install-from-git path while letting the production install succeed.
  3. macOS .pkg is signed with Developer ID Installer (SHA1 fingerprint to disambiguate).

Two small follow-ups, neither blocking:

  • CHANGELOG drift: the entry still says "Installers are unsigned. Code signing… will follow" but the .pkg is now signed in this PR. Worth tightening to "macOS .pkg is signed with Developer ID Installer; notarization and Windows Authenticode will follow."
  • Notarization is the other half of the Gatekeeper story. Signing alone doesn't satisfy spctl --assess for downloaded .pkg's; users will still hit "Apple cannot check this for malicious software" unless they right-click → Open or you notarize. Worth queueing as the next signing follow-up.

Approving on the artifact-only scope.

webzherd and others added 3 commits May 6, 2026 08:51
pkgbuild was hanging for 10+ minutes in pack-macos with no diagnostic
output, and a `gh run cancel` finally produced
`Terminate orphan process: pid (12617) (pkgbuild)`. Root cause: the
key was imported with -T productsign/productbuild only, so pkgbuild
itself wasn't on the trust list and was blocking on a non-existent
auth dialog when reaching for the signing key. Adding
-T /usr/bin/pkgbuild and -T /usr/bin/codesign closes the gap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
oclif's pack:macos calls productsign but doesn't fail loud if the
signature ends up unverifiable — we'd discover that only when a user
tries to install. Run pkgutil --check-signature on each produced
.pkg in the macOS job; non-zero exit fails the job. Catches missing
or corrupt signatures, untrusted cert chains, and other signature
defects before the artifact ships.

spctl --assess deliberately not added here — without notarization
it would fail on every signed-but-unnotarized .pkg. Will land
together with the notarization step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@webzherd webzherd marked this pull request as ready for review May 6, 2026 10:07
@webzherd webzherd merged commit 34760ae into main May 6, 2026
3 checks passed
@webzherd webzherd deleted the ci/pack-tarballs-workflow branch May 6, 2026 10:07
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.

2 participants