Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 50 additions & 24 deletions .github/workflows/sync-homebrew-tap.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
name: sync-homebrew-tap

# Keeps a separate sugatoray/homebrew-pptxdiff tap repo in sync with this
# monorepo's src/packages/pptxdiff-brew/ -- both the formula's url/sha256
# pin AND the package's README.md/CHANGELOG.md/LICENSE, which the tap repo
# needs as its own top-level files once it's a standalone repo (LICENSE in
# particular: the tap repo distributes the same Apache-2.0-licensed formula
# content, so it needs its own copy, same as pptxdiff-vscode already
# carries its own copy of the root LICENSE for the same reason).
# monorepo's src/packages/pptxdiff-brew/ -- the formula's url/sha256 pin,
# plus LICENSE and a tap-specific brew_README.md (pushed to the tap as its
# README.md; the package's own README.md/CHANGELOG.md are monorepo-internal
# docs and deliberately NOT copied to the tap -- see brew_README.md's own
# doc comment for why a separate minimal file exists). LICENSE is a real
# copy because the tap repo distributes the same Apache-2.0-licensed formula
# content and needs its own, same as pptxdiff-vscode already carries its own
# copy of the root LICENSE for the same reason.
#
# Trigger choice, deliberate: npm publishing from this repo is still a
# manual `npm publish` step (see root package.json's `build:npm` -- it only
# packs, it doesn't publish), so there's no reliable "npm publish just
# happened" CI event to hook. Rather than race a push-to-master trigger
# against an unpublished version, this runs on:
# - workflow_dispatch: the maintainer runs it by hand, optionally pinning
# an exact version. ALWAYS syncs current README/CHANGELOG/LICENSE/
# formula state to the tap on this trigger, even if the version pin
# itself didn't change -- this is the "I just edited the README, push
# it to the tap" path, a deliberate human action.
# an exact version. ALWAYS syncs current README/LICENSE/formula state to
# the tap on this trigger, even if the version pin itself didn't change
# -- this is the "I just edited the README, push it to the tap" path, a
# deliberate human action.
# - schedule (weekly): a safety net that catches version drift
# automatically even if the manual dispatch is forgotten, the same way
# `brew livecheck` periodically checks upstream versions in real
Expand Down Expand Up @@ -51,14 +53,14 @@ permissions:

jobs:
bump-formula:
name: Bump formula + stage README/CHANGELOG/LICENSE
name: Bump formula + stage README/LICENSE
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.sync.outputs.changed }}
version: ${{ steps.sync.outputs.version }}
# true if the formula pin actually changed, OR this run was a manual
# workflow_dispatch (an explicit human action that should always push
# the current README/CHANGELOG/LICENSE/formula state to the tap, even
# the current README/LICENSE/formula state to the tap, even
# when the version pin itself is unchanged). A scheduled run with no
# pin change stays false -- no point opening PRs / spinning up a
# macOS runner for a no-op weekly check.
Expand All @@ -80,13 +82,18 @@ jobs:
working-directory: src/packages/pptxdiff-brew
run: npm test

- name: Stage Formula/pptxdiff.rb + README.md + CHANGELOG.md + LICENSE for downstream jobs
# dist-tap/README.md comes from brew_README.md (a minimal, tap-facing
# install doc), NOT the package's own README.md -- that file documents
# this monorepo package itself (Red/Green TDD, CI pipeline internals,
# etc.) and is not meant to ship as the tap repo's README. CHANGELOG.md
# is intentionally not staged/copied at all -- the tap repo doesn't
# carry a changelog copy.
- name: Stage Formula/pptxdiff.rb + README.md + LICENSE for downstream jobs
if: steps.sync.outputs.changed == 'true' || github.event_name == 'workflow_dispatch'
run: |
mkdir -p dist-tap
cp src/packages/pptxdiff-brew/Formula/pptxdiff.rb dist-tap/pptxdiff.rb
cp src/packages/pptxdiff-brew/README.md dist-tap/README.md
cp src/packages/pptxdiff-brew/CHANGELOG.md dist-tap/CHANGELOG.md
cp src/packages/pptxdiff-brew/brew_README.md dist-tap/README.md
cp src/packages/pptxdiff-brew/LICENSE dist-tap/LICENSE

- name: Upload the staged files for downstream jobs
Expand Down Expand Up @@ -126,11 +133,32 @@ jobs:
name: pptxdiff-tap-files
path: .

# `brew audit` no longer accepts a bare file path (Homebrew/brew#18873:
# "Calling `brew audit [path ...]` is disabled! Use `brew audit
# [name ...]` instead.") -- it now requires the formula to live in a
# tap so it can be addressed by name. Stand up a throwaway local tap
# and drop the formula into it.
#
# A freshly created local tap is untrusted by default (Homebrew's Tap
# Trust feature, https://docs.brew.sh/Tap-Trust -- third-party tap
# Ruby is arbitrary/unsandboxed code, so Homebrew refuses to load it
# from a tap-qualified name until explicitly trusted: "Refusing to
# load formula local/pptxdiff-ci/pptxdiff from untrusted tap
# local/pptxdiff-ci."). Trust just this one formula (narrower than
# trusting the whole tap) before auditing it by name.
- name: brew audit --strict --online
run: brew audit --strict --online ./pptxdiff.rb

run: |
brew tap-new local/pptxdiff-ci --no-git
cp ./pptxdiff.rb "$(brew --repository)/Library/Taps/local/homebrew-pptxdiff-ci/Formula/pptxdiff.rb"
brew trust --formula local/pptxdiff-ci/pptxdiff
brew audit --strict --online local/pptxdiff-ci/pptxdiff

# `brew install` also now rejects a bare formula path ("Homebrew
# requires formulae to be in a tap, rejecting: ./pptxdiff.rb") -- same
# underlying tap requirement as `audit` above. Install by the same
# tap-qualified name from the local tap created/trusted above.
- name: brew install --formula
run: brew install --formula ./pptxdiff.rb
run: brew install --formula local/pptxdiff-ci/pptxdiff

- name: brew test
run: brew test pptxdiff
Expand All @@ -140,7 +168,7 @@ jobs:
run: brew uninstall --force pptxdiff || true

sync-tap-repo:
name: Push formula + README/CHANGELOG/LICENSE to sugatoray/homebrew-pptxdiff
name: Push formula + README/LICENSE to sugatoray/homebrew-pptxdiff
needs: [bump-formula, brew-audit]
if: needs.bump-formula.outputs.should_sync == 'true'
runs-on: ubuntu-latest
Expand All @@ -163,12 +191,11 @@ jobs:
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
path: homebrew-pptxdiff

- name: Copy the formula into Formula/, README/CHANGELOG/LICENSE into the repo root
- name: Copy the formula into Formula/, README/LICENSE into the repo root
run: |
mkdir -p homebrew-pptxdiff/Formula
cp pptxdiff-tap-files/pptxdiff.rb homebrew-pptxdiff/Formula/pptxdiff.rb
cp pptxdiff-tap-files/README.md homebrew-pptxdiff/README.md
cp pptxdiff-tap-files/CHANGELOG.md homebrew-pptxdiff/CHANGELOG.md
cp pptxdiff-tap-files/LICENSE homebrew-pptxdiff/LICENSE

- name: Open a PR against the tap repo
Expand All @@ -181,8 +208,8 @@ jobs:
body: |
Automated sync from `sugatoray/pptxdiff`'s
`.github/workflows/sync-homebrew-tap.yml`, source of truth at
`src/packages/pptxdiff-brew/` (`Formula/pptxdiff.rb`, `README.md`,
`CHANGELOG.md`, `LICENSE`).
`src/packages/pptxdiff-brew/` (`Formula/pptxdiff.rb`,
`brew_README.md` -> `README.md`, `LICENSE`).

Already verified in that run: real Red/Green tests
(`src/packages/pptxdiff-brew/test_formula.mjs` +
Expand All @@ -193,5 +220,4 @@ jobs:
add-paths: |
Formula/pptxdiff.rb
README.md
CHANGELOG.md
LICENSE
53 changes: 53 additions & 0 deletions docs/.scrolls/HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,59 @@

**Read `.scrolls/SPEC.md` first for the full feature list.** This file is the "what's the state of things right now" note — update it at the end of every session, keep it short and current (prune stale entries).

## Update (2026-08-09 — fixed the sync-homebrew-tap.yml `brew-audit` job)
- Direct ask: "the brew github actions pipeline ... did not succeed. Fix it." The workflow's first
real `workflow_dispatch` run (2026-08-08) failed at the `brew-audit` (macOS) job's `brew audit
--strict --online ./pptxdiff.rb` step: `##[error]Calling \`brew audit [path ...]\` is disabled!
Use \`brew audit [name ...]\` instead.` — a Homebrew CLI change (Homebrew/brew#18873) that landed
after this workflow was originally written, disallowing path-based `brew audit` invocations
entirely (install/test are unaffected — only `audit`).
- Fix: `.github/workflows/sync-homebrew-tap.yml`'s `brew-audit` job now stands up a throwaway local
tap (`brew tap-new local/pptxdiff-ci --no-git`), copies the staged `pptxdiff.rb` into that tap's
`Formula/` directory, and audits it by tap-qualified name (`local/pptxdiff-ci/pptxdiff`) instead of
by path. `brew install --formula ./pptxdiff.rb` and `brew test pptxdiff` steps left unchanged (not
affected by the breaking change).
- Confirmed root cause against the actual failed run's logs (`gh`/GitHub MCP `get_job_logs`, run id
31236058133) rather than guessing, then verified the fix pattern against Homebrew's own guidance
(Homebrew org discussion #4864) and a real-world precedent (`homebrew-releaser`'s CI) before
applying it. New WISDOM.md trap entry added so this isn't re-discovered from scratch next time
Homebrew ships another audit-CLI breaking change.
- **Follow-up (same day):** maintainer ran `workflow_dispatch` for real against this fix and it
failed again, at the same `brew audit --strict --online local/pptxdiff-ci/pptxdiff` line, with a
NEW error: "Refusing to load formula local/pptxdiff-ci/pptxdiff from untrusted tap
local/pptxdiff-ci." — Homebrew's Tap Trust feature (third-party tap Ruby is unsandboxed code, so a
freshly `tap-new`'d local tap is untrusted until explicitly trusted). The path-fix above was
necessary but not sufficient: moving to a tap-qualified name tripped a second, separate check.
Fixed by adding `brew trust --formula local/pptxdiff-ci/pptxdiff` right after copying the formula
into the tap, before `brew audit` references it by that name. See WISDOM.md's new trap entry.
- **Second follow-up (same day):** dispatched the workflow again (from this session, with explicit
user go-ahead) against the tap-trust fix. Progress — `tap-new`/`brew trust` both succeeded this
time — but `brew audit --strict --online` then failed on a real, pre-existing formula lint issue
it could finally reach: "`livecheck` (line 10) should be put before `depends_on` (line 8)".
Homebrew enforces a canonical formula-component order under `--strict`. Fixed by reordering
`Formula/pptxdiff.rb` (`livecheck` block now above `depends_on "node"`) — confirmed
`lib.mjs`'s regex-based parse/pin-update logic is order-agnostic (grepped for `livecheck`/
`depends_on`/`url`/`sha256` across `lib.mjs`/`test_formula.mjs`/`test_sync_tap.mjs`), then reran
`npm test` locally: 33/33 (11 `test_sync_tap` + 22 `test_formula`) still green.
- **Pattern worth naming**: this job has now failed on three DIFFERENT real Homebrew-CLI checks in
sequence, each only reachable once the prior one was fixed (path→tap→trust→audit-content). Treat
"the previous error is gone" as progress, not proof of green — always re-dispatch for real after
each fix and read the new logs rather than assuming the job now passes end-to-end.
- **Third follow-up (same day):** dispatched again after the livecheck-ordering fix. `brew audit
--strict --online` PASSED this time (first time this job got past audit at all). But
`brew install --formula ./pptxdiff.rb` then failed with "Homebrew requires formulae to be in a
tap, rejecting: ./pptxdiff.rb" — the same underlying tap requirement as the `brew audit [path]`
change, just enforced by `install` too and only reachable once `audit` stopped being the first
failure. Fixed by installing via the same tap-qualified name (`brew install --formula
local/pptxdiff-ci/pptxdiff`) instead of the bare path, reusing the tap already created/trusted for
the audit step. `brew test pptxdiff` left as-is (short name, not path-based, unaffected).
- Not yet re-verified end-to-end with a real `workflow_dispatch` run after THIS fourth fix — next
session/maintainer should re-run `sync-homebrew-tap` manually again and confirm all three jobs
actually go green (bump-formula → brew-audit → sync-tap-repo, including the real push to
`sugatoray/homebrew-pptxdiff`). Given the pattern of one fix exposing the next real check, don't
be surprised if `brew test` or the `sync-tap-repo` job surfaces something new too — re-check logs,
don't assume.

## Update (2026-08-05 — docs-site coverage for the Homebrew formula)
- Direct ask: "Make sure to update the documentation under pptxdiff/docs-site folder." Read
`docs/.scrolls/DOCS.md` first per `STARTER.md`'s standing rule (this is more than a routine
Expand Down
4 changes: 4 additions & 0 deletions docs/.scrolls/WISDOM.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
- **A second independent `JSZip.loadAsync` of the same buffer, awaited separately from the main parse, is a race-condition risk** on first mount (cold ESM import timing). If you already parsed the buffer once, prefer deriving extra data from that same parse pass, or from data you already know (see the embedded-fonts fix in HANDOFF.md), rather than re-opening the zip a second time.
- **Never assume "works when I manually retest" means "works on first automatic load."** Timing races on `componentDidMount`'s first run are a real, recurring category of bug in this file — always ask "does this depend on anything resolving before something else, without an explicit await chain guaranteeing it?"
- **`src/packages/pptxdiff-vscode/extension.js` has its own copy of the static file server from `bin/cli.js`, not a shared module.** The 2026-07-29/30 CLI security-hardening session (loopback binding, `path.relative()` containment, response headers) fixed only `bin/cli.js`, leaving the VS Code extension's server with the same unbound-`listen(0)` and raw-`startsWith(ROOT)` weaknesses until a follow-up session ported the fixes over by hand. There is no automated check that keeps the two servers in sync — when hardening (or any other logic change) touches one of these two files, check the other for the same pattern before considering the work done.
- **`brew audit` no longer accepts a bare file path** (`brew audit ./Formula/foo.rb` fails hard: "Calling `brew audit [path ...]` is disabled! Use `brew audit [name ...]` instead" — Homebrew/brew#18873). It now requires the formula to live in a tap so it can be addressed by `tap/name`. In CI, stand up a throwaway local tap first: `brew tap-new local/x --no-git`, copy the `.rb` file into `$(brew --repository)/Library/Taps/local/homebrew-x/Formula/`, then `brew audit --strict --online local/x/name`. `brew install --formula <path>` and `brew test <name>` are unaffected — only `audit` changed. Bit `.github/workflows/sync-homebrew-tap.yml`'s `brew-audit` job on its first real run (2026-08-08); fixed by wrapping the path in a local tap.
- **A freshly `tap-new`'d local tap is untrusted by default** (Homebrew's Tap Trust feature, https://docs.brew.sh/Tap-Trust — third-party tap Ruby is unsandboxed code). Referencing a formula by tap-qualified name (`local/x/name`, e.g. right after the fix above) fails with "Refusing to load formula local/x/name from untrusted tap local/x" until explicitly trusted. Fix: `brew trust --formula local/x/name` (narrower than trusting the whole tap with `brew trust local/x`) right after copying the formula into the tap, before `audit`/anything else references it by tap-qualified name. `HOMEBREW_NO_REQUIRE_TAP_TRUST=1` exists to disable the check globally but Homebrew's own docs say not to use it. Second bug found in the SAME `brew-audit` job fix (2026-08-09) — the path-fix alone wasn't enough because switching to a tap-qualified name tripped this separate, newer check; when a Homebrew CLI error changes, re-run for real and check whether a NEW error replaced the old one rather than assuming one fix closes the loop.
- **Homebrew's formula-component style order is enforced by `brew audit --strict`**: `livecheck do...end` must come before `depends_on` (canonical order is roughly desc/homepage/url/version/sha256/license/livecheck/depends_on/install/test — see the audit's own line-number-anchored complaint for the exact expected position). `Formula/pptxdiff.rb` had `depends_on "node"` above `livecheck`; audit failed with "`livecheck` (line 10) should be put before `depends_on` (line 8)" once the tap-trust fix above let audit actually run for real (2026-08-09, third bug found the same day working through this one job — each fix exposed the next real check). None of `lib.mjs`'s regex-based parsing/pin-update logic cares about stanza order, so reordering was a pure formula-file fix with no follow-on code change needed.
- **`brew install` rejects a bare formula path too, not just `brew audit`**: "Homebrew requires formulae to be in a tap, rejecting: ./pptxdiff.rb" — the SAME underlying tap requirement as the `brew audit [path]` deprecation, just enforced by a different subcommand and only reachable once `audit` stopped failing first. Fix is the same shape: install by the tap-qualified name (`brew install --formula local/x/name`) from the local tap already created for audit, not by path. Fourth bug found the same day/session working through this one `brew-audit` job (2026-08-09) — a strong sign these path-based invocations across `brew audit`/`brew install`/(maybe `brew test`, unverified) all trace back to one underlying Homebrew policy change requiring taps, so any future `brew <subcommand> ./x.rb` in this repo's CI should be treated as suspect on sight and written tap-qualified from the start.
- **A literal `</script` substring ANYWHERE in `index.html`'s source text prematurely closes its own outer `<script type="text/x-dc">` tag — including inside a `//` comment or a JS string literal, not just as real markup.** The HTML tokenizer scans raw bytes for that 8-character sequence while in "script data state"; it has no idea it's looking at a JS comment or a quoted string, so a comment that literally says `// a "</script>" tag...` or a self-test fixture string `'</script><img...>'` breaks the WHOLE class's `eval()` (silent per the trap above — "logic class eval FAILED... renders with props only", every dynamic value blank) exactly like a real syntax error would, but the actual defect is upstream of JS parsing entirely (the browser never even got a complete script element to hand to the JS parser). Bit twice in one project: once building the screenshot-export HTML-viewer's generated `<script>` string, again writing THIS trap's own regression self-test (a hostile-input fixture literally containing `</script>` to prove the app's OWN escaping works). Fix is always the same: never let the literal 8-char sequence appear contiguously in this file's source — split it (`'<' + '/script>...'`) or backslash-break it (`<\/script>`, safe in both comments and strings since the tokenizer only matches `<` immediately followed by `/`). After writing ANY string/comment that discusses or constructs script-tag-closing syntax, `grep -i '</script' index.html` and confirm every hit is a real, intentional `<script>...</script>` pair before trusting a green self-test run.

## Ditches — bad patterns or decisions to avoid
Expand Down
Loading
Loading