diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml new file mode 100644 index 0000000..3f34a35 --- /dev/null +++ b/.github/workflows/binaries.yml @@ -0,0 +1,93 @@ +name: binaries + +on: + push: + branches: [master] + paths: + - "bin/cli.js" + - "src/pptxdiff/**" + - "src/packages/binaries/**" + - ".github/workflows/binaries.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + # linux + win (x64 AND arm64) are genuinely cross-compiled by + # @yao-pkg/pkg from one host — confirmed directly (real ELF aarch64 / + # PE32+ Aarch64 executables built on an x64 dev sandbox) — no + # codesigning concern for either OS, so all four build together in one + # job. Cross-ARCH builds need --fallback-to-source (see build.mjs) since + # V8 bytecode generation for a foreign arch fails without QEMU emulation + # — harmless, this is already-open-source code with nothing to protect + # by shipping bytecode instead of plain source. + build-linux-win: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v5 + with: + node-version: "22" + - run: npm install + working-directory: src/packages/binaries + - run: npm test + working-directory: src/packages/binaries + - run: npm run test:e2e + working-directory: src/packages/binaries + - run: npm run build -- linux linux-arm64 win win-arm64 + working-directory: src/packages/binaries + - uses: actions/upload-artifact@v4 + with: + name: pptxdiff-linux + path: src/packages/binaries/pptxdiff-linux/pptxdiff-linux + if-no-files-found: error + - uses: actions/upload-artifact@v4 + with: + name: pptxdiff-linux-arm64 + path: src/packages/binaries/pptxdiff-linux/pptxdiff-linux-arm64 + if-no-files-found: error + - uses: actions/upload-artifact@v4 + with: + name: pptxdiff-win + path: src/packages/binaries/pptxdiff-win/pptxdiff-win.exe + if-no-files-found: error + - uses: actions/upload-artifact@v4 + with: + name: pptxdiff-win-arm64 + path: src/packages/binaries/pptxdiff-win/pptxdiff-win-arm64.exe + if-no-files-found: error + + # mac (both Intel x64 and Apple Silicon arm64) builds on a REAL macOS + # runner specifically so both can be ad-hoc codesigned (codesign only + # exists on macOS) — an unsigned mac binary is a real functional problem + # on Apple Silicon, not just a warning, so neither is cross-compiled from + # the linux job above. See build.mjs's header comment and + # docs/.scrolls/GAP_CONTEXT.md. GitHub's macos-latest runners are + # themselves Apple Silicon as of 2024, so the arm64 build here is a + # genuinely native build+sign, not a translated one. + build-mac: + runs-on: macos-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v5 + with: + node-version: "22" + - run: npm install + working-directory: src/packages/binaries + - run: npm test + working-directory: src/packages/binaries + - run: npm run test:e2e + working-directory: src/packages/binaries + - run: npm run build -- mac mac-arm64 + working-directory: src/packages/binaries + - uses: actions/upload-artifact@v4 + with: + name: pptxdiff-mac + path: src/packages/binaries/pptxdiff-mac/pptxdiff-mac + if-no-files-found: error + - uses: actions/upload-artifact@v4 + with: + name: pptxdiff-mac-arm64 + path: src/packages/binaries/pptxdiff-mac/pptxdiff-mac-arm64 + if-no-files-found: error diff --git a/.gitignore b/.gitignore index 4d55fa9..a5d792c 100644 --- a/.gitignore +++ b/.gitignore @@ -385,3 +385,22 @@ src/pptxdiff/docs-site/site/ ## Covers pptxdiff-cli, @pptxdiff/server, and any future src/packages/*/lib/. !src/packages/*/lib/ !src/packages/*/lib/** + +## Native-binary build output (src/packages/binaries/build.mjs, via +## @yao-pkg/pkg) — the single packaged executable per OS is generated, not +## source; keep each OS folder's own README.md and CHANGELOG.md tracked +## (they document the folder even before a build has ever run there) but +## ignore the built binary itself. build.mjs also writes a temp pkg config +## directly at the repo root (see WISDOM.md's pkg-config-colocation trap +## entry for why) and always removes it in a `finally` — ignored here too +## as a defensive backstop in case a build is interrupted mid-run. +src/packages/binaries/pptxdiff-win/* +src/packages/binaries/pptxdiff-mac/* +src/packages/binaries/pptxdiff-linux/* +!src/packages/binaries/pptxdiff-win/README.md +!src/packages/binaries/pptxdiff-mac/README.md +!src/packages/binaries/pptxdiff-linux/README.md +!src/packages/binaries/pptxdiff-win/CHANGELOG.md +!src/packages/binaries/pptxdiff-mac/CHANGELOG.md +!src/packages/binaries/pptxdiff-linux/CHANGELOG.md +/.pkg-binaries-config.*.json diff --git a/.gitignores/user.gitignore b/.gitignores/user.gitignore index d50e3a3..832da05 100644 --- a/.gitignores/user.gitignore +++ b/.gitignores/user.gitignore @@ -29,3 +29,22 @@ src/pptxdiff/docs-site/site/ ## Covers pptxdiff-cli, @pptxdiff/server, and any future src/packages/*/lib/. !src/packages/*/lib/ !src/packages/*/lib/** + +## Native-binary build output (src/packages/binaries/build.mjs, via +## @yao-pkg/pkg) — the single packaged executable per OS is generated, not +## source; keep each OS folder's own README.md and CHANGELOG.md tracked +## (they document the folder even before a build has ever run there) but +## ignore the built binary itself. build.mjs also writes a temp pkg config +## directly at the repo root (see WISDOM.md's pkg-config-colocation trap +## entry for why) and always removes it in a `finally` — ignored here too +## as a defensive backstop in case a build is interrupted mid-run. +src/packages/binaries/pptxdiff-win/* +src/packages/binaries/pptxdiff-mac/* +src/packages/binaries/pptxdiff-linux/* +!src/packages/binaries/pptxdiff-win/README.md +!src/packages/binaries/pptxdiff-mac/README.md +!src/packages/binaries/pptxdiff-linux/README.md +!src/packages/binaries/pptxdiff-win/CHANGELOG.md +!src/packages/binaries/pptxdiff-mac/CHANGELOG.md +!src/packages/binaries/pptxdiff-linux/CHANGELOG.md +/.pkg-binaries-config.*.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 84c4303..a800f84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 state to the tap even without a version-pin change, while a scheduled run still only does real work on an actual version bump. `test_formula.mjs` now also asserts `LICENSE` stays byte-identical to the repo root's copy, catching drift instead of silently shipping a stale license to the tap. +- New private `@pptxdiff/binaries` package (`src/packages/binaries/`) building standalone native `pptxdiff` executables for Windows, macOS, and Linux (x64 AND arm64 for all three) via `@yao-pkg/pkg` — download one file and run it, no separate Node.js install required. +- Native arm64 builds for all three OSes (`pptxdiff-mac-arm64`, `pptxdiff-win-arm64.exe`, `pptxdiff-linux-arm64`), each sharing its OS's output folder with the existing x64 build — avoids Apple Silicon Macs having to run the Intel binary via Rosetta 2 translation, and gives Windows-on-ARM/arm64-Linux users a native option too. +- Per-OS build output folders `src/packages/binaries/pptxdiff-{win,mac,linux}/`, each with its own `README.md` and `CHANGELOG.md`. +- `.github/workflows/binaries.yml`: `pkg` genuinely cross-compiles, so Windows and Linux (both chip variants) build together in one `ubuntu-latest` job; both macOS targets build in their own `macos-latest` job so they can be properly ad-hoc codesigned. +- `make pkg.binaries.build` / `npm run build:binary` for local builds (all six targets by default, or a specific subset). +- Red/Green TDD test suite for the build tooling itself: `npm test` (fast, pure — config/asset-drift/regression checks) and `npm run test:e2e` (slow, real — builds and runs the actual packaged binary over real HTTP) in `src/packages/binaries/`. ## [0.7.0] - 2026-08-02 diff --git a/Makefile b/Makefile index f20a91b..dcccba3 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,9 @@ VSCE_EXT_VERSION := $(shell node -p "require('$(VSCE_PKGJSON_PATH)').version") VSCE_EXT_NAME := $(shell node -p "require('$(VSCE_PKGJSON_PATH)').name") VSIX_EXT_PATH_LOCAL := dist/$(VSCE_EXT_NAME)-$(VSCE_EXT_VERSION).vsix +BINARIES_PKG_DIR_RELPATH := ./src/packages/binaries +BINARIES_PKG_DIR := $(abspath $(ROOT_DIR)/$(BINARIES_PKG_DIR_RELPATH)) + MKDOCS_YML_PATH := $(abspath $(ROOT_DIR)/$(MKDOCS_YML_RELPATH)) ####################### DETERMINE VSCODE EDITOR TYPE ########################### @@ -97,6 +100,7 @@ help: # pkg.build : Build the extension (creates a.vsix file). # pkg.publish : Publish the extension. # pkg.release : Build and Publish the extension. + # pkg.binaries.build: Build a standalone native pptxdiff executable for the current OS. # # vsce.open : Opens the VS Code Extension Management page for a Publisher. # vsce.token : Opens the Azure DevOps Page to Manage the Personal Access Token for VSCE. @@ -209,7 +213,16 @@ pkg.vsce.install.local: @echo -e "\n✨ Installing VS Code extension locally (from 'dist/' folder)... ⏳\n" @echo -e "\n✨ VS Code Type: $(VSCODE_CMD)" @$(VSCODE_CMD) --install-extension $(VSCE_PKG_DIR)/$(VSIX_EXT_PATH_LOCAL) --force - + +.PHONY: pkg.binaries.build +pkg.binaries.build: + @## npm run build:binary — builds a standalone native pptxdiff executable + @## for the CURRENT host OS only (Node SEA has no cross-compile mode); see + @## src/packages/binaries/README.md and .github/workflows/binaries.yml for + @## how all three (win/mac/linux) get built via a CI matrix. + @echo -e "\n✨ Building native pptxdiff binary for the current OS... ⏳\n" + @cd $(BINARIES_PKG_DIR) && npm install && npm run build + ############################## ..: COMMANDS vsce.*:.. ################################ diff --git a/docs/.scrolls/DOCS.md b/docs/.scrolls/DOCS.md index b1bd964..a550a35 100644 --- a/docs/.scrolls/DOCS.md +++ b/docs/.scrolls/DOCS.md @@ -286,3 +286,68 @@ PUBLIC-docs-site side of the same rule. index), so no FAQ change was needed; `docs/.scrolls/SPEC.md`/`GAP_ANALYSIS.md`/`GAP_CONTEXT.md` already fully cover the underlying feature from the sessions that built it — this session's scope was specifically the public docs site, per the explicit ask. +## 16. Content updates + new changelog subpage for standalone native binaries (`@pptxdiff/binaries`, added a later session) + +Follow-up to the `@pptxdiff/binaries` feature (SPEC.md §36 — six standalone executables, x64+arm64 +for Windows/macOS/Linux, via `@yao-pkg/pkg`) shipped across several turns of the same session — the +`.scrolls/` working-memory docs were updated as each piece landed, but the PUBLIC docs-site was not +touched at all until an explicit follow-up ("verify docs/.scrolls folder is updated and so is the +docs-site folder"). Confirmed via `git log --oneline master..HEAD -- src/pptxdiff/docs-site/` +returning zero commits before this entry — a real, not hypothetical, gap. + +- **`getting-started.md` gained "Option D — standalone binary (no Node.js at all)"**, between the + existing Option C (just the file) and "What happens on first load" — this page's whole job is + already "here are the ways to run pptxdiff," so a new install option belongs here, not a new + top-level page (see §14's own reasoning for the opposite call on `headless-cli-api.md`, which + documents a genuinely different tool with a different purpose — this is the SAME `bin/cli.js`, + just packaged differently). New `doc_coverage:` id `native-binaries` (`partial` — the page is + accurate, but the underlying feature has known gaps: unsigned/ad-hoc-signed, not on GitHub + Releases yet), anchored to the new section. +- **`cli.md` gained a one-paragraph cross-link** ("No Node.js at all?") pointing at the new Option D + — no new `doc_coverage:` id here, since `getting-started.md`'s new id already covers the feature + and this is supplementary framing on an already-covered page, not a second independent unit of + coverage. +- **`limitations.md` gained a new row** (`native-binaries-limitations`, `complete` — the limitation + itself is fully documented even though the underlying feature has real gaps, same convention §14 + established for `headless-cli-api-limitations`) and the existing "npm CLI opens a browser tab" row + was reworded to note the binaries share that same property (same `bin/cli.js`, not a native-window + wrapper) rather than reading as if the binaries somehow escaped it. +- **`index.md`'s "No install required" card** updated to mention the binaries option — a factual + correction (the card previously implied only two options existed), no coverage-registry impact + (the card doesn't carry its own `doc_coverage:` entry, `index.md`'s existing ids are unaffected). +- **New changelog subpage `changelogs/pptxdiff-binaries.md`**, added to nav under "NPM Package(s)" + (same category `pptxdiff-cli`/`@pptxdiff/server` already live in, despite also being `private: + true` unpublished packages — the nav grouping in this site means "package.json-defined sibling in + this repo," not "published to the npm registry") and to `changelog.md`'s index. **Structurally + different from every other changelog subpage**: `@pptxdiff/binaries` has no single package-level + `CHANGELOG.md` (each OS's binary has its own, since they're independent downloadable artifacts — + see `SPEC.md` §36/`GAP_CONTEXT.md`) — so this page transcludes all THREE + (`src/packages/binaries/pptxdiff-{win,mac,linux}/CHANGELOG.md`) under their own `## Windows` / + `## macOS` / `## Linux` subheadings, rather than one `--8<--` include like every other subpage. +- **A real `mkdocs build --strict` failure found and fixed, not just described**: the three per-OS + `CHANGELOG.md` files each linked back to the root `CHANGELOG.md` via a relative path + (`../../../../CHANGELOG.md`) that's correct when the file is read on GitHub (four levels up from + `src/packages/binaries/pptxdiff-win/`) but WRONG once transcluded verbatim into + `docs-site/docs/changelogs/pptxdiff-binaries.md` — `pymdownx.snippets` is a textual include, it + does not rewrite relative links to account for where the content ends up, so the link resolved + against the WRONG base and `mkdocs build --strict`'s link checker correctly flagged it (twice — the + same relative link appears in the transcluded content of two other, unrelated warnings the build + also printed for the same reason). Fixed at the SOURCE (`src/packages/binaries/pptxdiff-{win,mac, + linux}/CHANGELOG.md` themselves, since the docs-site page transcludes them verbatim) by swapping + the relative link for the same absolute GitHub URL pattern every other subpage's own "Source:" + line already uses — the fix had to happen in the package-level files, not the docs-site page, since + the page has no content of its own to fix. +- **`scripts/coverage_registry.yml` gained two new ids** (`native-binaries` under `features:`, + `native-binaries-limitations` under `limitations:`), each added in the same change as the + page/row that declares them via `doc_coverage:` front matter, per this file's own §8/§14 rule — + `sync_doc_coverage.py --write` then `--check` re-run to confirm 36 complete + 4 partial + 0 missing + (of 40; was 34 complete + 1 partial before this session's headless-CLI-API work, then presumably + higher still after intervening sessions not otherwise noted here). +- **Verified for real**: `mkdocs build --strict` clean after the link fix (one remaining, expected, + harmless warning — `git-revision-date-localized-plugin` complaining the brand-new, not-yet-committed + `pptxdiff-binaries.md` has no git history yet; resolves itself once committed, not a structural + issue). Directly grepped the built HTML (`getting-started/index.html`, `cli/index.html`, + `limitations/index.html`) to confirm the new anchor (`option-e-standalone-binary-no-nodejs-at-all`) + matches EXACTLY between the page that defines it and the two pages that link to it, rather than + trusting that `--strict` alone would have caught a mismatched anchor (MkDocs's built-in link + checker validates that a linked FILE exists, not that a `#fragment` inside it does). diff --git a/docs/.scrolls/GAP_ANALYSIS.md b/docs/.scrolls/GAP_ANALYSIS.md index 7f88cef..7f370b2 100644 --- a/docs/.scrolls/GAP_ANALYSIS.md +++ b/docs/.scrolls/GAP_ANALYSIS.md @@ -163,6 +163,15 @@ Concrete, testable gaps between what SPEC.md describes and a fully "real" implem - [ ] **No MCP server** (`pptxdiff-cli mcp`, CLI_API_DESIGN.md §9) — the more idiomatic AI-agent integration point than shelling out to the CLI or hand-rolling HTTP calls. Not started; `--json` output + the API's request/response shapes are the current AI-agent integration surface. - [ ] **Not published to npm.** Both packages depend on their monorepo siblings via `file:` protocol (documented in each README) rather than a real published semver range — intentional for local development, but a real blocker before either package can actually be `npm install`ed by anyone outside this repo. +## Standalone native binaries (this session, switched from Node SEA to `@yao-pkg/pkg` mid-session) +- [ ] **Windows `.exe` is unsigned; macOS binary is ad-hoc signed only (no Developer ID).** No code-signing certificate exists for this project — Windows SmartScreen and macOS Gatekeeper will both warn on a freshly-downloaded copy. Documented per-OS in `src/packages/binaries/pptxdiff-{win,mac}/README.md`; a real fix needs a paid cert (Apple Developer ID + Windows Authenticode), not a code change. +- [ ] **macOS is not cross-compiled** — `pkg` genuinely can cross-compile a macOS binary from Linux, but the result can't be codesigned there (`codesign` is macOS-only), and a completely unsigned binary may not even launch on Apple Silicon. `.github/workflows/binaries.yml` therefore still runs macOS on a real `macos-latest` runner (`build-mac`), separate from the `build-linux-win` job that genuinely does cross-compile both those targets from one Linux host. This is a real, load-bearing constraint, not leftover caution from the SEA-based version. +- [ ] **Not yet attached to GitHub Releases.** The CI workflow uploads each OS's binary as a workflow artifact (downloadable from the Actions run page) but nothing wires a release-tag push to attach them to an actual GitHub Release yet — a real, small follow-up (e.g. `softprops/action-gh-release` on `release: types: [published]`), not attempted this session. +- [x] **macOS native arm64 build** — CLOSED: `pptxdiff-mac-arm64` (`node22-macos-arm64`) added after an explicit follow-up question, sharing `pptxdiff-mac/` with the existing Intel `pptxdiff-mac`. Structurally verified in this sandbox (real `Mach-O 64-bit arm64 executable` via `file`); actual signed/run verification only happens on CI's `macos-latest` runner (itself Apple Silicon as of 2024). +- [x] **Windows and Linux native arm64 builds** — CLOSED: `pptxdiff-win-arm64.exe` (`node22-win-arm64`) and `pptxdiff-linux-arm64` (`node22-linux-arm64`) added after an explicit follow-up question ("can we support arm64 for windows and linux as well?"), sharing their OS's existing folder. Both genuinely cross-compiled from this x64 Linux sandbox (unlike macOS) — required discovering and adding `pkg`'s `--fallback-to-source` flag, since cross-ARCH V8 bytecode generation fails outright without QEMU/binfmt emulation (confirmed via a real exec-format error before the fix). Structurally verified via the real production `buildOne()` path (real `ELF ... ARM aarch64` / `PE32+ ... Aarch64` executables via `file`, landing in the correct SHARED output folder without disturbing tracked docs); not run (no arm64 emulation available in this sandbox for either). +- [ ] **`ldid`-based Linux-side ad-hoc signing for macOS binaries was not pursued.** `pkg` itself suggests installing the `ldid` utility so a Linux host could ad-hoc-sign a Mach-O binary without any real Mac at all, which would let `build-mac`'s two targets fold into the `build-linux-win` job (all six targets cross-compiled + signed from one Linux runner instead of two CI jobs). Not attempted — a real `macos-latest` CI runner was judged more reliable than depending on a third-party signing tool this project has no experience with; worth revisiting if CI cost/time ever becomes a real concern. +- [ ] **macOS/Windows builds are unverified end-to-end in this sandbox** — only the Linux x64 build was actually RUN (built, launched, served `index.html`/`support.js`/`vendor/*` via real HTTP requests) — under both the original SEA mechanism and, again, after switching to `@yao-pkg/pkg`. The other five targets (win x64/arm64, mac x64/arm64, linux-arm64) were all structurally produced here (real, correctly-typed executables, verified via `file`) but none of them RUN — no Windows/macOS host in this sandbox, and no arm64 execution emulation available for the arm64 targets. CI's `build-linux-win`/`build-mac` jobs are what actually execute and verify each of the other five for the first time. + ## Content checksum (this session) - [ ] **The content checksum shows "unavailable" under the plain `file://` open path.** `crypto.subtle` (native Web Crypto, no new dependency) requires a secure context — guaranteed under the CLI's `http://localhost` default, not guaranteed under this project's other documented launch path (`git clone` + open `index.html` directly). Handled honestly (checked once at boot via `cryptoSubtleAvailable`, shown as "unavailable (requires a secure context)" rather than a wrong/fabricated hash or a permanently-stuck "computing…"), but not worked around — would need either a pure-JS SHA-256 fallback (a real new dependency, or a hand-rolled implementation neither asked for nor free of its own correctness risk) or accepting the gap under that one launch path. - [ ] **The checksum's excluded-parts list (`docProps/core.xml`, `docProps/app.xml`, `docProps/thumbnail.*`) is a fixed, hardcoded set, not user-configurable.** If a future real-world `.pptx` turns out to carry other non-content, save-time-varying metadata this project hasn't seen yet, it would need a code change to exclude, not a setting. Not speculatively generalized ahead of an actual observed case. diff --git a/docs/.scrolls/GAP_CONTEXT.md b/docs/.scrolls/GAP_CONTEXT.md index 1d2ec44..6c8219f 100644 --- a/docs/.scrolls/GAP_CONTEXT.md +++ b/docs/.scrolls/GAP_CONTEXT.md @@ -163,6 +163,41 @@ Three options existed: (1) npm workspaces at the repo root, (2) a real published ## Why @pptxdiff/server's file upload wire format is base64-in-JSON, not multipart/form-data `multipart/form-data` is the more standard shape for file uploads, but parsing it correctly (boundary handling, streaming, encoding edge cases) either needs a new dependency or a genuinely nontrivial hand-rolled parser — neither justified for a Phase 1 whose goal was proving the endpoint surface and automation wiring, not building production-grade upload handling. Base64-in-JSON needs zero new code beyond `Buffer.from(content, 'base64')` and is fully consistent with the same `node:http`-only, no-framework style `bin/cli.js` already established. Documented as a real, named gap (GAP_ANALYSIS.md) rather than left undiscoverable — a future session should build multipart support when large-file efficiency actually matters, not before. +## Why the native binaries are standalone executables, not real OS installers +The user's own task description asked to place "downloadable installers" under `src/packages/binaries/pptxdiff-{win,mac,linux}` and explicitly asked whether the folder should be called `binaries` or `installers` — a genuine fork with materially different cost, not a naming bikeshed. Two options existed: (1) real OS-native installers (`.msi`/signed `.exe` wizard on Windows, `.pkg`/`.dmg` on macOS, `.deb`/`.rpm`/AppImage on Linux) that register the CLI on PATH and appear in the system's app list, needing a paid code-signing certificate (Apple Developer ID, Windows Authenticode) to avoid OS security warnings; (2) standalone single-file-ish executables (Node SEA) that a user downloads and runs directly, no install step, unsigned/ad-hoc-signed. Asked directly, the user picked (2). This is also the option consistent with a PRIOR explicit decision already on record (see "Why the npm CLI opens a browser tab instead of a real native window" below): that session was asked Electron vs. Tauri vs. CLI+browser specifically to avoid installer/signing overhead, and picked CLI+browser for exactly that reason. Building real signed installers now would have silently reversed that reasoning without a corresponding explicit ask to do so — per WISDOM.md's rule ("a new user ask directly contradicting a prior decision is the current truth"), the fact that THIS ask was answered "standalone binaries," not "real installers," means the prior decision's reasoning still holds and wasn't overridden. Folder name `binaries` (already what the user's own task description used) is the accurate name for what's actually produced. + +## Why the folder structure keeps build output out of git except a README per OS folder +`src/packages/binaries/pptxdiff-{win,mac,linux}/` are build-artifact directories (the actual `.exe`/binary/`assets/`/`.zip`, all generated by `build.mjs`), not source — same category as `dist/` (npm pack output) or `pptxdiff-vscode/dist/` (`.vsix` output), both already gitignored in this repo. Following that existing precedent rather than inventing a new one: gitignore everything build.mjs writes, but keep one tracked `README.md` per OS folder so the folder structure the user asked for (and a description of what will appear there) exists in a fresh clone even before anyone has run a build. + +## Why the binaries reuse `bin/cli.js`'s `startServer()` instead of a separate server implementation — SUPERSEDED, see below (still true, mechanism changed) +This project already has a documented trap for exactly this mistake: `pptxdiff-vscode/extension.js` once grew its OWN independent copy of `bin/cli.js`'s static file server, which silently fell out of sync when the real one was security-hardened (see WISDOM.md's trap entry and GAP_ANALYSIS.md's "Security hardening" section). `startServer()` was already designed to be reusable (exported specifically so `pptxdiff-cli`'s automation layer could reuse it) — under the original Node-SEA-based version, it needed one small addition, an optional `root` parameter, so the packaged binary could point it at an `assets/` folder next to the executable instead of the npm package's own `src/pptxdiff` directory. Reusing `startServer()` at all (rather than writing a third copy) is still exactly right — see the entry below for why the `root` parameter itself turned out to be avoidable entirely once the mechanism changed. + +## Why the binary resolves its assets relative to `process.execPath`, not embedded via SEA's asset store — SUPERSEDED, see below +Node SEA does support embedding arbitrary binary assets directly into the executable (retrieved at runtime via `require('node:sea').getAsset()`), which would give a genuinely single-file artifact with no separate `assets/` folder to keep alongside it. That was considered and deliberately not used for the first (SEA-based) pass: `bin/cli.js`'s static server already does ordinary `fs.readFile()` against a directory — reusing it as-is (only changing what `root` points to) meant zero changes to the actual file-serving logic, versus rewriting it to read from `sea.getAsset()` instead. The shipped shape at the time — binary + `assets/` folder, zipped together — was judged good enough; true single-file embedding was flagged as a reasonable follow-up. The entry below explains what actually superseded this. + +## Why the switch from Node SEA to `@yao-pkg/pkg` (and why `bin/cli.js` needed zero changes afterward) +Explicit follow-up question: "Why aren't you using the npm library yao-pkg/pkg?" Honest answer at the time: SEA was reached for by default (a Node core feature, no added third-party build-tool dependency, consistent with this project's general dependency-conservatism — see the vendoring entries elsewhere in this file) without seriously evaluating `pkg`/`@yao-pkg/pkg` first. Once actually compared, two of `pkg`'s properties were concretely better for this feature's own stated goals, not just "different": +1. **Real cross-compilation.** Verified directly in this project's own dev sandbox (Linux): `pkg -t node22-win-x64 ...` produced a genuine `PE32+ executable ... for MS Windows`, and `-t node22-macos-x64` produced a genuine `Mach-O 64-bit x86_64 executable` — both from one Linux host, `pkg`'s own fetched base-binary cache as the proof (`/root/.pkg-cache/v3.6/fetched-v22.23.2-{win,macos}-x64`), not assumed from documentation. SEA cannot do this at all (it injects into a copy of the CURRENTLY RUNNING node binary, full stop) — the original 3-OS CI matrix existed purely to work around that; `pkg` collapses it to two jobs (see below for why not one). +2. **Built-in asset embedding that needed ZERO `bin/cli.js` changes**, discovered empirically, not assumed: `pkg`'s snapshot filesystem preserves the real project's relative directory structure at runtime (confirmed by walking `/snapshot` inside a real built-and-run test binary). Pointing `pkg` directly at the UNMODIFIED `bin/cli.js` as the entry, with `src/pptxdiff/**` listed as `pkg` "assets," made `bin/cli.js`'s own pre-existing `ROOT = path.join(__dirname, "..", "src", "pptxdiff")` resolve correctly with no packaging-specific code at all — reverting the `root` parameter SEA had required, back to the exact `startServer()` this project shipped before this whole feature existed (confirmed byte-identical via `git show`). + +The honest tradeoff accepted: one more devDependency (`@yao-pkg/pkg`, dev-time only — never shipped in the binaries or the npm package), and it's a community fork of a project (`vercel/pkg`) its original maintainer archived rather than an org-backed package. Judged worth it because the two wins above directly serve THIS feature's own goals (fewer CI jobs, a genuinely single-file artifact) in a way SEA structurally cannot, not because "more modern tool" is inherently better — SEA remains a perfectly reasonable choice for a project that weighs the zero-added-dependency property more heavily than these two wins. + +**A hard-won gotcha found DURING this switch, not from documentation**: `pkg`'s `"assets"` glob paths in a config file resolve relative to wherever THAT CONFIG FILE ITSELF lives, not cwd, not the entry file's directory — confirmed by a controlled A/B test (same relative glob, config file moved to a different directory, assets silently stopped embedding with zero error/warning either time). This is why `build.mjs`'s temp pkg config is written directly at `REPO_ROOT` (next to the real `package.json`) rather than kept as a normal file inside `src/packages/binaries/` itself — full write-up in WISDOM.md's new trap entry, since a build tool silently producing an asset-less binary with no error message is exactly the kind of failure mode worth flagging loudly for future sessions. + +## Why macOS is still built on its own CI runner instead of also being cross-compiled from the Linux job +`pkg` genuinely CAN produce a macOS binary from Linux (verified — see above), so "just cross-compile all three from one job" was the first instinct. Rejected after considering what codesigning actually requires: `codesign` only exists on macOS, so a Linux-built mac binary can never be even ad-hoc signed, and on Apple Silicon specifically, AMFI (Apple Mobile File Integrity) requires AT LEAST an ad-hoc signature for an arm64 executable to launch at all — this isn't "a Gatekeeper warning users can click through" the way it is on Intel Macs, it can mean the binary refuses to run, full stop. Shipping a binary that might not even launch on the now-dominant Apple Silicon Macs would be a real regression from the original SEA-based version (which DID build mac on an actual `macos-latest` runner and could ad-hoc sign it). `.github/workflows/binaries.yml` keeps macOS on its own `macos-latest` job specifically so `codesign --sign -` runs for real; `build.mjs`'s `buildOne()` warns loudly (not silently) if the mac target is ever built off of a non-darwin host, rather than producing something that looks fine at build time and fails mysteriously at launch time. `pkg`'s own error output independently confirmed this reasoning is correct, not just this project's own assumption: attempting `pkg -t node22-macos-arm64` from Linux prints "Due to the mandatory code signing requirement... it will be immediately killed by kernel on launch. An ad-hoc signature is sufficient" verbatim — Apple's own enforcement, described by a tool with no stake in this project's specific decisions. + +## Why macOS got a native arm64 build but Windows/Linux didn't (yet) — SUPERSEDED, see below +Explicit follow-up question: "Does the mac binary work for Apple Silicon MacBooks?" Honest answer at the time: yes, but only via Rosetta 2 translation, since only the `node22-macos-x64` target had been built — a real, asked-about gap, not a hypothetical one. `pkg` supports `node22-win-arm64`/`node22-linux-arm64` equally well, so in principle all three OSes could get a native arm64 build — but only macOS's was added AT THE TIME, for two concrete reasons specific to that platform, not "arm64 in general": (1) it was the one actually asked about; (2) macOS is the platform where running the "wrong" chip's binary has a REAL functional cost for this app's audience — Apple Silicon is the dominant Mac chip as of 2026, Rosetta 2 isn't guaranteed pre-installed on a fresh machine, and there's real translation overhead on every launch. Windows-on-ARM/Linux-on-ARM were judged comparatively rare enough not to scope in without being asked — same "respect the literal scope of the ask" principle WISDOM.md documents for confirmation-gate scoping. The very next follow-up question directly asked for them anyway ("can we support arm64 for windows and linux as well?") — see the entry below for what shipped and what that surfaced. +- **Mechanically**: `TARGET_MAP` entries gained an `outDirKey` field, separate from the map's own key, specifically so `mac` and `mac-arm64` (two different pkg targets, two different `binName`s) can share ONE output folder (`pptxdiff-mac/`) — from a user's perspective picking a download, "which folder do I look in" should be answered by OS, and "which specific file do I download" by chip, not two different folders for the same OS. `buildOne()` was changed to compute `outDir` from `target.outDirKey` rather than the `osKey` argument it's called with, with a dedicated regression test (`test_build_config.mjs`) protecting exactly that computation, demonstrated RED→GREEN for real. +- **Why not use `ldid` to also fold macOS into the cross-compiled Linux job**: `pkg`'s own error output (see the entry above) explicitly suggests installing `ldid` — a tool that can produce ad-hoc-equivalent Mach-O signatures from a non-macOS host — as an alternative to building on a real Mac. Considered but not pursued: this project has zero prior experience with `ldid`'s reliability/compatibility characteristics, and a real `macos-latest` GitHub Actions runner (using Apple's own `codesign`, the same tool a real Mac uses) is unambiguously the more trustworthy signing path for a first implementation. Worth revisiting explicitly if CI job count/time ever becomes a real constraint — flagged as a named, deliberate non-choice in GAP_ANALYSIS.md rather than silently never considered. (This reasoning still stands — unaffected by the Windows/Linux arm64 addition below, which has nothing to do with macOS signing.) + +## Why Windows/Linux arm64 was added after all, and what it took to actually work +The premise of the entry above ("Windows-on-ARM/Linux-on-ARM are comparatively rare, not asked about") was accurate for its moment but stopped applying the instant the user asked directly — per WISDOM.md's standing rule that a new explicit ask supersedes a prior scope decision rather than needing to be argued with. Unlike the mac case, there was no codesigning blocker to work around: `pkg` genuinely cross-compiles both `node22-win-arm64` and `node22-linux-arm64` from an x64 host with no signing step needed at all for either OS, so both fold directly into the existing `build-linux-win` CI job rather than needing a new one. +- **A real, previously-unencountered build failure was found and fixed, not just "it worked because pkg supports arm64 targets."** The very first attempt (`pkg -t node22-linux-arm64 ...`) failed with a genuine `ERR_ASSERTION`; with `--debug`, the actual cause was a `Syntax error: ")" unexpected` coming from `/root/.pkg-cache/.../fetched-v22.23.2-linuxstatic-arm64` — `pkg` needs to EXECUTE a matching-arch "fabricator" helper binary to generate V8 bytecode for the entry script, and running a foreign-arch ELF directly on this x64 sandbox (no QEMU/binfmt emulation registered — confirmed via `which qemu-aarch64`/`/proc/sys/fs/binfmt_misc` both coming up empty) makes the shell try to interpret the binary's raw bytes as a script, producing exactly that syntax error. `pkg`'s own warning pointed at the fix: `--fallback-to-source`, which ships the entry as plain JS instead of failing when bytecode generation isn't possible. Verified the fix actually works (not just that the error message went away) by checking the resulting binary's `file` output and comparing its size to the known-good x64 build (72-75MB either way, consistent with real assets actually being embedded, not a stripped-down failure artifact). +- **Why `--fallback-to-source` unconditionally, not just for arm64 targets**: it's a no-op for a same-arch build (bytecode generation succeeds normally there, so the flag is never invoked) — conditionally applying it only for `*-arm64` targets would add a branch to protect against a failure mode that doesn't exist for the other case, for no behavioral difference. Simpler to always pass it and let `pkg` decide per-build whether it's needed. +- **Why this wasn't cross-verified by actually RUNNING the arm64 binaries**: this sandbox is x64 with no arm64 execution emulation available (confirmed, not assumed — see above). The structural verification performed (a real build via the actual production `buildOne()` path with the real asset config, `file` confirming the correct architecture/format, confirming the binary size is consistent with assets actually being embedded, confirming it landed in the correct shared output folder without disturbing tracked docs) is the strongest verification achievable in this environment; CI's `build-linux-win` job is what will actually execute these two targets for the first time, the same honest gap already true of the Windows x64 binary before any of this session's work. + ## Why @pptxdiff/server ships with no authentication rather than a minimal API key CLI_API_DESIGN.md §8 calls for API-key-required-on-non-loopback-bind as part of the design, but implementing even a minimal key check touches real security-sensitive surface (where the key comes from, how it's compared, timing-attack considerations) that deserves its own deliberate pass rather than being bolted on inside a Phase-1 session already covering three other new pieces (automation shim, CLI, server routing). The loopback-by-default bind (matching `bin/cli.js`'s existing precedent) is the one security property that WAS carried over faithfully; the auth gap is real, named explicitly in the package's own README (not just a scroll only this project's own sessions read), and is the literal next thing to build before anyone binds this server to a non-loopback host in practice. diff --git a/docs/.scrolls/HANDOFF.md b/docs/.scrolls/HANDOFF.md index 4b48cfd..e053013 100644 --- a/docs/.scrolls/HANDOFF.md +++ b/docs/.scrolls/HANDOFF.md @@ -2,6 +2,68 @@ **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-05 — docs-site update for the binaries feature, a real gap closed) +- Explicit ask: "verify docs/.scrolls folder is updated and so is the docs-site folder, apart from the README/CHANGELOG file(s)." Checked rather than assumed: `docs/.scrolls/` was confirmed fully updated across all four `@pptxdiff/binaries` commits so far, but `git log --oneline master..HEAD -- src/pptxdiff/docs-site/` came back with **zero commits** — the public MkDocs site had never been touched by any of this session's binaries work. A real gap, not a formality. +- Read `docs/.scrolls/DOCS.md` first, per its own standing rule (anything beyond a routine single-page content edit gets checked against it) — this was more than routine (new page, new nav entry, new coverage-registry ids). +- **`getting-started.md`** gained "Option D — standalone binary (no Node.js at all)," landing between the existing Option C and "What happens on first load" (this page's whole job is already "here are the ways to run pptxdiff"). New `doc_coverage:` id `native-binaries` (`partial`). +- **`cli.md`** gained a one-line cross-link to Option D; **`index.md`**'s "No install required" card updated to mention it; **`limitations.md`** gained a new `native-binaries-limitations` row (`complete`) and had its existing "npm CLI opens a browser tab" row reworded to note the binaries share that property rather than reading as an exception. +- **New changelog subpage `changelogs/pptxdiff-binaries.md`**, added to nav — structurally different from every sibling subpage since `@pptxdiff/binaries` has no single package-level `CHANGELOG.md` (each OS has its own); this page transcludes all three under `## Windows`/`## macOS`/`## Linux` headings instead of one `--8<--` include. +- **Real `mkdocs build --strict` failure found and fixed, not just described**: the three per-OS `CHANGELOG.md` files' relative link back to the root `CHANGELOG.md` (`../../../../CHANGELOG.md`, correct on GitHub) broke once transcluded into the new subpage — `pymdownx.snippets` doesn't rewrite relative links for their new location. Fixed at the source (the package-level files themselves, since the docs-site page has no content of its own) by swapping to the same absolute GitHub URL pattern every other subpage's "Source:" line already uses. +- **`scripts/coverage_registry.yml`** gained `native-binaries`/`native-binaries-limitations`; `sync_doc_coverage.py --write` then `--check` re-run, confirmed 36 complete + 4 partial + 0 missing (of 40). +- **Verified for real**: `mkdocs build --strict` clean (one remaining, expected, harmless warning about the brand-new file having no git history yet — resolves on commit). Directly grepped the built HTML to confirm the new anchor (`option-e-standalone-binary-no-nodejs-at-all`) matches exactly between the defining page and both linking pages, rather than trusting `--strict` alone (it validates linked files exist, not that `#fragment`s inside them do). +- Full technical writeup: `docs/.scrolls/DOCS.md` §15 (new). `docs-site/CHANGELOG.md` gained a matching dated entry per the project's own house rule. + +## Update (2026-08-05 — binaries: native Windows/Linux arm64 builds, `pptxdiff-win-arm64.exe`/`pptxdiff-linux-arm64`) +- Direct, immediate follow-up to the mac-only arm64 addition below: "Can we support arm64 for windows and linux as well?" Unlike macOS, neither Windows nor Linux needs a signing step, so no new CI job was needed — both fold straight into the existing `build-linux-win` job. +- **Verified `pkg` genuinely supports both targets first, hands-on, before implementing** (same rigor as every other step in this feature): the FIRST attempt (`pkg -t node22-linux-arm64 bin/cli.js ...`) failed outright with `ERR_ASSERTION`. `--debug` traced the real cause to a genuine exec-format failure: `pkg` needs to run a matching-arch "fabricator" helper binary to generate V8 bytecode for the entry script, and this x64 sandbox has no QEMU/binfmt arm64 emulation registered (confirmed empty, not assumed: `which qemu-aarch64` and `/proc/sys/fs/binfmt_misc` both came up empty) — the shell tried to interpret the foreign-arch ELF's raw bytes as a script, producing a `Syntax error`. `pkg`'s own warning named the fix: `--fallback-to-source` (ships the entry as plain JS instead of failing when bytecode generation isn't possible for the target arch) — added unconditionally to `buildOne()`'s pkg invocation (harmless no-op for same-arch builds, where bytecode generation just succeeds normally). +- **Confirmed the fix actually works, not just that the error message went away**: rebuilt both targets via the real production `buildOne()` path (real asset config, not a bare entry-only smoke test), confirmed via `file` genuine `ELF 64-bit LSB executable, ARM aarch64` and `PE32+ executable ... Aarch64` outputs, and — since this sandbox can't execute either binary (no arm64 emulation) — cross-checked binary SIZE against the known-good x64 builds (72-75MB either way) as evidence real assets are actually embedded, not a truncated/failed artifact. +- **`TARGET_MAP` gained `linux-arm64`/`win-arm64` entries**, each sharing its OS's existing `outDirKey` (same mechanism the mac-arm64 addition established) — `.github/workflows/binaries.yml`'s `build-linux-win` job now builds and uploads all four Windows/Linux target/arch combinations. `test_build_e2e.mjs`'s host-detection generalized from "only darwin checks arch" to checking `os.arch()` uniformly across all three platforms, so a future arm64 Linux/Windows CI runner would also genuinely test its native target. +- **Genuine RED→GREEN demonstrated on the new `--fallback-to-source` guard**: temporarily removed the flag, confirmed the dedicated regression test caught it (28/29 — this is exactly the kind of regression that would silently break every arm64 target while every x64 test kept passing, hence a dedicated static check rather than trusting "the tests still pass"), restored it, confirmed 29/29. `test_build_config.mjs` now 29 assertions (was 23). +- Scrolls updated to match: SPEC.md §36, PLAN.md (new "Done this session" block, prior "not attempted" ticket closed), GAP_ANALYSIS.md (arm64 gap for win/linux closed, the "unverified end-to-end" gap widened to name all five non-run targets honestly), GAP_CONTEXT.md (prior entry marked superseded, new entry with the full exec-format-failure reproduction), per-OS win/linux `README.md`/`CHANGELOG.md`, top-level `src/packages/binaries/README.md`, root `CHANGELOG.md`. + +## Update (2026-08-05 — binaries: native Apple Silicon build, `pptxdiff-mac-arm64`) +- Direct follow-up question on the same-day `@yao-pkg/pkg` switch below: "Does the mac binary work for Apple Silicon MacBooks?" Honest answer given first: yes, but only via Rosetta 2 translation — only the `node22-macos-x64` target existed. Asked whether to add a native arm64 build (real effort: another CI target, another README/CHANGELOG update) via `AskUserQuestion` before doing the work; user said yes. +- **`TARGET_MAP` gained an `outDirKey` field**, separate from the map's own key, so `mac` (Intel) and the new `mac-arm64` (Apple Silicon, `node22-macos-arm64`) can share ONE output folder (`pptxdiff-mac/`) while keeping distinct `binName`s — from a user's download perspective, "which folder" should be answered by OS, "which file" by chip, not two folders for the same OS. `buildOne()` now computes `outDir` from `target.outDirKey`, not the `osKey` it's called with. +- **`.github/workflows/binaries.yml`'s `build-mac` job now builds both mac targets** (`npm run build -- mac mac-arm64`), uploading each as its own artifact. `test_build_e2e.mjs` now picks `mac` vs `mac-arm64` based on the host's actual `os.arch()` (not just `process.platform`), so GitHub's `macos-latest` runners — themselves Apple Silicon as of 2024 — genuinely exercise the native arm64 build in CI, not the Intel one. +- **Verified for real in this sandbox (Linux)**: ran `node build.mjs mac-arm64` directly, confirmed via `file` a genuine `Mach-O 64-bit arm64 executable` (unsigned, as expected off a non-macOS host), confirmed it landed correctly in the SHARED `pptxdiff-mac/` folder without disturbing the tracked `README.md`/`CHANGELOG.md` already there, then cleaned it up (can't be run/signed/verified over HTTP from Linux — that's CI's job). `pkg` itself independently printed the exact Apple-Silicon-signing warning ("Due to the mandatory code signing requirement... it will be immediately killed by kernel on launch") this project's own reasoning already relied on — real, external confirmation, not just this project's own assumption. +- **Genuine RED→GREEN demonstrated on the new `outDirKey` guard**: temporarily reverted `buildOne()`'s `outDir` computation back to using the `osKey` argument, confirmed the dedicated regression test caught it (22/23), restored it, confirmed 23/23 — `test_build_config.mjs` now 23 assertions total (was 18). +- **Windows/Linux deliberately stay x64-only** — not asked about, and arm64 desktop usage is a much smaller fraction of this app's likely audience for those two OSes than Apple Silicon is of the Mac audience. `pkg` supports `node22-win-arm64`/`node22-linux-arm64` equally well if this is ever revisited. +- **`ldid`-based Linux-side signing considered, not pursued**: `pkg`'s own error output suggests it as an alternative to needing a real Mac at all (would let `build-mac` fold into the cross-compiled `build-linux-win` job) — flagged as a real, deliberate non-choice in GAP_ANALYSIS.md rather than silently never considered; a real `macos-latest` runner using Apple's own `codesign` was judged more trustworthy for a first pass. +- Scrolls updated to match: SPEC.md §36, PLAN.md (new "Done this session" block + 2 new tickets), GAP_ANALYSIS.md (arm64 gap closed for mac, new ldid/win-linux-arm64 tickets), GAP_CONTEXT.md (two new entries — why mac got native arm64 but win/linux didn't, why `ldid` wasn't pursued), per-OS mac `README.md`/`CHANGELOG.md`, top-level `src/packages/binaries/README.md`, root `CHANGELOG.md`. + +## Update (2026-08-05 — binaries: switched from Node SEA to `@yao-pkg/pkg`) +- Direct follow-up question on the same-day binaries work below: "Why aren't you using the npm library yao-pkg/pkg?" Honest answer given first: SEA was reached for by default (Node core feature, no added third-party build-tool dependency) without actually evaluating `pkg` first — then investigated hands-on rather than defending the choice abstractly. +- **Verified two real advantages in this sandbox, not just cited from memory**: (1) genuine cross-compilation — built a real Windows `.exe` (`file` confirmed `PE32+ executable ... for MS Windows`) and a real macOS binary (`Mach-O 64-bit x86_64 executable`) FROM THIS LINUX SANDBOX, something Node SEA cannot do at all; (2) built-in asset embedding needing zero `bin/cli.js` changes — `pkg`'s snapshot filesystem preserves the real project's relative directory layout, so pointing `pkg` directly at the UNMODIFIED `bin/cli.js` with `src/pptxdiff/**` as `pkg` assets made the existing `ROOT = path.join(__dirname, "..", "src", "pptxdiff")` just resolve correctly. Asked the user to confirm via `AskUserQuestion` before doing the rework (real effort, real new dependency) rather than just switching unilaterally; user picked switch. +- **`bin/cli.js` fully reverted** — the SEA-era `startServer(root = ROOT)` optional parameter is gone; `startServer()` is back to taking no arguments, confirmed byte-identical to the pre-binaries-feature version via `git show ee9bec7~1:bin/cli.js | diff -`. `sea-entry.cjs` deleted entirely (no longer needed — `pkg` points straight at the real `bin/cli.js`). +- **Real, silently-failing gotcha found DURING the switch** (not from docs): `pkg`'s `"assets"` glob paths in a config file resolve relative to WHEREVER THE CONFIG FILE ITSELF LIVES — not cwd, not the entry file's directory. Confirmed via a controlled A/B test (same relative glob string, config file moved between directories) — assets silently stopped embedding with zero error or warning either time, only showing up as 404s when the built binary was actually run. Fixed by having `buildOne()` write its temp pkg config directly at `REPO_ROOT` (next to the real `package.json`, where `src/pptxdiff/**` actually resolves), removed in a `finally` block. Full reproduction recorded in WISDOM.md's new trap entry — this is exactly the kind of failure a future session could easily reintroduce by "simplifying" the config placement. +- **`build.mjs` rewritten** around `pkg`'s programmatic `exec()` API: `buildOne(osKey, target)` (single target) and `buildAll(osKeys)` (defaults to all three, used by local `npm run build`). Output is now a genuine single file per OS — no more `assets/` folder, no more zip wrapper (the whole point of switching). +- **macOS deliberately NOT cross-compiled**, even though `pkg` technically can produce a mac binary from Linux: `codesign` only exists on macOS, and on Apple Silicon a completely unsigned binary may not even launch (AMFI requires at least an ad-hoc signature — not just a Gatekeeper warning the way Intel Macs work). `buildOne()` only codesigns when `process.platform === "darwin"`, warns loudly otherwise. `.github/workflows/binaries.yml` restructured to 2 jobs: `build-linux-win` on `ubuntu-latest` (genuine cross-compile, both targets in one job), `build-mac` kept on its own `macos-latest` runner. +- **Both test files rewritten and re-verified with genuine RED→GREEN**: `test_build_config.mjs` (18 assertions, fast/pure) — demonstrated RED on the sharpest new guard by moving the temp-config write location from `REPO_ROOT` to `__dirname` and confirming the test caught it (17/18), restored, confirmed 18/18. `test_build_e2e.mjs` (10 assertions, slow/real) re-run against the new mechanism: built and ran an actual `pkg`-produced binary, confirmed real HTTP responses, added an explicit assertion that no separate `assets/` folder exists. +- Per-OS `README.md`/`CHANGELOG.md` (still unreleased, so amended in place) and root `CHANGELOG.md` updated to match — no remaining mention of Node SEA or the `root` parameter as the current mechanism (both fully retired), though GAP_CONTEXT.md keeps the old reasoning entries with explicit "SUPERSEDED, see below" markers rather than deleting them, per this project's own convention. +- Scrolls updated to match: SPEC.md §36 (rewritten for `pkg`), PLAN.md (new "switched to @yao-pkg/pkg" Done-this-session block, ticket 3 marked done-by-supersession), GAP_ANALYSIS.md (macOS-not-cross-compiled reframed as a real load-bearing constraint, not leftover SEA caution), GAP_CONTEXT.md (two entries marked superseded, two new entries: the switch reasoning and why mac stays on its own runner), WISDOM.md (new trap entry with the full A/B reproduction). + +## Update (2026-08-05 — binaries follow-up: Red/Green TDD + per-OS CHANGELOG.md) +- Direct follow-up ask on the same-day binaries work below: "use Red/Green TDD and update documentation and add a CHANGELOG.md for each os specific folder." +- **`build.mjs` refactored for testability**: added the same entrypoint guard `capture_screenshots.mjs` already established (`process.argv[1] === fileURLToPath(import.meta.url)`) so `PLATFORM_MAP`/`ASSET_ENTRIES`/`resolveTarget()`/`buildBinary(target)` are now named exports importable without a real build running as a side effect. +- **`test_build_config.mjs`** (fast/pure, wired as `npm test`): 17 assertions — `PLATFORM_MAP` per-OS shape, an `ASSET_ENTRIES`-vs-root-`package.json`-"files" drift guard (mirrors the project's existing fixture-drift-check concern), and a static-source regression check that `bin/cli.js`'s `startServer()` still has the `root = ROOT` optional param this entire feature depends on. **Demonstrated genuine RED→GREEN**: temporarily reverted that exact signature back to the old no-param form, ran the test, confirmed exactly the one dependent assertion failed (16/17 — every other check stayed green), restored it via the pre-edit backup, confirmed `bin/cli.js` was byte-identical to HEAD (`git diff` empty) and 17/17 passed again. +- **`test_build_e2e.mjs`** (slow/real, wired as `npm run test:e2e`, current-platform only — same split rationale as `pptxdiff-cli`'s `test:difftool`): calls the real `buildBinary()`, then spawns the ACTUAL resulting executable and drives it over real HTTP (`GET /`, `/support.js`, `/vendor/react.production.min.js` — all 200 with correct bodies/content-types — plus a path-traversal request confirming `isPathContained` still holds for this feature's different `root` value). Ran for real in this sandbox: built a genuine ~120MB Linux binary, launched it, got 11/11 GREEN. +- **Real bug found and fixed while writing the e2e test, before it ever touched committed files**: both `build.mjs`'s "clean the output directory before building" step AND the e2e test's own post-run cleanup were a blind `rm -rf ` — fine when that directory held only generated output, but it's the SAME directory as each OS's tracked `README.md` (and now `CHANGELOG.md`). Fixed both with a `cleanGeneratedOutDir()`/equivalent that removes only the specific known-generated entries (binary by exact name, `assets/` folder, `*.zip` files) rather than the whole directory. Verified the fix for real: ran the actual build twice in a row and confirmed `README.md`/`CHANGELOG.md` survived both times. New WISDOM.md trap entry recorded so a future generator touching a mixed generated/tracked-content directory doesn't repeat this. +- **`src/packages/binaries/pptxdiff-{win,mac,linux}/CHANGELOG.md`** added (Keep a Changelog format, un-ignored in `.gitignores/user.gitignore` + regenerated `.gitignore` the same way `README.md` already was) — tracks the bundled `pptxdiff` app version per OS folder, since the binary itself has no independent feature set to version separately. +- **Root `CHANGELOG.md`**: filled in the previously-empty `[Unreleased]` placeholder with this whole binaries feature (both this update and the earlier same-day one). +- Scrolls updated to match: SPEC.md §36 (testing subsection added), PLAN.md (new "Done this session" bullet + a 4th ticket), WISDOM.md (new "clean the output dir" trap). + +## Update (2026-08-05 — standalone native binaries for Windows/macOS/Linux, `src/packages/binaries/`) +- Task asked for a "mechanism to create downloadable installers" under `src/packages/binaries/pptxdiff-{win,mac,linux}`, and explicitly asked whether the folder should be named `binaries` or `installers` — flagged this as a real fork (not a naming bikeshed) since it changes scope by an order of magnitude, and asked the user directly via `AskUserQuestion` before building anything: standalone binaries (Node SEA, no install wizard, no signing) vs. true OS installers (`.msi`/`.pkg`/`.deb` with code signing). User picked **standalone binaries** — consistent with a prior session's explicit Electron/Tauri-vs-CLI+browser decision already on record in GAP_CONTEXT.md (picked CLI+browser specifically to avoid installer/signing overhead). +- **Implementation**: `src/packages/binaries/` (new package, `@pptxdiff/binaries`, private). + - `bin/cli.js`'s `startServer()` gained a backward-compatible optional `root = ROOT` parameter (one-line change, every existing caller — `pptxdiff-cli`'s automation layer, `bin/cli.js`'s own direct-run path — unaffected) so a packaged binary can point it at an `assets/` folder next to the executable instead of the npm package's own `__dirname`-relative `src/pptxdiff`. + - `sea-entry.cjs`: the SEA entry point — resolves `root` from `path.dirname(process.execPath)`, requires `../../../bin/cli.js` (resolved by esbuild at BUILD time, not a runtime path lookup), calls the same `startServer()`/`buildBrowserOpenCommand()` bin/cli.js already uses. Zero server-logic duplication — deliberately avoiding the exact mistake `pptxdiff-vscode/extension.js` made once before (WISDOM.md's existing trap entry about that drift). + - `build.mjs`: bundles `sea-entry.cjs` via esbuild → generates the Node SEA blob (`node --experimental-sea-config`) → copies `process.execPath` and injects the blob via `postject` → (macOS only) `codesign --remove-signature` before injection, `codesign --sign -` (ad-hoc) after → copies `index.html`/`support.js`/`sample-pptx.js`/`vendor/` into `assets/` next to the binary → zips binary+assets into `pptxdiff--.zip`. + - `.github/workflows/binaries.yml`: 3-OS CI matrix (`ubuntu-latest`/`macos-latest`/`windows-latest`) — Node SEA has no cross-compile mode, each OS's binary can only be built ON that OS, so this is the actual mechanism that produces all three, not a single build script. Uploads each as a workflow artifact. + - `make pkg.binaries.build` / `npm run build:binary` build for the current host only (local dev convenience). + - Output folders `src/packages/binaries/pptxdiff-{win,mac,linux}/` are gitignored (build artifacts, `.gitignores/user.gitignore` + regenerated `.gitignore`, same treatment as `dist/`) except one tracked `README.md` per OS folder describing what a build produces there. +- **Verified for real on Linux (this sandbox)**: ran `npm install && node build.mjs` in `src/packages/binaries/`, got a real 120MB ELF executable + a 42MB zip. Then actually RAN the packaged binary directly (not `bin/cli.js`) — it printed `pptxdiff running at http://localhost:`, and real `curl` requests confirmed `index.html`/`support.js`/`vendor/react.production.min.js` all served correctly (200, correct content-type) from its own `assets/` folder resolved via `process.execPath`. macOS/Windows code paths (codesign steps, `.exe` naming) are structurally parallel but genuinely unverified until CI runs them — no non-Linux host available in this sandbox. +- **Known, documented gaps** (see GAP_ANALYSIS.md/PLAN.md): unsigned Windows `.exe` / ad-hoc-signed-only macOS binary (no code-signing cert — real ongoing cost, not a code fix); not yet attached to GitHub Releases (workflow artifacts only); ~120MB per binary (SEA embeds the whole Node runtime, inherent to the approach); true single-file binaries via SEA's embedded-asset store (`sea.getAsset()`) not attempted — shipped as binary+`assets/`-folder zipped together instead, to avoid changing `bin/cli.js`'s already-hardened file-serving logic for this first pass. +- Scrolls updated to match: SPEC.md §36 (new), PLAN.md (new "Done this session" entry + 3 new tickets), GAP_ANALYSIS.md (new "Standalone native binaries" section), GAP_CONTEXT.md (three new entries: why standalone-not-installers, why build output stays out of git except a README, why `startServer()` was reused instead of a third server copy, why assets aren't SEA-embedded). ## 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 diff --git a/docs/.scrolls/PLAN.md b/docs/.scrolls/PLAN.md index 0b98ae8..51bb679 100644 --- a/docs/.scrolls/PLAN.md +++ b/docs/.scrolls/PLAN.md @@ -391,3 +391,154 @@ shim, not sequentially — that plan is what shipped below. ## Done this session (Red/Green regression test for the Chocolatey package) - [x] **P2 — `test_chocolatey_package.mjs`**: pure-Node static-analysis regression test for `src/packages/pptxdiff-chocolatey/` (no `choco`/`pwsh` needed). 21 assertions covering version-sync across `pptxdiff.nuspec`/root `package.json`/the install script's fallback pin, the nuspec's `nodejs` dependency version, both `.ps1` scripts' npm commands, the cmdlet-argument-mode `+`-concatenation bug staying absent, `tools/LICENSE.txt` staying byte-identical to root `LICENSE`, and required companion files existing. Genuinely demonstrated RED (18/21, 3 real failures) before GREEN (21/21) by temporarily reintroducing a version mismatch and the PowerShell bug, then restoring both. - [x] Ticket 2 above ("Automate version sync") is now PARTIALLY addressed: still a manual bump, but drift is caught automatically by the new test instead of shipping silently — see GAP_ANALYSIS.md's updated entry. +## Done this session (standalone native binaries for Windows/macOS/Linux) — mechanism superseded later this same session, see "switched to @yao-pkg/pkg" below +- [x] **P2 — `src/packages/binaries/`: standalone native `pptxdiff` executables via Node SEA.** + Explicit ask, with an explicit up-front choice (asked directly): standalone binaries vs. real + signed OS installers — user picked standalone binaries, consistent with the prior explicit + Electron/Tauri-vs-CLI+browser decision (see GAP_CONTEXT.md). `build.mjs` bundles a small SEA entry + point (reusing `bin/cli.js`'s `startServer()`/`buildBrowserOpenCommand()` — `startServer()` gained + a backward-compatible optional `root` param for this) via esbuild, generates the Node SEA blob, + injects it into a copy of the current `node` binary via `postject`, and copies the static app + files into an `assets/` folder next to it. Output lands in + `src/packages/binaries/pptxdiff-{win,mac,linux}/` (gitignored — build artifacts, one tracked + `README.md` each) as a `pptxdiff--.zip`. `.github/workflows/binaries.yml` runs the + build on a 3-OS CI matrix (SEA has no cross-compile mode — each OS's binary can only be built ON + that OS) and uploads each as a workflow artifact. `make pkg.binaries.build` / `npm run + build:binary` build for the current host only. Verified for real on Linux (this sandbox): built, + ran the actual packaged binary (not just `bin/cli.js`), confirmed it serves `index.html`/ + `support.js`/`vendor/*` correctly via real HTTP requests. macOS/Windows are structurally identical + but unverified until CI runs them (no non-Linux host in this sandbox) — see GAP_ANALYSIS.md. +- [x] **P2 — Red/Green TDD for the binaries build (explicit follow-up ask), plus CHANGELOG.md per OS + folder.** `build.mjs` refactored with an entrypoint guard (same pattern as `capture_screenshots.mjs`) + exporting `PLATFORM_MAP`/`ASSET_ENTRIES`/`resolveTarget`/`buildBinary` for testability. + `test_build_config.mjs` (fast/pure, `npm test`): 17 assertions including a static-source regression + guard on `bin/cli.js`'s `startServer(root = ROOT)` signature — demonstrated genuine RED→GREEN by + temporarily reverting that exact signature, confirming the one dependent assertion failed (16/17), + restoring it, confirming 17/17. `test_build_e2e.mjs` (slow/real, `npm run test:e2e`, current-platform + only): actually builds and RUNS the real packaged binary, 11/11 assertions against real HTTP + responses (including a path-traversal check against this feature's different `root` value). Caught + and fixed a real bug while writing the e2e test: `build.mjs`'s (and the test's own) "clean the + output dir" step was a blind `rm -rf` that would have deleted the tracked `README.md`/`CHANGELOG.md` + living in the same per-OS folder on every build — fixed with a `cleanGeneratedOutDir()` that removes + only the specific generated entries; verified by running the real build twice in a row and + confirming both docs files survive. `src/packages/binaries/pptxdiff-{win,mac,linux}/CHANGELOG.md` + added (Keep a Changelog format, tracks the bundled `pptxdiff` app version). Root `CHANGELOG.md` + `[Unreleased]` section filled in for this whole feature (previously an empty placeholder). See + SPEC.md §36, WISDOM.md's new "clean the output dir" trap entry. + +## New tickets opened this session +1. **P2 — Attach the built binaries to GitHub Releases**, not just CI workflow artifacts. Needs a + `release: types: [published]`-triggered job (or similar) that re-runs the build and uploads the + binaries to the release — not built this session, current CI only produces downloadable workflow + artifacts on push/dispatch. +2. **P3 — Code signing for the Windows `.exe` and a real Apple Developer ID for macOS.** Needs a + purchased/managed certificate (real ongoing cost, not a code change) — until then, both binaries + trigger their OS's "unidentified/unsigned" security warning on first run. Documented per-OS in + each `pptxdiff-/README.md`. +3. ~~**P4 — True single-file binaries via Node SEA's embedded-asset store.**~~ **[DONE, by switching + mechanism entirely]** — see the `@yao-pkg/pkg` switch below. `pkg`'s built-in asset embedding gave + a genuine single file with zero `bin/cli.js` changes, superseding this ticket rather than closing + it as originally scoped. +4. ~~**P4 — `test_build_e2e.mjs` only exercises the CURRENT host's platform branch.**~~ Still true + under `pkg` (same reason: only the current host's binary can actually be RUN and verified over + HTTP locally) — carried forward, not re-opened as new. + +## Done this session (switched `@pptxdiff/binaries` from Node SEA to `@yao-pkg/pkg`) +- [x] **P2 — Switched the native-binary build mechanism after an explicit follow-up question** ("why + aren't you using yao-pkg/pkg?"). Investigated hands-on rather than reasoning abstractly: built real + test binaries in this sandbox confirming `pkg` genuinely cross-compiles (a real Linux-built `.exe` + confirmed via `file` as `PE32+ executable ... for MS Windows`, a real Linux-built mac binary + confirmed as `Mach-O 64-bit x86_64 executable`) and that its snapshot filesystem lets `bin/cli.js` + serve its static assets with ZERO code changes (reverted the SEA-era `root` parameter entirely — + `bin/cli.js` is now byte-identical to before this whole feature, confirmed via `git show` diff). + `build.mjs` rewritten around `pkg`'s `exec()` API (`buildOne(osKey, target)`/`buildAll(osKeys)`), + `sea-entry.cjs` deleted (no longer needed — `pkg` points directly at the real `bin/cli.js`). + Output is now a true single file per OS (no more `assets/` folder, no more zip wrapper). +- [x] **Found and fixed a real, silently-failing gotcha mid-switch**: `pkg`'s `"assets"` glob paths + resolve relative to wherever the CONFIG FILE ITSELF lives, not cwd or the entry file's directory — + confirmed via a controlled A/B test (moving the config file between directories with the identical + glob, watching assets silently stop embedding with zero error). Fixed by having `buildOne()` write + its temp pkg config directly at `REPO_ROOT` (removed in a `finally`); new WISDOM.md trap entry with + the full reproduction, since this fails completely silently at build time. +- [x] **`.github/workflows/binaries.yml` restructured to 2 jobs** (from the original 3-OS matrix): + `build-linux-win` on `ubuntu-latest` builds both those targets in one job (genuine cross-compile); + `build-mac` stays on its own `macos-latest` runner — NOT collapsed into the Linux job, because + `codesign` only exists on macOS and a completely unsigned binary may not even launch on Apple + Silicon (AMFI requires at least an ad-hoc signature) — see GAP_CONTEXT.md's new entry for the full + reasoning on why this one target intentionally isn't cross-compiled despite `pkg` technically being + able to. +- [x] **Both test files (`test_build_config.mjs`/`test_build_e2e.mjs`) rewritten for the new shape** + and re-verified with genuine RED→GREEN on the sharpest new guard (the config-colocation regression + check) — moved the temp-config write location, confirmed the test caught it (17/18), restored, + confirmed 18/18. `test_build_e2e.mjs` re-run for real against the new mechanism: 10/10, a real + binary built via `pkg` and run, serving the real app over real HTTP, with an explicit assertion + that no separate `assets/` folder exists anymore. +- [x] Per-OS `README.md`/`CHANGELOG.md` (both still-unreleased, so amended in place rather than + given a second changelog entry) updated to describe the single-file artifact and, for macOS + specifically, the "must be built on a real Mac" constraint. +- [x] Root `CHANGELOG.md`'s `[Unreleased]` section updated to match (mentions `@yao-pkg/pkg`, the + 2-job CI split, and the Red/Green test suite — no longer mentions Node SEA or `startServer`'s + `root` param, since that was fully reverted). + +## Done this session (native Apple Silicon build: `pptxdiff-mac-arm64`) +- [x] **P3 — Added `pptxdiff-mac-arm64`, a native Apple Silicon binary**, after an explicit follow-up + question ("does the mac binary work for Apple Silicon MacBooks?"). Before this, Apple Silicon Macs + could only run the Intel `pptxdiff-mac` binary via Rosetta 2 translation. `TARGET_MAP` gained an + `outDirKey` field (separate from the map key) so `mac`/`mac-arm64` share `pptxdiff-mac/` as their + output folder while keeping distinct `binName`s — `buildOne()` computes `outDir` from + `target.outDirKey`, not the `osKey` it's called with. `.github/workflows/binaries.yml`'s `build-mac` + job now builds both mac targets (`npm run build -- mac mac-arm64`) and uploads both as separate + artifacts; `test_build_e2e.mjs` picks `mac` vs `mac-arm64` based on the host's actual `os.arch()`, + so GitHub's Apple-Silicon `macos-latest` runners genuinely exercise the native build. Verified for + real in this sandbox (Linux): built the `node22-macos-arm64` target directly, confirmed via `file` + it's a genuine `Mach-O 64-bit arm64 executable`, confirmed it landed in the shared `pptxdiff-mac/` + folder without disturbing the tracked `README.md`/`CHANGELOG.md`, and confirmed `pkg`'s own error + output independently prints the same Apple-Silicon-signing warning this project's reasoning already + relied on. Windows/Linux stay x64-only — not asked about, and arm64 desktops are a much smaller + fraction of that audience than Apple Silicon is of the Mac audience (see GAP_CONTEXT.md). +- [x] **Genuine RED→GREEN demonstrated on the new `outDirKey` guard**: temporarily reverted + `buildOne()`'s `outDir` computation to use the `osKey` argument instead of `target.outDirKey`, + confirmed the dedicated regression test caught it (22/23), restored it, confirmed 23/23. +- [x] Per-OS mac `README.md`/`CHANGELOG.md`, the top-level `src/packages/binaries/README.md`, root + `CHANGELOG.md`, `SPEC.md` §36, `GAP_ANALYSIS.md`, and `GAP_CONTEXT.md` all updated to describe both + mac targets. + +## New tickets opened this session +1. ~~**P4 — Native Windows/Linux arm64 builds**, if ever asked for.~~ **[DONE, same day]** — see + below; the user asked directly in an immediate follow-up. +2. **P4 — Investigate `ldid` for Linux-side ad-hoc signing of macOS binaries**, which `pkg`'s own + error output suggests as an alternative to a real macOS CI runner — would let `build-mac` fold + into the cross-compiled `build-linux-win` job (one CI job instead of two). Not pursued; a real + `macos-latest` runner using Apple's own `codesign` was judged more trustworthy for a first pass — + revisit if CI job count/time ever becomes a real constraint. + +## Done this session (native Windows/Linux arm64 builds: `pptxdiff-win-arm64.exe`, `pptxdiff-linux-arm64`) +- [x] **P4 — Added `pptxdiff-win-arm64.exe` and `pptxdiff-linux-arm64`**, direct follow-up to "can we + support arm64 for windows and linux as well?" (immediately after the mac-only arm64 addition + above). Unlike macOS, neither needs a signing step, so both fold straight into the existing + `build-linux-win` CI job — `TARGET_MAP` gained `linux-arm64`/`win-arm64` entries sharing their + OS's `outDirKey`, `.github/workflows/binaries.yml`'s `build-linux-win` job now builds and uploads + all four Windows/Linux target/arch combos. +- [x] **Found and fixed a genuine new build failure, not just "it worked because pkg supports arm64 + targets."** First attempt (`pkg -t node22-linux-arm64 ...`) failed with `ERR_ASSERTION`; `--debug` + traced it to a real exec-format error — generating V8 bytecode for a foreign arch requires running + a matching-arch "fabricator" helper, which fails outright without QEMU/binfmt emulation (confirmed + absent in this sandbox: `which qemu-aarch64` and `/proc/sys/fs/binfmt_misc` both empty). `pkg`'s + own warning named the fix: `--fallback-to-source`, now applied unconditionally in `buildOne()`'s + pkg invocation (a no-op for same-arch builds, where bytecode generation just succeeds normally). +- [x] **Verified via the real production `buildOne()` path** (not a bare smoke test): built + `node22-linux-arm64` and `node22-win-arm64` with the real asset config, confirmed via `file` genuine + `ELF ... ARM aarch64` / `PE32+ ... Aarch64` executables, confirmed binary SIZE is consistent with + real assets actually being embedded (72-75MB, matching the known-good x64 builds — not a + stripped-down failure artifact), confirmed each landed in its correct SHARED output folder without + disturbing the tracked `README.md`/`CHANGELOG.md` already there. Not run — no arm64 execution + emulation available in this sandbox; CI is what actually executes these for the first time. +- [x] **Genuine RED→GREEN demonstrated on the new `--fallback-to-source` regression guard**: + temporarily removed the flag from `buildOne()`'s pkg invocation, confirmed the dedicated test + caught it (28/29), restored it, confirmed 29/29. `test_build_config.mjs` now 29 assertions (was 23). +- [x] `test_build_e2e.mjs`'s host-target detection generalized from "arm64 only matters on darwin" to + checking `os.arch()` for every platform, so an arm64 Linux/Windows CI runner would also genuinely + exercise its native target rather than always falling back to x64. +- [x] Per-OS win/linux `README.md`/`CHANGELOG.md`, the top-level `src/packages/binaries/README.md`, + root `CHANGELOG.md`, `SPEC.md` §36, `GAP_ANALYSIS.md`, and `GAP_CONTEXT.md` all updated — all six + targets now documented consistently. diff --git a/docs/.scrolls/SPEC.md b/docs/.scrolls/SPEC.md index 0540754..b092989 100644 --- a/docs/.scrolls/SPEC.md +++ b/docs/.scrolls/SPEC.md @@ -300,3 +300,19 @@ Word-level diff (LCS-based) highlights changed words within text/table-cell/char - **Explicit browser behavior**: `--browser=chrome` opens Google Chrome (`open -a "Google Chrome"` on macOS, `start chrome` on Windows, `google-chrome` on Linux). `--browser=msedge` opens Microsoft Edge (`open -a "Microsoft Edge"` on macOS, `start msedge` on Windows, `microsoft-edge` on Linux). - **Failure behavior**: unsupported browser values or unknown options fail before the local server starts, with exit code `2` and a clear error. If the selected browser command itself is missing or cannot launch in a headless/no-GUI environment, the CLI still prints the local URL and ignores the browser-open failure, preserving the prior "URL is enough to proceed manually" behavior. - **Testing**: `src/pptxdiff/test_execfile_browser_open_cli.mjs` now covers `parseArgs()` for both `--browser=value` and `--browser value`, rejects unsupported values, and verifies all platform/browser command builders still pass the URL as a single `execFile()` argv element rather than shell-interpolating it. +## 36. Standalone native binaries (`@pptxdiff/binaries`, added this session) +- **What it does**: `src/packages/binaries/` builds a standalone, native `pptxdiff` executable per target — six total (x64 + arm64 for each of Windows/macOS/Linux) — download ONE file, run it, `pptxdiff` opens in the browser. No Node.js install, no `npm install -g`, no `npx`, no separate assets folder to keep alongside it. Output lands in `src/packages/binaries/pptxdiff-win/{pptxdiff-win.exe,pptxdiff-win-arm64.exe}`, `pptxdiff-mac/{pptxdiff-mac,pptxdiff-mac-arm64}`, `pptxdiff-linux/{pptxdiff-linux,pptxdiff-linux-arm64}` (the folder names the user requested — each OS's two chip variants share one folder, distinguished by binary name). Build output is gitignored (generated, not source); each OS folder keeps a tracked `README.md` and `CHANGELOG.md` describing what a build produces there. +- **Native arm64 builds for all three OSes**, added incrementally across two follow-up questions: first macOS ("does the mac binary work for Apple Silicon MacBooks?" — answer: only via Rosetta 2 translation until this), then Windows/Linux ("can we support arm64 for windows and linux as well?"). `TARGET_MAP` entries gained an `outDirKey` field (separate from the map's own key) so e.g. `mac`/`mac-arm64` share `pptxdiff-mac/` as their output folder while each keeping distinct `binName`s — `buildOne()` computes `outDir` from `target.outDirKey`, not the `osKey` argument. Cross-ARCH builds (`*-arm64` from an x64 host, or vice versa) need `pkg`'s `--fallback-to-source` flag: generating V8 bytecode for a foreign architecture requires executing a matching-arch helper binary, which fails outright without QEMU/binfmt emulation — confirmed directly via a genuine exec-format error before the flag was added, not assumed from documentation. Verified for real in this sandbox (x64 Linux): built `node22-macos-arm64`, `node22-linux-arm64`, and `node22-win-arm64` directly via the real production `buildOne()` path (real asset config, not a bare smoke test), confirmed via `file` they're genuine `Mach-O 64-bit arm64`/`ELF ... ARM aarch64`/`PE32+ ... Aarch64` executables landing correctly in their SHARED output folders without disturbing the tracked `README.md`/`CHANGELOG.md` already there; `pkg` itself independently prints the same Apple-Silicon-signing warning this project's own reasoning already relied on for the mac case. +- **Deliberately standalone binaries, not real OS installers**: asked directly (binaries vs. true `.msi`/`.pkg`/`.deb` installers with an install wizard, PATH registration, code signing) and the user picked standalone binaries — consistent with this project's prior explicit decision (see GAP_CONTEXT.md "Why the npm CLI opens a browser tab instead of a real native window") to avoid Electron/Tauri-style installer and code-signing overhead. See `src/packages/binaries/README.md` for the full reasoning. +- **Mechanism: `@yao-pkg/pkg`, not Node's own SEA feature.** This package originally shipped using Node's built-in Single Executable Applications (SEA) feature. Switched after an explicit user question ("why aren't you using yao-pkg/pkg?") surfaced two real advantages SEA doesn't have — see GAP_CONTEXT.md for the full reasoning and the honest tradeoff (one more third-party build-tool devDependency, dev-time only): + 1. **Real cross-compilation.** `pkg` downloads a prebuilt base `node` binary per TARGET platform and injects the bundled app into it, so one Linux host builds the Windows AND Linux binaries. SEA can only build for whatever OS it's currently running on. + 2. **Built-in asset embedding, with zero `bin/cli.js` changes.** `pkg`'s snapshot filesystem mirrors the real project's relative directory layout at runtime — `bin/cli.js`'s existing, completely UNMODIFIED `ROOT = path.join(__dirname, "..", "src", "pptxdiff")` computation resolves correctly with `build.mjs` simply listing `src/pptxdiff/index.html`/`support.js`/`sample-pptx.js`/`vendor/**` as pkg `"assets"`. (The SEA version had needed an added `root` parameter on `startServer()` plus a separate `assets/` folder shipped next to the binary — both fully reverted; `bin/cli.js` is now byte-identical to how it looked before this whole feature.) +- **A hard-won gotcha discovered mid-build** (see WISDOM.md's new trap entry): `pkg`'s `"assets"` glob paths in a config file resolve relative to wherever THAT CONFIG FILE ITSELF lives — not cwd, not the entry file's directory. Get it wrong and it fails completely silently (zero assets embedded, no warning, only a 404 when the packaged binary actually runs). `build.mjs`'s `buildOne()` writes a temp pkg config directly at the repo root (next to the real `package.json`, where `src/pptxdiff/**` actually resolves), removed in a `finally` block. +- **Neither macOS target is cross-compiled** (Windows and Linux both ARE, x64 and arm64 alike). `pkg` can produce macOS binaries (either chip) from Linux, but can't codesign them (`codesign` only exists on macOS) — and on Apple Silicon, a completely unsigned binary may not even launch (AMFI requires at least an ad-hoc signature, unlike Intel Macs where it's "only" a Gatekeeper warning). `buildOne()` only runs its codesign step when `process.platform === "darwin"`, warning loudly otherwise rather than silently shipping something that might not run. `.github/workflows/binaries.yml` reflects this: Windows+Linux (all four target/arch combos) build together in one `ubuntu-latest` job (`build-linux-win`); both `mac`/`mac-arm64` build separately on `macos-latest` (`build-mac`, itself an Apple Silicon runner as of 2024) so they're genuinely signed. +- **Known, documented gaps** (see GAP_ANALYSIS.md): unsigned Windows `.exe`s (SmartScreen warning) and ad-hoc-signed-only macOS binaries (Gatekeeper warning) — no code-signing certificate exists for this project; not yet wired to GitHub Releases; a Linux-side `ldid`-based signing workaround for macOS (which would let `build-mac` fold into the cross-compiled Linux job) was considered but not pursued. +- **Verified locally** (Linux, this sandbox), for both mechanisms in turn: built for real, ran the actual packaged binary directly (not just `bin/cli.js`), confirmed it correctly serves `index.html`/`support.js`/`vendor/*` via real `curl` requests — once under Node SEA, and again after the switch to `@yao-pkg/pkg`, this time with a true single file and zero `bin/cli.js` changes. Windows/macOS builds are structurally identical (same `buildOne()`, only the mac codesign branch differs) but unverified end-to-end locally — no Windows/macOS host in this sandbox; CI exercises them for real. +- **Red/Green TDD, two test files** (`build.mjs` has an entrypoint guard — same pattern as `capture_screenshots.mjs`, see WISDOM.md — so `TARGET_MAP`/`ASSET_GLOBS`/`resolveTarget`/`buildOne`/`buildAll` are importable without a real build running as a side effect): + - `test_build_config.mjs` (fast, pure, no subprocess/network, `npm test`): 29 assertions covering `TARGET_MAP`'s per-target shape (six entries — `linux`/`linux-arm64`/`win`/`win-arm64`/`mac`/`mac-arm64`), that each OS's two chip variants share one `outDirKey` and have distinct `binName`s (no collision), an `ASSET_GLOBS`-vs-root-`package.json`-"files" drift guard, that `bin/cli.js` is passed to pkg completely unmodified (no packaging-specific parameter), the macOS-signing safety checks, and three sharp static-source regression checks: that `buildOne()` writes its temp pkg config at `REPO_ROOT` (the config-colocation gotcha above), that it computes `outDir` from `target.outDirKey` rather than the `osKey` argument (the exact mechanism that lets chip variants share one folder), and that its `pkg` invocation includes `--fallback-to-source` (required for every cross-arch build — losing it would silently break all four arm64 targets while leaving the x64 ones unaffected, easy to miss without a dedicated check). Demonstrated genuine RED→GREEN four times across this session's three rounds: the now-retired SEA-era `startServer(root)` contract, the pkg-config-colocation guard, the `outDirKey` guard, and the `--fallback-to-source` guard (removed the flag, confirmed the one dependent assertion failed (28/29), restored it, confirmed 29/29). + - `test_build_e2e.mjs` (slow, real, current-platform-only, `npm run test:e2e` — same split as `pptxdiff-cli`'s `test:difftool`): picks the host's own target via `process.platform`+`os.arch()` (an arm64 host of any OS tests its `-arm64` target, not the x64 one), calls `buildOne()`, then spawns the REAL resulting single-file executable and drives it over real HTTP — `GET /`, `/support.js`, `/vendor/react.production.min.js` all 200 with correct content, an explicit assertion that no separate `assets/` folder exists, plus a path-traversal request. 10/10 assertions, genuinely GREEN against a real binary built and run in this sandbox by BOTH mechanisms in turn, and unaffected by the arm64 target additions (this sandbox is x64, so it still exercises `linux`). Cleans up only the binary file afterward, never the tracked `README.md`/`CHANGELOG.md` in the same folder. +- **Structurally verified the new `mac-arm64` target directly in this sandbox** (Linux): ran a real `node build.mjs mac-arm64` build, confirmed via `file` a genuine `Mach-O 64-bit arm64 executable`, confirmed it landed in the SHARED `pptxdiff-mac/` folder alongside the tracked `README.md`/`CHANGELOG.md` without disturbing them, and confirmed `pkg` itself independently prints the exact Apple-Silicon-signing warning this project's own reasoning already relied on — cleaned up afterward (can't be run/verified over HTTP from Linux; that's CI's job). + diff --git a/docs/.scrolls/WISDOM.md b/docs/.scrolls/WISDOM.md index b638c81..51262f9 100644 --- a/docs/.scrolls/WISDOM.md +++ b/docs/.scrolls/WISDOM.md @@ -152,6 +152,10 @@ - The `capture_screenshots.mjs` ESM entrypoint guard (`if (process.argv[1] === fileURLToPath(import.meta.url))`) has a direct CommonJS equivalent: `if (require.main === module) { ... }`. Used this to make `bin/cli.js`'s pure `buildBrowserOpenCommand(platform, url)` helper importable/unit-testable without the side effect of actually starting the real static server — when a CJS file is loaded via a dynamic ESM `import()` from a test script (rather than run directly as `node bin/cli.js`), `require.main` is `undefined` inside it (there's no CJS main module when the process entrypoint is ESM), so the guard correctly skips the server-start/browser-open block. Same underlying principle as the ESM case: a self-executing script becomes testable by guarding its own execution, not by splitting into a separate always-imported library file. - When exec-vs-execFile matters for shell-injection hardening, the strongest test isn't just "does the source contain the word execFile" (a static grep) — it's asserting the pure command-builder passes a shell-metacharacter-laden string through as ONE untouched argv array element (proving no shell interpolation is possible), on every platform branch, including the Windows one (`cmd.exe /c start "" ` — `start` is a cmd.exe built-in, not a standalone executable, so it must be routed through `cmd.exe` rather than `execFile("start", ...)` directly). +## Wisdom — a build script's "clean the output dir" step can eat tracked files sitting in that same dir (addendum, `@pptxdiff/binaries` session) +- **`rm -rf ` followed by `mkdir` is only safe if EVERYTHING in that directory is generated.** `src/packages/binaries/build.mjs`'s first draft blindly wiped `pptxdiff-/` before every build — fine when the directory held only build output, but it was also the same directory holding that OS's tracked `README.md`/`CHANGELOG.md`. The bug was caught before it ever ran against the committed files (found while writing this session's e2e test, which needed to assert those docs survive a real build) — the fix was a `cleanGeneratedOutDir()` that removes only the specific known-generated entries (the binary by exact name, the `assets/` folder, any `*.zip`) rather than nuking the whole directory. **Any time a build/generator script's output directory is ALSO where hand-written, tracked files live (a README describing the folder, a CHANGELOG, a `.gitkeep`), a full-directory wipe is the wrong tool — enumerate and remove only what the script itself produces.** Same root mistake class as `test_build_e2e.mjs`'s own cleanup step, which had to be fixed the identical way for the identical reason. +- **Test the "does cleanup preserve what it should" property for real, not just "does the build succeed."** The e2e test ran the real build TWICE in a row specifically to prove `README.md`/`CHANGELOG.md` survive repeated builds (not just present after one run, which wouldn't catch a wipe-then-never-restore bug) — a cheap, high-value check once the fix was in place, worth doing any time a generator's repeated-run idempotency touches a directory with mixed generated/tracked content. + ## Wisdom — verify a "known weakness" is actually reachable through the specific call site before testing for it (addendum, security-hardening session) - `path.normalize()` on an already-absolute string (leading `/`) clamps `..` at the root — it can't produce a result that still starts with `..`. `path.join(root, laterArg)` does NOT treat a leading `/` in `laterArg` as an absolute-path reset (unlike `path.resolve()`) — it just concatenates segments. Combined, `path.join(ROOT, path.normalize(reqPath))` in `bin/cli.js`'s request handler already prevented `../`-style HTTP traversal from escaping `ROOT` *by construction*, before the containment check (`startsWith(ROOT)` or its `path.relative()`-based replacement) even runs — confirmed with a plain `node -e` trace comparing several traversal payloads against both forms. - This mattered concretely when writing `test_path_containment_cli.mjs` for the `isPathContained()` hardening (P0 ticket 3): an HTTP-level test asserting the old code returned `403` for `../../../etc/passwd` was simply wrong — it returned `404` (the resolved-but-still-contained path just doesn't exist), both before and after the fix. The actual, meaningful `startsWith()`-vs-`path.relative()` divergence (a sibling directory sharing `ROOT`'s prefix, e.g. `/app/pptxdiff-evil`) isn't reachable through this call site's `path.join(ROOT, ...)` construction at all — a `path.join` can't literally produce a `ROOT + "-evil"` string, since it always inserts a separator. That case is only testable by calling the pure containment function directly with a crafted sibling path. @@ -218,3 +222,9 @@ ## Wisdom — PowerShell command-mode vs. expression-mode argument parsing (addendum, Chocolatey package session) - **`SomeCmdlet "a" + "b"` is NOT string concatenation in PowerShell.** Cmdlet/function calls are parsed in "command mode," where `+` between two quoted-string arguments is just another positional argument token, not the binary `+` operator — `Write-Warning "line one " + "line two"` actually passes `Write-Warning` three positional arguments (`"line one "`, `"+"`, `"line two"`), and since `Write-Warning` only accepts one positional `-Message` argument, this throws `A positional parameter cannot be found that accepts argument '+'` the first time that code path actually runs. `throw` doesn't have this problem — it's a language keyword taking a real expression, so `throw "a" + "b"` concatenates correctly. **Rule**: any multi-line message string passed to a cmdlet (`Write-Warning`, `Write-Host`, `Write-Error`, etc.) must be built as a separate variable assignment first (`$msg = "a" + "b"`, which IS expression mode) and then passed as `SomeCmdlet $msg` — never inline `+`-concatenated directly in the argument position. Found while writing `src/packages/pptxdiff-chocolatey/tools/chocolateyuninstall.ps1`, with no `pwsh` available in this sandbox to catch it by running — caught by reasoning through PowerShell's command-vs-expression parsing modes instead. Worth a deliberate second look any time a future session writes/edits `.ps1` files in this repo (currently just the Chocolatey package's `tools/*.ps1`), since there's no automated PowerShell test/lint in this project's CI to catch it otherwise. **Now permanently guarded**: `src/packages/pptxdiff-chocolatey/test_chocolatey_package.mjs`'s `hasCmdletPlusConcatBug()` regex-scans both `.ps1` scripts for exactly this pattern (a `Write-Warning`/`Write-Host`/`Write-Error`/`Write-Output`/`Write-Verbose` call followed by a quoted string then `+`) and fails the build if it's ever reintroduced — demonstrated genuinely RED (reintroduced the bug, confirmed the check caught it) before being restored GREEN, so this doesn't rely on a future session remembering to re-read this note. +## Trap — `@yao-pkg/pkg`'s `"assets"` glob paths resolve relative to the CONFIG FILE'S OWN directory, not cwd or the entry file (found switching `@pptxdiff/binaries` from Node SEA to `pkg`) +- A `pkg` config's `"assets"` array is documented as accepting glob patterns, but WHERE those patterns are resolved FROM is not obvious from the CLI's own `--help` output, and getting it wrong produces **zero error and zero warning** — the build succeeds, produces a binary of a plausible size, and only fails at RUNTIME with 404s on every asset request. This is a nastier failure mode than a build-time error: nothing in the build log tells you anything is wrong. +- **Found by a controlled A/B test, not by reading docs closely enough**: built the exact same app (entry file + relative `"assets"` glob) three different ways — (1) config file in the SAME directory as the entry/project root: assets embedded correctly, confirmed by walking `/snapshot` inside the running packaged binary; (2) config passed via `-c /absolute/path/elsewhere/config.json` with the SAME relative glob string: silently embedded NOTHING (glob resolved against the config file's own directory, which had no matching files); (3) absolute asset paths in a config file living elsewhere: STILL embedded nothing — ruling out "just make the glob absolute" as a fix, and confirming the resolution base is specifically the config file's directory, not affected by whether the glob itself is relative or absolute. +- **The concrete, verified-working fix**: write the pkg config file directly into the SAME directory as the project's real `package.json` (this project's case: `REPO_ROOT`, since `bin/cli.js`'s entry and `src/pptxdiff/**`'s assets are both relative to there) — even though that config is temporary/generated and doesn't belong there as a normal tracked file. `build.mjs`'s `buildOne()` writes it fresh before each `pkg` invocation and removes it in a `finally` block (success or failure), with a matching gitignore entry (`/.pkg-binaries-config.*.json`) as a defensive backstop if a build is ever interrupted mid-run. +- **General lesson for any build tool with a similarly "helpful" auto-detected project root**: when a tool infers "the project" from the entry file's location (rather than requiring an explicit project-root flag), assume ANY other path-bearing config value it reads is ALSO resolved relative to that same inferred root, not to wherever you happen to have put the config file or run the command from — verify this empirically (write a debug entry file that dumps what the tool actually sees, e.g. `fs.readdirSync` on whatever virtual/snapshot root it exposes) rather than trusting a relative path "should just work" from wherever felt natural to place the config. +- **Why this made choosing between mechanisms (Node SEA vs. `pkg`) worth re-litigating mid-implementation rather than shipping the first choice**: this gotcha was found DURING an explicit follow-up question ("why not use yao-pkg/pkg?") that prompted actually trying it, rather than reasoning about it in the abstract — the same spirit as this project's standing rule to verify claims empirically (real builds, real `curl` requests, real `git diff` checks) rather than trust documentation or a plausible-sounding design on paper. A tool switch motivated by a real question, tested hands-on before committing to it, surfaced both a genuine capability win (cross-compilation, confirmed with real `file` output on genuine PE32+/Mach-O binaries) AND a real gotcha (this one) that pure reading wouldn't have surfaced as concretely. diff --git a/package.json b/package.json index 96c2fab..7e3457b 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build:npm": "mkdir -p dist && npm pack --pack-destination dist", - "package:vscode": "cd src/packages/pptxdiff-vscode && mkdir -p dist && vsce package --out dist/pptxdiff-vscode-$(node -p \"require('./package.json').version\").vsix" + "package:vscode": "cd src/packages/pptxdiff-vscode && mkdir -p dist && vsce package --out dist/pptxdiff-vscode-$(node -p \"require('./package.json').version\").vsix", + "build:binary": "cd src/packages/binaries && npm install && npm run build" }, "files": [ "bin", diff --git a/src/packages/binaries/README.md b/src/packages/binaries/README.md new file mode 100644 index 0000000..1f95f42 --- /dev/null +++ b/src/packages/binaries/README.md @@ -0,0 +1,170 @@ +# @pptxdiff/binaries + +Builds standalone, native `pptxdiff` executables for Windows, macOS, and +Linux — download one file, run it, and pptxdiff opens in your browser. No +Node.js install, no `npm install -g`, no `npx`. + +This is deliberately **not** a real OS installer (no `.msi`/`.pkg`/`.deb` +wizard, no PATH registration, no entry in Add/Remove Programs) — see +`docs/.scrolls/GAP_CONTEXT.md` for why: this project already made an +explicit, reasoned call to avoid Electron/Tauri-style installer and +code-signing overhead when it chose the CLI+browser architecture over a +native-window app, and building real signed installers would mean +reversing that without a corresponding ask. A standalone executable gets +"download and run, no Node.js required" — the actual pain point — without +that cost. + +## Why `@yao-pkg/pkg`, not Node's own SEA feature + +This package originally used Node's built-in Single Executable +Applications (SEA) feature. Switched to `@yao-pkg/pkg` (the +actively-maintained community fork of the Vercel-archived `pkg`) after an +explicit question about it, for two concrete reasons SEA can't match: + +1. **Real cross-compilation.** `pkg` downloads a prebuilt "base" node + binary per target platform+arch and injects the bundled app into it — + one Linux host can build the Windows AND Linux binaries, x64 AND arm64 + (confirmed directly: real `ELF ... ARM aarch64` and `PE32+ ... Aarch64` + executables, both built on this project's own x64 Linux dev sandbox). + SEA injects into a copy of the *currently running* node binary, so it + can only ever build for the OS+arch it's actually running on (the old + 3-OS CI matrix existed solely to work around that). +2. **Built-in asset embedding.** `pkg`'s snapshot filesystem preserves the + real project's relative directory structure at runtime, so + `bin/cli.js`'s existing, UNMODIFIED `ROOT = path.join(__dirname, "..", + "src", "pptxdiff")` resolution just works — no `assets/` folder + shipped alongside the binary, no `root` parameter added to `bin/cli.js` + for packaging's sake. A true single file. + +The tradeoff: one more third-party build-tool devDependency (dev-time +only — never shipped in the binaries or the npm package), vs. a fork of a +project Vercel walked away from. Judged worth it for the two wins above; +see `docs/.scrolls/GAP_CONTEXT.md` for the full reasoning. + +**macOS is the one OS NOT cross-compiled here** (for either chip). `pkg` +CAN produce macOS binaries from Linux, but it can't codesign them +(`codesign` only exists on macOS) — and on Apple Silicon, a completely +unsigned binary may not even *launch* (AMFI requires at least an ad-hoc +signature, not just a Gatekeeper warning the way Intel Macs work; `pkg` +itself prints this exact warning if you try). So both mac targets only run +their codesign step when actually built on a macOS host — see +`.github/workflows/binaries.yml`'s separate `build-mac` job. (`pkg` does +mention one Linux-side workaround — installing the `ldid` utility so it +can ad-hoc-sign Mach-O binaries without a real Mac at all — not pursued +here; a real `macos-latest` CI runner was judged simpler and more +reliable than depending on a third tool for signing.) + +## arm64 targets + +Six targets exist total, two per OS, sharing one output folder each: + +| osKey | pkg target | binary | chip | +|---|---|---|---| +| `linux` | `node22-linux-x64` | `pptxdiff-linux` | x64 | +| `linux-arm64` | `node22-linux-arm64` | `pptxdiff-linux-arm64` | arm64 | +| `win` | `node22-win-x64` | `pptxdiff-win.exe` | x64 | +| `win-arm64` | `node22-win-arm64` | `pptxdiff-win-arm64.exe` | arm64 | +| `mac` | `node22-macos-x64` | `pptxdiff-mac` | Intel | +| `mac-arm64` | `node22-macos-arm64` | `pptxdiff-mac-arm64` | Apple Silicon (native) | + +`TARGET_MAP` entries share an `outDirKey` (e.g. `"mac"` for both mac +targets) separate from their own map key, specifically so multiple chip +variants of the same OS land in one folder — see `build.mjs`'s +`buildOne()`. + +**Cross-ARCH builds need `--fallback-to-source`.** Generating V8 bytecode +for a foreign architecture requires running a matching-arch "fabricator" +helper — confirmed directly: attempting `node22-linux-arm64` from this +project's x64 sandbox without the flag failed with a genuine exec-format +error (the shell tried to interpret the arm64 ELF helper's raw bytes as a +script). `--fallback-to-source` ships the entry as plain JS instead of +precompiled bytecode whenever that happens — a real, worthwhile tradeoff +here (this is already-open-source code; bytecode's only benefit is +marginal startup speed and minor reverse-engineering friction, neither +worth a hard build failure). Harmless for same-arch builds, where +bytecode generation just succeeds and the flag is never invoked. + +macOS is the one OS where arm64 isn't "just another cross-compiled +target" — without a native arm64 build, an Apple Silicon Mac could only +run the Intel binary via Rosetta 2 translation (extra launch overhead, +and Rosetta isn't guaranteed pre-installed on a fresh Mac). Windows/Linux +arm64 desktops are a comparatively small fraction of this app's likely +audience, so those two targets exist for completeness/parity rather than +a specific reported need — see `docs/.scrolls/GAP_CONTEXT.md`. + +## How it works + +`build.mjs`'s `buildOne(osKey, target)`: + +1. Writes a temporary pkg config (`{"assets": [...]}` — the same static + files the npm package ships: `index.html`/`support.js`/ + `sample-pptx.js`/`vendor/**`) **directly at the repo root**, next to the + real `package.json`. This placement matters — see "A hard-won gotcha" + below. +2. Calls `@yao-pkg/pkg`'s `exec()` with `bin/cli.js` as the entry, that + config, and the target platform string (e.g. `node22-linux-x64`). +3. On the `mac` target, ad-hoc codesigns the result if running on an + actual macOS host (`codesign --sign -`); otherwise warns loudly that + the binary is unsigned rather than silently shipping it. +4. Removes the temporary config in a `finally` block, success or failure. + +### A hard-won gotcha (see `docs/.scrolls/WISDOM.md`) + +`pkg`'s `"assets"` glob paths in a config file resolve relative to +**wherever that config file itself lives** — not the process's cwd, not +the entry file's directory. Get this wrong and the failure is silent: no +error, no warning, the binary just embeds zero assets and 404s on every +request at runtime. This is why the config is written to `REPO_ROOT` +(where `src/pptxdiff/**` actually resolves) rather than kept as a normal +tracked file inside this package's own directory. + +## Building locally + +```sh +cd src/packages/binaries +npm install +npm run build # builds all six targets by default +npm run build -- linux linux-arm64 win win-arm64 # or build a specific subset +npm run build -- mac mac-arm64 # (on macOS, for a properly-signed result) +``` + +Output lands in `./pptxdiff-/` — one file per +target, nothing else needed alongside it (each OS's two chip variants +share one folder). Each OS folder keeps a tracked `README.md` +(usage/known-warnings) and `CHANGELOG.md` (Keep a Changelog, tracks the +bundled `pptxdiff` app version) — `build.mjs` only ever touches the +specific binary file it's building, never those two or any other target's +binary. + +## Testing (Red/Green TDD) + +```sh +npm test # fast, pure — TARGET_MAP/ASSET_GLOBS shape, a drift + # guard against root package.json's "files", and a + # regression guard on the config-colocation gotcha above +npm run test:e2e # slow, real — builds an actual binary for the CURRENT + # host OS and drives it over real HTTP (index.html/ + # support.js/vendor/* + a path-traversal check) +``` + +`test:e2e` only exercises the current host's own target — it picks the +`-arm64` variant of whatever OS it's running on when the HOST's actual +`os.arch()` is arm64, so an arm64 CI runner (or Apple Silicon macOS one) +genuinely tests the native build, not the x64 one. Every target is +structurally identical (same `buildOne()`, only the mac codesign branch +differs) but only actually built-and-run by CI — this sandbox is x64, so +only `linux`/`win`/`mac`'s x64 code paths are actually EXECUTED locally; +the four arm64 targets are structurally verified (real builds via +`buildOne()`, confirmed via `file`) but not run. + +## Known gaps (see `docs/.scrolls/GAP_ANALYSIS.md`) + +- **Unsigned Windows `.exe`s / ad-hoc-signed-only macOS binaries.** No + code-signing certificate — Windows SmartScreen and macOS Gatekeeper will + warn on a freshly-downloaded copy. Documented per-OS in each + `pptxdiff-/README.md`. +- **Not attached to GitHub Releases yet.** CI currently only uploads build + artifacts on push/dispatch; wiring a release-tag trigger to attach them + to a GitHub Release is a follow-up, not done here. +- **`ldid` (Linux-side macOS signing) not pursued** — see "arm64 targets" + above; would let `build-mac` fold into the cross-compiled Linux job. diff --git a/src/packages/binaries/build.mjs b/src/packages/binaries/build.mjs new file mode 100644 index 0000000..58a40f4 --- /dev/null +++ b/src/packages/binaries/build.mjs @@ -0,0 +1,171 @@ +#!/usr/bin/env node +"use strict"; + +// Builds standalone native pptxdiff executables using @yao-pkg/pkg (the +// actively-maintained fork of the Vercel-archived `pkg` — see +// docs/.scrolls/GAP_CONTEXT.md for why this was chosen over Node's own +// Single Executable Applications feature, which this package originally +// used). +// +// Unlike Node SEA, pkg genuinely cross-compiles: it downloads a prebuilt +// "base" node binary for each TARGET platform+arch and injects the +// bundled app into it, so a single Linux (or any) host can build the +// Windows and Linux binaries (x64 AND arm64 — confirmed directly: a real +// ELF aarch64 executable and a real PE32+ Aarch64 executable, both built +// on this project's own x64 Linux dev sandbox). macOS is the one +// exception in THIS build — see below. +// +// Points bin/cli.js's UNMODIFIED entry point directly at pkg, and embeds +// the same static app files (index.html/support.js/sample-pptx.js/ +// vendor/**) as pkg "assets" — pkg's snapshot filesystem preserves the +// real relative directory structure (entry at snapshot `/bin/cli.js`, +// assets at snapshot `/src/pptxdiff/...`), so `bin/cli.js`'s own +// `ROOT = path.join(__dirname, "..", "src", "pptxdiff")` resolves +// correctly with ZERO code changes — no assets-folder-next-to-the-binary +// workaround needed the way Node SEA required. +// +// IMPORTANT, hard-won gotcha (see WISDOM.md): pkg's "assets" glob paths in +// a config file resolve relative to WHATEVER DIRECTORY THAT CONFIG FILE +// ITSELF LIVES IN — not the process's cwd, not the entry file's directory. +// Silently: no error, no warning, it just embeds nothing if the globs +// don't match from the config's own location. The config therefore has to +// be written to REPO_ROOT (next to the real package.json) for +// "src/pptxdiff/**" to resolve — written fresh before each build and +// removed in a `finally`, since it isn't a real project file. +// +// **macOS (both `mac`/Intel and `mac-arm64`/Apple Silicon) is NOT +// cross-compiled from this build.** pkg can produce either macOS binary +// from Linux/Windows regardless of the BUILD host's own architecture, but +// it cannot codesign either one (`codesign` only exists on macOS) — and an +// entirely unsigned binary is a real functional problem specifically on +// Apple Silicon (arm64 requires at least an ad-hoc signature to launch at +// all under AMFI, not just a Gatekeeper warning like on Intel). So +// `buildOne()` only runs its codesign step when `process.platform === +// 'darwin'`, for either mac target; on any other host it still produces a +// binary (for local experimentation) but loudly warns it's unsigned +// rather than silently shipping something that may not launch. +// .github/workflows/binaries.yml reflects this: linux+win build together +// on ubuntu-latest, both mac targets build separately on macos-latest +// (GitHub's macos-latest runners are themselves Apple Silicon as of 2024, +// so `mac-arm64` there is a genuinely native build+sign, not translated). + +import { execFileSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = path.resolve(__dirname, "..", "..", ".."); + +// `outDirKey` is separate from the map's own key so multiple targets can +// share one OS folder — e.g. `mac`/`mac-arm64` (Intel/Apple Silicon) both +// land in `pptxdiff-mac/`, `win`/`win-arm64` both land in `pptxdiff-win/`, +// since they're the same OS as far as a user picking a download is +// concerned, just a different chip. +export const TARGET_MAP = { + linux: { pkgTarget: "node22-linux-x64", binName: "pptxdiff-linux", outDirKey: "linux", needsMacSign: false }, + "linux-arm64": { pkgTarget: "node22-linux-arm64", binName: "pptxdiff-linux-arm64", outDirKey: "linux", needsMacSign: false }, + win: { pkgTarget: "node22-win-x64", binName: "pptxdiff-win.exe", outDirKey: "win", needsMacSign: false }, + "win-arm64": { pkgTarget: "node22-win-arm64", binName: "pptxdiff-win-arm64.exe", outDirKey: "win", needsMacSign: false }, + mac: { pkgTarget: "node22-macos-x64", binName: "pptxdiff-mac", outDirKey: "mac", needsMacSign: true }, + "mac-arm64": { pkgTarget: "node22-macos-arm64", binName: "pptxdiff-mac-arm64", outDirKey: "mac", needsMacSign: true }, +}; + +// Same subset root package.json's "files" ships to npm — the exact static +// files bin/cli.js's server reads from ROOT. Relative to REPO_ROOT, which +// is where the temp pkg config gets written (see the file header comment +// on WHY that placement matters). +export const ASSET_GLOBS = [ + "src/pptxdiff/index.html", + "src/pptxdiff/support.js", + "src/pptxdiff/sample-pptx.js", + "src/pptxdiff/vendor/**/*", +]; + +// Pure: osKey -> TARGET_MAP entry, or null if unknown. +export function resolveTarget(osKey) { + return TARGET_MAP[osKey] || null; +} + +function log(osKey, msg) { + console.log(`[build-binary:${osKey}] ${msg}`); +} + +// Builds ONE OS's binary. `target` must be a TARGET_MAP entry. Returns the +// absolute path to the built executable. Writes bin.mjs's temp pkg config +// at REPO_ROOT and always removes it afterward, success or failure. +export async function buildOne(osKey, target) { + const outDir = path.join(__dirname, `pptxdiff-${target.outDirKey}`); + fs.mkdirSync(outDir, { recursive: true }); + const binOut = path.join(outDir, target.binName); + fs.rmSync(binOut, { force: true }); + + const tmpConfigPath = path.join(REPO_ROOT, `.pkg-binaries-config.${osKey}.json`); + fs.writeFileSync(tmpConfigPath, JSON.stringify({ assets: ASSET_GLOBS }, null, 2)); + + try { + log(osKey, `Building ${target.pkgTarget} -> ${binOut}`); + const { exec } = await import("@yao-pkg/pkg"); + await exec([ + path.join(REPO_ROOT, "bin", "cli.js"), + "-c", + tmpConfigPath, + "-t", + target.pkgTarget, + "-o", + binOut, + // A cross-ARCH build (e.g. building *-arm64 from this x64 host) needs + // to run a foreign-arch "fabricator" helper to generate V8 bytecode, + // which fails outright without QEMU/binfmt emulation registered — + // confirmed directly (a genuine exec-format failure, not asset- or + // config-related). --fallback-to-source ships the entry as plain JS + // instead of pre-compiled bytecode when that happens; harmless for a + // same-arch build (bytecode generation just succeeds normally and + // this flag is never invoked) and an acceptable tradeoff here — this + // is already-open-source code, so bytecode's only real benefit + // (marginal startup speed / minor reverse-engineering friction) + // isn't worth failing the build over. + "--fallback-to-source", + ]); + + if (target.needsMacSign) { + if (process.platform === "darwin") { + log(osKey, "codesign --sign - (ad-hoc; no paid cert available — see GAP_ANALYSIS.md)"); + execFileSync("codesign", ["--sign", "-", binOut], { stdio: "inherit" }); + } else { + console.warn( + `[build-binary:${osKey}] WARNING: built on ${process.platform}, not darwin — this binary is COMPLETELY UNSIGNED (not even ad-hoc). ` + + "It may not launch at all on Apple Silicon (AMFI requires at least an ad-hoc signature). Build on a real macOS host/runner for a distributable artifact." + ); + } + } + + if (!target.binName.endsWith(".exe")) fs.chmodSync(binOut, 0o755); + log(osKey, `Done: ${binOut}`); + return binOut; + } finally { + fs.rmSync(tmpConfigPath, { force: true }); + } +} + +// Builds every osKey in `osKeys` (default: every TARGET_MAP entry — a +// reasonable local-dev default since pkg CAN cross-compile all of them +// from one machine; the mac-signing caveat above still applies to both +// `mac` and `mac-arm64`). Returns { [osKey]: binPath }. +export async function buildAll(osKeys = Object.keys(TARGET_MAP)) { + const results = {}; + for (const osKey of osKeys) { + const target = resolveTarget(osKey); + if (!target) throw new Error(`Unknown osKey "${osKey}" (expected one of ${Object.keys(TARGET_MAP).join(", ")})`); + results[osKey] = await buildOne(osKey, target); + } + return results; +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const requested = process.argv.slice(2); + buildAll(requested.length ? requested : undefined).catch((e) => { + console.error(e); + process.exitCode = 1; + }); +} diff --git a/src/packages/binaries/package-lock.json b/src/packages/binaries/package-lock.json new file mode 100644 index 0000000..6e73b4f --- /dev/null +++ b/src/packages/binaries/package-lock.json @@ -0,0 +1,2093 @@ +{ + "name": "@pptxdiff/binaries", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@pptxdiff/binaries", + "version": "0.1.0", + "license": "Apache-2.0", + "devDependencies": { + "@yao-pkg/pkg": "^6.22.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@roberts_lando/vfs": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@roberts_lando/vfs/-/vfs-0.3.3.tgz", + "integrity": "sha512-YjkxVSLw5WMZQoARaryRAjcxA+GbBzWMJdwYZX5oLUt9cC/gew9as4Dn7tcLzPp7BPoR221VpTZ+78TRPawnjg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 22" + } + }, + "node_modules/@yao-pkg/pkg": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/@yao-pkg/pkg/-/pkg-6.22.0.tgz", + "integrity": "sha512-u+ZgwLsvEFB+Q1rA+IGymgxnm+anl1qGJWsTH6hDHyy2cCiOvMteDNK7NgqFYxN/zdithtORIlCsYSyhAXAgQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.23.0", + "@babel/parser": "^7.23.0", + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0", + "@roberts_lando/vfs": "^0.3.3", + "@yao-pkg/pkg-fetch": "3.6.5", + "esbuild": "^0.28.1", + "into-stream": "^9.1.0", + "multistream": "^4.1.0", + "picocolors": "^1.1.0", + "picomatch": "^4.0.2", + "postject": "^1.0.0-alpha.6", + "prebuild-install": "^7.1.1", + "resolve": "^1.22.10", + "resolve.exports": "^2.0.3", + "stream-meter": "^1.0.4", + "tar": "^7.5.7", + "tinyglobby": "^0.2.11", + "unzipper": "^0.12.3" + }, + "bin": { + "pkg": "lib-es5/bin.js" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@yao-pkg/pkg-fetch": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@yao-pkg/pkg-fetch/-/pkg-fetch-3.6.5.tgz", + "integrity": "sha512-Sd1Hff7imsF2rcZ2GLQuIzVm2fc3Za+nE4KSWPTwIYegN/r90UFg3bq0MLSugbWQaVht72zzxV0MiKE0wvT1zA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.0", + "progress": "^2.0.3", + "semver": "^7.3.5", + "tar-fs": "^3.1.1", + "undici": "^7.28.0", + "yargs": "^16.2.0" + }, + "bin": { + "pkg-fetch": "lib-es5/bin.js" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.0.tgz", + "integrity": "sha512-fM+MhCvdQhZ7NV6S95a07gPSqjIYKn6mFaXfx266wN3ajZGl/+1AzH+ubkXQ0fFZvOe2nk9VHkzdYkQE5zMV3Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.28.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", + "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", + "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.7.tgz", + "integrity": "sha512-o8CRCiJtib+ycO3mE4A5UChtGX4dDP2XxsWVu9P+Zc3H8tcmKwNVEDoDTXmwN+uuMhfKeT7/i7Y26xS8W7ohoA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/into-stream": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-9.1.0.tgz", + "integrity": "sha512-DRsRnQrbzdFjaQ1oe4C6/EIUymIOEix1qROEJTF9dbMq+M4Zrm6VaLp6SD/B9IsiEjPZuBSnWWFN+udajugdWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/multistream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", + "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "once": "^1.4.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.94.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz", + "integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/stream-meter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", + "integrity": "sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.1.4" + } + }, + "node_modules/stream-meter/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/stream-meter/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-meter/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/streamx": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", + "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-fs": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.3.tgz", + "integrity": "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "11.3.1", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yargs": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + } + } +} diff --git a/src/packages/binaries/package.json b/src/packages/binaries/package.json new file mode 100644 index 0000000..1bb8e45 --- /dev/null +++ b/src/packages/binaries/package.json @@ -0,0 +1,36 @@ +{ + "name": "@pptxdiff/binaries", + "version": "0.1.0", + "description": "Build script producing standalone native pptxdiff executables (via @yao-pkg/pkg) for Windows, macOS, and Linux — no separate Node.js install required to run them.", + "license": "Apache-2.0", + "private": true, + "author": { + "name": "Sugato Ray", + "email": "sugatoray.dev@gmail.com", + "url": "https://github.com/sugatoray" + }, + "scripts": { + "build": "node build.mjs", + "test": "node test_build_config.mjs", + "test:e2e": "node test_build_e2e.mjs" + }, + "devDependencies": { + "@yao-pkg/pkg": "^6.22.0" + }, + "engines": { + "node": ">=20" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/sugatoray/pptxdiff.git", + "directory": "src/packages/binaries" + }, + "homepage": "https://github.com/sugatoray/pptxdiff#readme", + "keywords": [ + "pptx", + "powerpoint", + "diff", + "pkg", + "native-binary" + ] +} diff --git a/src/packages/binaries/pptxdiff-linux/CHANGELOG.md b/src/packages/binaries/pptxdiff-linux/CHANGELOG.md new file mode 100644 index 0000000..949d685 --- /dev/null +++ b/src/packages/binaries/pptxdiff-linux/CHANGELOG.md @@ -0,0 +1,49 @@ +# Changelog — pptxdiff for Linux (standalone binaries) + +All notable changes to the Linux standalone `pptxdiff-linux`/ +`pptxdiff-linux-arm64` builds are documented here. The format is based on +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the version +tracked is the `pptxdiff` app version bundled into the binary (see the +root [`CHANGELOG.md`](https://github.com/sugatoray/pptxdiff/blob/master/CHANGELOG.md) for the app's own history) +since the binaries have no independent feature set of their own. + +## [Unreleased] + +... + +## [0.7.0] - 2026-08-05 + +### Added + +- First standalone Linux executable, built via `@yao-pkg/pkg` (see + `../README.md` and `docs/.scrolls/SPEC.md` §36) — download + `pptxdiff-linux`, `chmod +x pptxdiff-linux && ./pptxdiff-linux`. A true + single file (Node runtime and the static app files it serves are both + embedded inside it) — no separate Node.js install, no companion folder + needed. +- Genuinely cross-compiled: this binary can be built from any host OS, not + just Linux itself. +- Verified end-to-end in this project's own dev sandbox, twice: once + against the original Node-SEA-based mechanism, and again after switching + to `@yao-pkg/pkg` — the actual packaged binary was built and run for + real, confirmed to correctly serve `index.html`/`support.js`/`vendor/*` + over real HTTP requests with zero code changes to `bin/cli.js` (see + `../test_build_e2e.mjs`). +- **Native arm64 build (`pptxdiff-linux-arm64`)**, added after an explicit + follow-up ask to extend the macOS arm64 work to Windows/Linux too. Needs + `--fallback-to-source` (see `../README.md`) since generating V8 bytecode + for a foreign architecture isn't possible without QEMU emulation on the + build host — confirmed directly via a genuine exec-format failure + without the flag. Verified for real in this project's own dev sandbox + (x64): built the `node22-linux-arm64` target via the real production + path (`buildOne()`, real asset config) and confirmed via `file` it's a + genuine `ELF ... ARM aarch64` executable, landing correctly in this + shared folder without disturbing the tracked `README.md`/`CHANGELOG.md`. + Not run (no arm64 emulation available in that sandbox) — real execution + verification is CI's job. + +### Known limitations + +- Not yet attached to GitHub Releases — built by + `.github/workflows/binaries.yml`'s `build-linux-win` job and available + as a workflow artifact. diff --git a/src/packages/binaries/pptxdiff-linux/README.md b/src/packages/binaries/pptxdiff-linux/README.md new file mode 100644 index 0000000..e78686e --- /dev/null +++ b/src/packages/binaries/pptxdiff-linux/README.md @@ -0,0 +1,31 @@ +# pptxdiff for Linux + +This folder holds the built Linux artifacts — not committed here, +generated by `../build.mjs` (see `../README.md`; can be built from any +host OS, `@yao-pkg/pkg` cross-compiles both chip variants). + +After a build, this folder contains one or both of: + +- `pptxdiff-linux` — x64. +- `pptxdiff-linux-arm64` — arm64 (e.g. Raspberry Pi 4/5 running a 64-bit + OS, AWS Graviton, most modern arm64 SBCs/servers). + +If you're not sure which one you need, run `uname -m` — `x86_64` means +`pptxdiff-linux`, `aarch64`/`arm64` means `pptxdiff-linux-arm64`. Both are +true single files: the Node runtime AND the static app files it serves +are both embedded inside them. No separate folder needed alongside +either one. + +Run with `chmod +x && ./` (the build already sets the +executable bit; re-set it if you moved/copied it somewhere that dropped +it). + +Verified end-to-end in this project's own dev sandbox: built for real, +the actual x64 packaged binary was run and confirmed to correctly serve +`index.html`/`support.js`/`vendor/*` over real HTTP requests, with zero +code changes to `bin/cli.js` (see `../test_build_e2e.mjs`). The arm64 +binary was built for real too (confirmed via `file` as a genuine `ELF ... +ARM aarch64` executable) but not run — this sandbox is x64 and has no +QEMU/binfmt arm64 emulation available to execute it. + +To build: `cd src/packages/binaries && npm install && npm run build -- linux linux-arm64`. diff --git a/src/packages/binaries/pptxdiff-mac/CHANGELOG.md b/src/packages/binaries/pptxdiff-mac/CHANGELOG.md new file mode 100644 index 0000000..0eb4c05 --- /dev/null +++ b/src/packages/binaries/pptxdiff-mac/CHANGELOG.md @@ -0,0 +1,49 @@ +# Changelog — pptxdiff for macOS (standalone binaries) + +All notable changes to the macOS standalone `pptxdiff-mac`/ +`pptxdiff-mac-arm64` builds are documented here. The format is based on +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the version +tracked is the `pptxdiff` app version bundled into the binary (see the +root [`CHANGELOG.md`](https://github.com/sugatoray/pptxdiff/blob/master/CHANGELOG.md) for the app's own history) +since the binaries have no independent feature set of their own. + +## [Unreleased] + +... + +## [0.7.0] - 2026-08-05 + +### Added + +- First standalone macOS executables, built via `@yao-pkg/pkg` (see + `../README.md` and `docs/.scrolls/SPEC.md` §36) — download + `pptxdiff-mac` (Intel) or `pptxdiff-mac-arm64` (Apple Silicon, native), + run it. Each is a true single file (Node runtime and the static app + files it serves are both embedded inside it) — no separate Node.js + install, no companion folder needed. +- **Native Apple Silicon (`pptxdiff-mac-arm64`) build**, added after an + explicit follow-up question ("does the mac binary work for Apple + Silicon?"). Without it, an Apple Silicon Mac could only run the Intel + binary via Rosetta 2 translation — real launch overhead, and Rosetta + isn't guaranteed pre-installed on a fresh Mac. Verified for real in this + project's own dev sandbox (Linux): built the `node22-macos-arm64` target + and confirmed via `file` it's a genuine `Mach-O 64-bit arm64 executable` + (unsigned, since built off a non-macOS host — real signing only happens + on the `macos-latest` CI runner or a real Mac). + +### Known limitations + +- **Must be built on an actual macOS host, not cross-compiled.** Unlike + the Windows/Linux targets, both of these need `codesign` (macOS-only) + for a usable result — an unsigned build may not even launch on Apple + Silicon (confirmed independently by `pkg` itself, which prints this + exact warning when building `mac-arm64` off of a non-macOS host). See + `../README.md`. +- **Ad-hoc signed, not notarized.** No Apple Developer ID — Gatekeeper + will likely block a freshly-downloaded copy ("cannot be opened because + the developer cannot be verified"); right-click → Open, or + `xattr -d com.apple.quarantine ` first. See `../README.md` and + `docs/.scrolls/GAP_ANALYSIS.md`. +- Not yet attached to GitHub Releases — built by + `.github/workflows/binaries.yml`'s dedicated `build-mac` job and + available as a workflow artifact. diff --git a/src/packages/binaries/pptxdiff-mac/README.md b/src/packages/binaries/pptxdiff-mac/README.md new file mode 100644 index 0000000..fb81242 --- /dev/null +++ b/src/packages/binaries/pptxdiff-mac/README.md @@ -0,0 +1,36 @@ +# pptxdiff for macOS + +This folder holds the built macOS artifacts — not committed here, +generated by `../build.mjs`. **Must be built on an actual macOS host** +(or `macos-latest` CI runner) — see `../README.md`'s "Why `@yao-pkg/pkg`" +section for why these targets aren't cross-compiled: they need `codesign` +(macOS-only) to be ad-hoc signed, without which the binary may not even +launch on Apple Silicon. + +After a build, this folder contains one or both of: + +- `pptxdiff-mac` — Intel (x64), ad-hoc signed. +- `pptxdiff-mac-arm64` — Apple Silicon (arm64), native, ad-hoc signed. + +**If you're not sure which one you need**: Apple menu → About This Mac → +"Chip" — `Apple M1`/`M2`/`M3`/`M4` (or similar) means download +`pptxdiff-mac-arm64`; `Intel` means download `pptxdiff-mac`. Both are true +single files: the Node runtime AND the static app files served are both +embedded inside them. + +**Ad-hoc signed, not notarized.** There is no Apple Developer ID +certificate for this project, so Gatekeeper will likely block a +freshly-downloaded copy on first launch ("cannot be opened because the +developer cannot be verified") — right-click the binary → Open, or run +`xattr -d com.apple.quarantine ` first. See +`docs/.scrolls/GAP_ANALYSIS.md` for why this is a documented, accepted +tradeoff rather than an oversight. + +To build (on macOS only, for a properly-signed result): +`cd src/packages/binaries && npm install && npm run build -- mac mac-arm64`. +Building either target from Linux/Windows produces a completely unsigned +binary — for `pptxdiff-mac-arm64` specifically, that means it likely won't +launch at all (Apple's AMFI enforcement requires at least an ad-hoc +signature for arm64 executables); the Intel `pptxdiff-mac` is less strict +about this but still untrustworthy to ship unsigned. `build.mjs` warns +loudly if you try either, and `pkg` itself prints the same warning. diff --git a/src/packages/binaries/pptxdiff-win/CHANGELOG.md b/src/packages/binaries/pptxdiff-win/CHANGELOG.md new file mode 100644 index 0000000..5cc8713 --- /dev/null +++ b/src/packages/binaries/pptxdiff-win/CHANGELOG.md @@ -0,0 +1,43 @@ +# Changelog — pptxdiff for Windows (standalone binaries) + +All notable changes to the Windows standalone `pptxdiff-win.exe`/ +`pptxdiff-win-arm64.exe` builds are documented here. The format is based +on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the version +tracked is the `pptxdiff` app version bundled into the binary (see the +root [`CHANGELOG.md`](https://github.com/sugatoray/pptxdiff/blob/master/CHANGELOG.md) for the app's own history) +since the binaries have no independent feature set of their own. + +## [Unreleased] + +... + +## [0.7.0] - 2026-08-05 + +### Added + +- First standalone Windows executable, built via `@yao-pkg/pkg` (see + `../README.md` and `docs/.scrolls/SPEC.md` §36) — download + `pptxdiff-win.exe`, run it. A true single file (Node runtime and the + static app files it serves are both embedded inside it) — no separate + Node.js install, no companion folder needed. +- Genuinely cross-compiled: this binary can be built from any host OS + (Linux, macOS, or Windows), not just Windows itself. +- **Native arm64 build (`pptxdiff-win-arm64.exe`)**, added after an + explicit follow-up ask to extend the macOS arm64 work to Windows/Linux + too. Needs `--fallback-to-source` (see `../README.md`) since generating + V8 bytecode for a foreign architecture isn't possible without QEMU + emulation on the build host — confirmed directly via a genuine + exec-format failure without the flag. Verified for real in this + project's own dev sandbox (x64 Linux): built the `node22-win-arm64` + target and confirmed via `file` it's a genuine `PE32+ ... Aarch64` + executable. + +### Known limitations + +- **Unsigned.** No code-signing certificate — Windows SmartScreen will + likely warn on first run ("Windows protected your PC"); click "More + info" → "Run anyway". See `../README.md` and + `docs/.scrolls/GAP_ANALYSIS.md`. +- Not yet attached to GitHub Releases — built by + `.github/workflows/binaries.yml`'s `build-linux-win` job and available + as a workflow artifact. diff --git a/src/packages/binaries/pptxdiff-win/README.md b/src/packages/binaries/pptxdiff-win/README.md new file mode 100644 index 0000000..68bed32 --- /dev/null +++ b/src/packages/binaries/pptxdiff-win/README.md @@ -0,0 +1,31 @@ +# pptxdiff for Windows + +This folder holds the built Windows artifacts — not committed here, +generated by `../build.mjs` (see `../README.md`; can be built from ANY +host OS, `@yao-pkg/pkg` cross-compiles both chip variants — no Windows +machine needed). + +After a build, this folder contains one or both of: + +- `pptxdiff-win.exe` — x64 (Intel/AMD). +- `pptxdiff-win-arm64.exe` — arm64 (Windows on ARM, e.g. Surface Pro X and + newer Snapdragon-based laptops). + +If you're not sure which one you need, `pptxdiff-win.exe` (x64) is almost +certainly correct — it also runs on most arm64 Windows machines via +built-in x64 emulation, just not natively. Both are true single files: +the Node runtime AND the static app files it serves are both embedded +inside them. No separate folder needed alongside either one, no Node.js +install needed to run them. + +**Unsigned.** There is no code-signing certificate for this project, so +Windows SmartScreen will likely warn on first run ("Windows protected your +PC") — click "More info" → "Run anyway". See +`docs/.scrolls/GAP_ANALYSIS.md` for why this is a documented, accepted +tradeoff rather than an oversight. + +To build: `cd src/packages/binaries && npm install && npm run build -- win win-arm64` +(works from Linux, macOS, or Windows — both targets are genuinely +cross-compiled; the arm64 one needs `--fallback-to-source`, already baked +into `build.mjs`, since cross-arch V8 bytecode generation isn't possible +without QEMU emulation on the build host). diff --git a/src/packages/binaries/test_build_config.mjs b/src/packages/binaries/test_build_config.mjs new file mode 100644 index 0000000..ee9277e --- /dev/null +++ b/src/packages/binaries/test_build_config.mjs @@ -0,0 +1,152 @@ +#!/usr/bin/env node +"use strict"; + +// Fast, pure regression checks for build.mjs's build CONFIGURATION — no +// real pkg invocation, no network, no subprocess beyond reading files. +// Complements test_build_e2e.mjs (real build + real run, slow). Same +// fast/slow split as pptxdiff-cli's `npm test` vs `npm run test:difftool`. +// +// Run: node test_build_config.mjs + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { TARGET_MAP, ASSET_GLOBS, resolveTarget } from "./build.mjs"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = path.resolve(__dirname, "..", "..", ".."); + +let pass = 0; +let fail = 0; +function assert(name, cond) { + if (cond) { + pass++; + } else { + fail++; + console.error(`FAIL: ${name}`); + } +} + +// --- TARGET_MAP / resolveTarget --- +const EXPECTED_KEYS = ["linux", "linux-arm64", "mac", "mac-arm64", "win", "win-arm64"]; +assert(`TARGET_MAP has exactly the six expected keys (got ${JSON.stringify(Object.keys(TARGET_MAP).sort())})`, ( + JSON.stringify(Object.keys(TARGET_MAP).sort()) === JSON.stringify(EXPECTED_KEYS) +)); +assert("linux target: node22-linux-x64, no .exe suffix, own outDirKey, doesn't need mac signing", ( + TARGET_MAP.linux.pkgTarget === "node22-linux-x64" && !TARGET_MAP.linux.binName.includes(".") && TARGET_MAP.linux.outDirKey === "linux" && TARGET_MAP.linux.needsMacSign === false +)); +assert("linux-arm64 target: node22-linux-arm64, shares linux's outDirKey, distinct binName, doesn't need mac signing", ( + TARGET_MAP["linux-arm64"].pkgTarget === "node22-linux-arm64" && TARGET_MAP["linux-arm64"].outDirKey === "linux" && TARGET_MAP["linux-arm64"].binName !== TARGET_MAP.linux.binName && TARGET_MAP["linux-arm64"].needsMacSign === false +)); +assert("win target: node22-win-x64, binName ends .exe, own outDirKey, doesn't need mac signing", ( + TARGET_MAP.win.pkgTarget === "node22-win-x64" && TARGET_MAP.win.binName.endsWith(".exe") && TARGET_MAP.win.outDirKey === "win" && TARGET_MAP.win.needsMacSign === false +)); +assert("win-arm64 target: node22-win-arm64, binName ends .exe, shares win's outDirKey, distinct binName, doesn't need mac signing", ( + TARGET_MAP["win-arm64"].pkgTarget === "node22-win-arm64" && TARGET_MAP["win-arm64"].binName.endsWith(".exe") && TARGET_MAP["win-arm64"].outDirKey === "win" && TARGET_MAP["win-arm64"].binName !== TARGET_MAP.win.binName && TARGET_MAP["win-arm64"].needsMacSign === false +)); +assert("mac target: node22-macos-x64, needsMacSign true (the whole reason it's built separately in CI)", ( + TARGET_MAP.mac.pkgTarget === "node22-macos-x64" && TARGET_MAP.mac.needsMacSign === true +)); +assert("mac-arm64 target: node22-macos-arm64, needsMacSign true, binName distinct from the x64 one", ( + TARGET_MAP["mac-arm64"].pkgTarget === "node22-macos-arm64" && TARGET_MAP["mac-arm64"].needsMacSign === true && TARGET_MAP["mac-arm64"].binName !== TARGET_MAP.mac.binName +)); +assert("mac and mac-arm64 share the SAME outDirKey (both download from pptxdiff-mac/)", ( + TARGET_MAP.mac.outDirKey === "mac" && TARGET_MAP["mac-arm64"].outDirKey === "mac" +)); +assert("only the two mac targets need signing — every linux/win target (x64 or arm64) does not", ( + ["linux", "linux-arm64", "win", "win-arm64"].every((k) => TARGET_MAP[k].needsMacSign === false) +)); +assert("resolveTarget('linux') === TARGET_MAP.linux", resolveTarget("linux") === TARGET_MAP.linux); +assert("resolveTarget('mac-arm64') === TARGET_MAP['mac-arm64']", resolveTarget("mac-arm64") === TARGET_MAP["mac-arm64"]); +assert("resolveTarget('win-arm64') === TARGET_MAP['win-arm64']", resolveTarget("win-arm64") === TARGET_MAP["win-arm64"]); +assert("resolveTarget returns null for an unknown osKey", resolveTarget("solaris") === null); +assert("resolveTarget returns null for an empty string", resolveTarget("") === null); +assert("every TARGET_MAP entry declares an outDirKey", Object.values(TARGET_MAP).every((t) => typeof t.outDirKey === "string" && t.outDirKey.length > 0)); +assert("every TARGET_MAP binName is unique (no two targets would collide in the same outDir)", ( + new Set(Object.values(TARGET_MAP).map((t) => t.outDirKey + "/" + t.binName)).size === Object.keys(TARGET_MAP).length +)); + +// --- ASSET_GLOBS drift guard: must match root package.json's "files" --- +// (same fixture-drift-check concern this project already tracks elsewhere +// — see GAP_ANALYSIS.md's "Fixture drift-check" ticket — applied here so +// the asset set a packaged binary embeds can never silently diverge from +// what the npm package itself ships.) +const rootPkg = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, "package.json"), "utf8")); +const npmStaticFiles = rootPkg.files.filter((f) => f.startsWith("src/pptxdiff/")); +// ASSET_GLOBS uses a "/**/*" suffix for the vendor directory (a pkg glob +// requirement — bare "vendor" alone does not recurse); normalize before +// comparing against package.json's plain-directory "vendor" entry. +const assetGlobsNormalized = ASSET_GLOBS.map((g) => g.replace(/\/\*\*\/\*$/, "")).sort(); +assert( + `ASSET_GLOBS (normalized) match root package.json "files" static app subset (got ${JSON.stringify(assetGlobsNormalized)} vs ${JSON.stringify([...npmStaticFiles].sort())})`, + JSON.stringify(assetGlobsNormalized) === JSON.stringify([...npmStaticFiles].sort()) +); +assert("every ASSET_GLOBS entry's literal (non-glob) prefix exists on disk", ( + ASSET_GLOBS.every((g) => fs.existsSync(path.join(REPO_ROOT, g.replace(/\/\*\*\/\*$/, "")))) +)); + +// --- pkg's config-colocation requirement (see WISDOM.md's trap entry): --- +// buildOne() MUST write its temp pkg config directly at REPO_ROOT (next to +// the real package.json) — pkg resolves "assets" glob paths relative to +// wherever the CONFIG FILE ITSELF lives, not cwd, not the entry file's +// directory. A regression here fails SILENTLY at build time (pkg embeds +// zero assets, no error) and only shows up as 404s when the binary is +// actually run — exactly the kind of bug this static check exists to +// catch before it ever reaches test_build_e2e.mjs. +const buildSrc = fs.readFileSync(path.join(__dirname, "build.mjs"), "utf8"); +assert("buildOne() writes its temp pkg config at REPO_ROOT, not __dirname or cwd", ( + /tmpConfigPath\s*=\s*path\.join\(REPO_ROOT,/.test(buildSrc) +)); +assert("buildOne() removes the temp pkg config in a finally block", ( + /finally\s*\{[^}]*rmSync\(tmpConfigPath/.test(buildSrc) +)); +assert("ASSET_GLOBS are relative (repo-root-relative) paths, not absolute", ( + ASSET_GLOBS.every((g) => !path.isAbsolute(g)) +)); +assert("buildOne() computes outDir from target.outDirKey, not the osKey argument (so mac/mac-arm64 share pptxdiff-mac/)", ( + /outDir\s*=\s*path\.join\(__dirname, `pptxdiff-\$\{target\.outDirKey\}`\)/.test(buildSrc) +)); +// A cross-ARCH build (e.g. linux-arm64/win-arm64 from an x64 host) fails +// outright without this flag — confirmed directly, a genuine exec-format +// error trying to run a foreign-arch bytecode-generation helper. Losing +// this flag would silently break EVERY arm64 target's build (pkg errors +// out instead of falling back to plain-source shipping), the same +// "passes on x64, breaks only for arm64" blind spot the config-colocation +// gotcha above already represents for a different reason. +assert("buildOne()'s pkg invocation includes --fallback-to-source (required for cross-arch builds)", ( + /"--fallback-to-source"/.test(buildSrc) +)); + +// --- bin/cli.js is passed to pkg UNMODIFIED — no assets-folder workaround --- +// This is the whole point of switching to pkg (see GAP_CONTEXT.md): the +// packaged binary should need zero special-casing in bin/cli.js itself. +const cliSrc = fs.readFileSync(path.join(REPO_ROOT, "bin", "cli.js"), "utf8"); +assert("bin/cli.js's startServer() takes no parameters (no packaging-specific root override)", ( + /function startServer\(\) \{/.test(cliSrc) +)); +assert("bin/cli.js still exports startServer for pptxdiff-cli's reuse", ( + /module\.exports\s*=\s*\{[^}]*startServer[^}]*\}/.test(cliSrc) +)); +assert("build.mjs points pkg directly at the real bin/cli.js (no wrapper entry file)", ( + /path\.join\(REPO_ROOT, "bin", "cli\.js"\)/.test(buildSrc) +)); + +// --- macOS signing safety: never silently ship an unsigned mac binary --- +assert("buildOne() warns explicitly when building the mac target off of a non-darwin host", ( + /WARNING.*UNSIGNED/.test(buildSrc) +)); +assert("buildOne() only runs codesign when process.platform === \"darwin\"", ( + /process\.platform === "darwin"/.test(buildSrc) +)); + +// --- package.json devDependency present --- +const binPkg = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8")); +assert("package.json devDependencies includes @yao-pkg/pkg", Boolean(binPkg.devDependencies && binPkg.devDependencies["@yao-pkg/pkg"])); + +console.log(`build-config check: ${pass}/${pass + fail} passed`); +if (fail > 0) { + console.error(`${fail} check(s) FAILED (RED).`); + process.exitCode = 1; +} else { + console.log("All build-config checks passed (GREEN)."); +} diff --git a/src/packages/binaries/test_build_e2e.mjs b/src/packages/binaries/test_build_e2e.mjs new file mode 100644 index 0000000..46e5d84 --- /dev/null +++ b/src/packages/binaries/test_build_e2e.mjs @@ -0,0 +1,164 @@ +#!/usr/bin/env node +"use strict"; + +// Real, slow, current-platform-only end-to-end check: actually calls +// buildOne() for the CURRENT host's OS (the same code `node build.mjs` +// runs), then spawns the REAL resulting single-file executable and drives +// it over real HTTP. Deliberately kept out of the default `npm test` (a +// real pkg build downloads/uses a base binary and takes a while) — run via +// `npm run test:e2e`, same split as pptxdiff-cli's `test:difftool`. +// +// Only exercises the CURRENT host's own platform+arch target — every other +// target is structurally identical (same buildOne(), only the mac +// codesign branch differs) but only actually built-and-run by CI's +// linux+win / macos-specific jobs (see .github/workflows/binaries.yml and +// build.mjs's header comment for why neither mac target is cross-built +// there). Picks the `-arm64` variant of whatever OS it's running on when +// the HOST's actual `os.arch()` is arm64, so an Apple-Silicon macOS +// runner (GitHub's macos-latest, as of 2024) or an arm64 Linux/Windows +// runner genuinely exercises the native build, not the x64 one. +// +// Run: node test_build_e2e.mjs + +import { execFile } from "node:child_process"; +import fs from "node:fs"; +import http from "node:http"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { buildOne, resolveTarget } from "./build.mjs"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +function hostOsKey() { + const isArm64 = os.arch() === "arm64"; + if (process.platform === "win32") return isArm64 ? "win-arm64" : "win"; + if (process.platform === "linux") return isArm64 ? "linux-arm64" : "linux"; + if (process.platform === "darwin") return isArm64 ? "mac-arm64" : "mac"; + return null; +} + +let pass = 0; +let fail = 0; +function assert(name, cond) { + if (cond) { + pass++; + console.log(`ok - ${name}`); + } else { + fail++; + console.error(`FAIL: ${name}`); + } +} + +function fetchText(url) { + return new Promise((resolve, reject) => { + http + .get(url, (res) => { + let body = ""; + res.on("data", (c) => (body += c)); + res.on("end", () => resolve({ status: res.statusCode, headers: res.headers, body })); + }) + .on("error", reject); + }); +} + +function waitForLine(child, matcher, timeoutMs) { + return new Promise((resolve, reject) => { + let buf = ""; + const onData = (chunk) => { + buf += chunk.toString(); + const m = buf.match(matcher); + if (m) { + cleanup(); + resolve(m); + } + }; + const timer = setTimeout(() => { + cleanup(); + reject(new Error(`Timed out waiting for ${matcher} in output:\n${buf}`)); + }, timeoutMs); + function cleanup() { + clearTimeout(timer); + child.stdout.off("data", onData); + } + child.stdout.on("data", onData); + }); +} + +async function main() { + const osKey = hostOsKey(); + const target = osKey && resolveTarget(osKey); + if (!target) { + console.error(`No target mapping for process.platform=${process.platform}/${os.arch()} — nothing to e2e-test here.`); + process.exitCode = 1; + return; + } + + console.log(`Building a real ${osKey} binary via @yao-pkg/pkg (this takes a while, downloads a base binary on first run)...`); + const binPath = await buildOne(osKey, target); + + assert("build produced the binary file", fs.existsSync(binPath)); + assert("no separate assets/ folder needed (pkg embeds them in the one file)", !fs.existsSync(path.join(path.dirname(binPath), "assets"))); + if (!target.binName.endsWith(".exe")) { + const mode = fs.statSync(binPath).mode; + assert("binary is executable (owner +x bit set)", Boolean(mode & 0o100)); + } + + // Actually run the packaged binary and talk to it over real HTTP — proves + // pkg's snapshot filesystem genuinely satisfies bin/cli.js's UNMODIFIED + // `ROOT = path.join(__dirname, "..", "src", "pptxdiff")` resolution, not + // just that files exist somewhere inside the binary. + const child = execFile(binPath, { cwd: os.tmpdir(), env: {} }); + let urlMatch; + try { + urlMatch = await waitForLine(child, /pptxdiff running at (http:\/\/localhost:\d+)/, 20000); + } catch (e) { + console.error("Binary never printed its startup line:", e.message); + child.kill(); + fail++; + reportAndExit(); + return; + } + const baseUrl = urlMatch[1]; + assert("binary printed a startup URL", Boolean(baseUrl)); + + try { + const index = await fetchText(`${baseUrl}/`); + assert("GET / returns 200", index.status === 200); + assert("GET / body looks like the real app shell", index.body.includes("") && index.body.includes('src="./support.js"')); + + const supportJs = await fetchText(`${baseUrl}/support.js`); + assert("GET /support.js returns 200", supportJs.status === 200); + assert("GET /support.js has JS content-type", (supportJs.headers["content-type"] || "").includes("javascript")); + + const vendorFile = await fetchText(`${baseUrl}/vendor/react.production.min.js`); + assert("GET /vendor/react.production.min.js returns 200 (embedded vendor/ assets are actually being served)", vendorFile.status === 200); + + const traversal = await fetchText(`${baseUrl}/../../../etc/passwd`); + assert("path traversal against the packaged binary is rejected (never 200)", traversal.status !== 200); + } finally { + child.kill(); + } + + reportAndExit(); + + function reportAndExit() { + console.log(`build-e2e check: ${pass}/${pass + fail} passed`); + if (fail > 0) { + console.error(`${fail} check(s) FAILED (RED).`); + process.exitCode = 1; + } else { + console.log("All build-e2e checks passed (GREEN)."); + } + // Clean up the built binary afterward — this test's job is to prove + // the build+run path works, not to leave a ~70MB+ binary lying around. + // Only removes the binary itself, never the tracked README.md/ + // CHANGELOG.md that live in the same per-OS folder. + fs.rmSync(binPath, { force: true }); + } +} + +main().catch((e) => { + console.error(e); + process.exitCode = 1; +}); diff --git a/src/pptxdiff/docs-site/CHANGELOG.md b/src/pptxdiff/docs-site/CHANGELOG.md index d41b709..594f39b 100644 --- a/src/pptxdiff/docs-site/CHANGELOG.md +++ b/src/pptxdiff/docs-site/CHANGELOG.md @@ -35,6 +35,22 @@ No changes yet. ### Changed - `index.md`'s Install tabs gained a "Homebrew" option; `getting-started.md` gained "Option D — Homebrew" (intro sentence reworded from "three equivalent ways" to "several," so it won't need editing again next time); `architecture.md`'s packaging-layers diagram now shows five surfaces instead of four; `limitations.md` gained two new rows (tap doesn't exist yet; not verified against real `brew` locally, with the real root cause explained). +## 2026-08-05 — Content updates + new changelog subpage for standalone native binaries + +### Added + +- `getting-started.md`: new "Option E — standalone binary (no Node.js at all)" section (rebased after master's Homebrew work claimed "Option D" first); `cli.md` gained a one-paragraph cross-link to it. +- `limitations.md`: new row for the standalone binaries' own limitations (unsigned/ad-hoc-signed, not on GitHub Releases yet). +- New changelog subpage `changelogs/pptxdiff-binaries.md`, added to nav — structurally different from every other subpage since it transcludes THREE per-OS `CHANGELOG.md` files (no single package-level one exists for this package) under `## Windows` / `## macOS` / `## Linux` subheadings. +- New `documentation-coverage.md` registry entries: `native-binaries` (feature, `partial`) and `native-binaries-limitations` (limitation, `complete`). + +### Fixed + +- The three per-OS `CHANGELOG.md` files' relative link back to the root `CHANGELOG.md` broke once transcluded into the new changelog subpage (`pymdownx.snippets` doesn't rewrite relative links for their new location) — caught by `mkdocs build --strict`, fixed by swapping to an absolute GitHub URL at the source. + +### Changed + +- `index.md`'s "No install required" card now mentions the binaries option. See `docs/.scrolls/DOCS.md` §15 for the full reasoning. diff --git a/src/pptxdiff/docs-site/docs/changelog.md b/src/pptxdiff/docs-site/docs/changelog.md index 8d08224..12bdaa3 100644 --- a/src/pptxdiff/docs-site/docs/changelog.md +++ b/src/pptxdiff/docs-site/docs/changelog.md @@ -11,6 +11,7 @@ title: Changelog - [Changelog: pptxdiff](changelogs/pptxdiff.md) - [Changelog: @pptxdiff/cli](changelogs/pptxdiff-cli.md) - [Changelog: @pptxdiff/server](changelogs/pptxdiff-server.md) +- [Changelog: @pptxdiff/binaries](changelogs/pptxdiff-binaries.md) ## VS Code Extension(s) diff --git a/src/pptxdiff/docs-site/docs/changelogs/pptxdiff-binaries.md b/src/pptxdiff/docs-site/docs/changelogs/pptxdiff-binaries.md new file mode 100644 index 0000000..55a1695 --- /dev/null +++ b/src/pptxdiff/docs-site/docs/changelogs/pptxdiff-binaries.md @@ -0,0 +1,25 @@ +--- +title: "Changelog: @pptxdiff/binaries" +--- + +# Changelog: @pptxdiff/binaries + +Unlike this site's other package changelogs, `@pptxdiff/binaries` has no single package-level `CHANGELOG.md` — each OS's standalone binary has its own, since they're independent downloadable artifacts. + +## Windows + +Source: [`src/packages/binaries/pptxdiff-win/CHANGELOG.md`](https://github.com/sugatoray/pptxdiff/blob/master/src/packages/binaries/pptxdiff-win/CHANGELOG.md) + +--8<-- "src/packages/binaries/pptxdiff-win/CHANGELOG.md" + +## macOS + +Source: [`src/packages/binaries/pptxdiff-mac/CHANGELOG.md`](https://github.com/sugatoray/pptxdiff/blob/master/src/packages/binaries/pptxdiff-mac/CHANGELOG.md) + +--8<-- "src/packages/binaries/pptxdiff-mac/CHANGELOG.md" + +## Linux + +Source: [`src/packages/binaries/pptxdiff-linux/CHANGELOG.md`](https://github.com/sugatoray/pptxdiff/blob/master/src/packages/binaries/pptxdiff-linux/CHANGELOG.md) + +--8<-- "src/packages/binaries/pptxdiff-linux/CHANGELOG.md" diff --git a/src/pptxdiff/docs-site/docs/cli.md b/src/pptxdiff/docs-site/docs/cli.md index 30424e1..6e355d6 100644 --- a/src/pptxdiff/docs-site/docs/cli.md +++ b/src/pptxdiff/docs-site/docs/cli.md @@ -24,6 +24,8 @@ npm install -g pptxdiff && pptxdiff # global install There are no command-line flags. One optional environment variable is supported — see [Lite mode](#lite-mode-cdn-sourcing) below. +**No Node.js at all?** A standalone native executable per OS+chip runs this exact, unmodified `bin/cli.js` with the Node runtime embedded inside it — see [Getting Started → Option E](getting-started.md#option-e-standalone-binary-no-nodejs-at-all). + ## What it does `bin/cli.js` is a stdlib-only Node script (`node:http` / `node:fs` / `node:child_process`, no npm dependencies): diff --git a/src/pptxdiff/docs-site/docs/documentation-coverage.md b/src/pptxdiff/docs-site/docs/documentation-coverage.md index c516eae..6e9cea1 100644 --- a/src/pptxdiff/docs-site/docs/documentation-coverage.md +++ b/src/pptxdiff/docs-site/docs/documentation-coverage.md @@ -2,21 +2,21 @@ title: Documentation Coverage render_macros: true coverage_summary: - generated_at: '2026-08-05T08:54:58Z' + generated_at: '2026-08-09T03:15:30Z' totals: overall: - items: 40 - complete: 36 - partial: 4 + items: 42 + complete: 37 + partial: 5 missing: 0 feature: - items: 26 + items: 27 complete: 22 - partial: 4 + partial: 5 missing: 0 limitation: - items: 14 - complete: 14 + items: 15 + complete: 15 partial: 0 missing: 0 items: @@ -123,6 +123,13 @@ coverage_summary: locations: - page: features/exports.md anchor: live-push + - id: native-binaries + kind: feature + title: Standalone native binaries (@pptxdiff/binaries, x64+arm64 for win/mac/linux) + quality: partial + locations: + - page: getting-started.md + anchor: option-e-standalone-binary-no-nodejs-at-all - id: npm-cli-packaging kind: feature title: npm CLI (bin/cli.js) @@ -293,6 +300,14 @@ coverage_summary: locations: - page: limitations.md anchor: null + - id: native-binaries-limitations + kind: limitation + title: 'Standalone binaries: unsigned/ad-hoc-signed, macOS not cross-compiled, + not on GitHub Releases' + quality: complete + locations: + - page: limitations.md + anchor: null - id: offline-capability-limitations kind: limitation title: Offline-capability limitations (pdfjs-dist unvendored, latin-only font diff --git a/src/pptxdiff/docs-site/docs/getting-started.md b/src/pptxdiff/docs-site/docs/getting-started.md index b98c346..62ad9fe 100644 --- a/src/pptxdiff/docs-site/docs/getting-started.md +++ b/src/pptxdiff/docs-site/docs/getting-started.md @@ -11,6 +11,9 @@ doc_coverage: - id: homebrew-formula quality: partial anchor: option-d-homebrew + - id: native-binaries + quality: partial + anchor: option-e-standalone-binary-no-nodejs-at-all --- # Getting started @@ -53,6 +56,33 @@ pptxdiff Same behavior as Option B once installed — the formula packages the identical published npm tarball, just via `brew` instead of `npm`. Not yet in a real `brew tap` (so no plain `brew install pptxdiff` yet — the `--formula ` form above works today without one); see the [Homebrew formula](homebrew.md) page for full details and status. +## Option E — standalone binary (no Node.js at all) + +```bash +./pptxdiff-linux # or pptxdiff-mac / pptxdiff-mac-arm64 / pptxdiff-win.exe, etc. +``` + +A native, standalone executable per OS+chip — download one file from a +[GitHub Actions build](https://github.com/sugatoray/pptxdiff/actions/workflows/binaries.yml) +and run it directly. No Node.js install of any kind, not even the `npx`/`npm` +step Options A/B still need. Six targets exist (x64 and arm64 for each of +Windows, macOS, and Linux) — see the [`@pptxdiff/binaries` package +README](https://github.com/sugatoray/pptxdiff/blob/master/src/packages/binaries/README.md) +for exactly which file to pick and how to build them yourself. + +This is a genuine single file (the Node runtime and the app's static assets +are both embedded inside it via [`@yao-pkg/pkg`](https://github.com/yao-pkg/pkg)) +running the exact same, unmodified `bin/cli.js` Options A/B run — same local +server, same loopback binding, same [security properties](cli.md#security-note). + +!!! warning "Not code-signed" + There's no code-signing certificate for this project yet, so Windows + SmartScreen and macOS Gatekeeper will both warn on a freshly-downloaded + copy — see each OS folder's own README for how to proceed anyway + (`pptxdiff-{win,mac,linux}/README.md` in the repo). Not attached to a + tagged GitHub Release yet either; download from the Actions run's + workflow artifacts for now. + ## What happens on first load The app ships a built-in **sample deck** — `sample-pptx.js` generates a Before/After pair on load — so you can try every feature immediately, with nothing to upload. Drop in your own `.pptx` pair whenever you're ready, or click **Reset to sample** to go back to the demo data. @@ -61,7 +91,7 @@ The app ships a built-in **sample deck** — `sample-pptx.js` generates a Before ## Requirements -- **Node.js ≥ 18** if you're using the `npx`/`npm` install paths (see `engines` in `package.json`). +- **Node.js ≥ 18** if you're using the `npx`/`npm` install paths (see `engines` in `package.json`). Not needed at all for Option E (standalone binary) — the Node runtime is embedded inside it. - **No internet connection required, for any install option.** React, ReactDOM, Babel-standalone, `@aiden0z/pptx-renderer`, JSZip, and fonts are all vendored locally under `src/pptxdiff/vendor/` and loaded from disk, not from a CDN. The app works fully offline/air-gapped by default — an opt-in `PPTXDIFF_LITE_MODE` switches back to CDN sourcing if you ever want that; see the [CLI reference](cli.md#lite-mode-cdn-sourcing). - **Your `.pptx` files never leave your machine.** Parsing, rendering, and diffing all happen client-side, in your browser's memory. diff --git a/src/pptxdiff/docs-site/docs/index.md b/src/pptxdiff/docs-site/docs/index.md index 5e28f04..75e8176 100644 --- a/src/pptxdiff/docs-site/docs/index.md +++ b/src/pptxdiff/docs-site/docs/index.md @@ -45,7 +45,7 @@ It's a **single self-contained HTML file**. No server, no build step, no cloud u --- - Open `index.html` directly, or run it with `npx pptxdiff`. + Open `index.html` directly, run it with `npx pptxdiff`, or download a standalone native binary — no Node.js needed for that last one. [:octicons-arrow-right-24: Getting started](getting-started.md) diff --git a/src/pptxdiff/docs-site/docs/limitations.md b/src/pptxdiff/docs-site/docs/limitations.md index 4edf43a..6ee88f1 100644 --- a/src/pptxdiff/docs-site/docs/limitations.md +++ b/src/pptxdiff/docs-site/docs/limitations.md @@ -28,6 +28,8 @@ doc_coverage: quality: complete - id: homebrew-formula-limitations quality: complete + - id: native-binaries-limitations + quality: complete --- # Known limitations @@ -48,7 +50,8 @@ These are documented, accepted trade-offs — not bugs waiting to be fixed. Each | **Batch filename-similarity pairing uses an untuned threshold** | The bigram Dice-coefficient cutoff (0.3) for filename-based pairing hasn't been validated against real-world messy filename patterns, and isn't user-adjustable. See [Batch mode](features/batch-mode.md). | | **On-disk sample fixtures don't cover everything the in-browser sample does** | `docs/assets/sample_*.pptx` (generated by `pptxgenjs`) don't exercise SmartArt/diagrams, slide transitions, embedded fonts, or real video/audio media — `pptxgenjs` has no API surface for any of these. The in-browser "Reset to sample" deck still covers all four, for its own narrower purpose (feeding this app's own parser/renderer, never downloaded as a real file). | | **English-only UI, no full accessibility audit** | `aria-label`s exist on the highest-value icon-only controls and toasts are screen-reader-friendly, but there's no localization and no full keyboard-navigation/focus-ring audit. | -| **The npm CLI opens a browser tab, not a native app window** | No dock/taskbar icon, no standalone app process — `bin/cli.js` is a static file server, not an Electron/Tauri wrapper. See [CLI reference](cli.md). | +| **The npm CLI (and the standalone binaries) open a browser tab, not a native app window** | No dock/taskbar icon, no standalone app process — `bin/cli.js` is a static file server, not an Electron/Tauri wrapper, and the standalone binaries run that exact same server. See [CLI reference](cli.md). | +| **Standalone binaries are unsigned/ad-hoc-signed, and not yet on GitHub Releases** | No code-signing certificate exists for this project — Windows SmartScreen and macOS Gatekeeper both warn on a freshly-downloaded copy. Only available as CI workflow artifacts today, not attached to a tagged release. macOS specifically can't be cross-compiled from Linux/Windows for this reason (an unsigned binary may not even launch on Apple Silicon) — it builds on a real `macos-latest` runner instead. See [Getting Started → Option E](getting-started.md#option-e-standalone-binary-no-nodejs-at-all). | | **Offline vendoring has two known gaps** | `pdfjs-dist` (an optional peer dependency of the rendering library, used only for embedded-PDF-object rendering) isn't vendored — this was never functional even before the app went offline-capable. The vendored Spectral font ships only its latin subset (matching the English-only UI); non-Latin text falls back to the browser's default font. See [Architecture](architecture.md#runtime-dependencies-vendored-locally). | | **`PPTXDIFF_LITE_MODE` is all 5 dependencies at once** | No way to mix vendored and CDN sourcing per-dependency (e.g. vendored React with CDN `pptx-renderer`) without hand-editing the source. See [CLI reference](cli.md#lite-mode-cdn-sourcing). | | **The headless CLI/API aren't published, and `batch`/`report`/`merge` aren't built yet** | `pptxdiff-cli`/`@pptxdiff/server` are source-only (`file:` dependencies on their monorepo siblings) — install from a checkout, not `npm install`. `diff`/`checksum`/git integration (`textconv`/`difftool`/`install-git-integration`) ship; `batch`/`report`/`merge` and server authentication for a non-loopback bind are designed but not built. Every invocation currently pays a real headless-browser boot cost (no native, browser-free engine yet). See [Headless CLI & Web API](headless-cli-api.md). | diff --git a/src/pptxdiff/docs-site/mkdocs.yml b/src/pptxdiff/docs-site/mkdocs.yml index 2a2219a..cdcbd09 100644 --- a/src/pptxdiff/docs-site/mkdocs.yml +++ b/src/pptxdiff/docs-site/mkdocs.yml @@ -124,6 +124,7 @@ nav: - "Changelog: pptxdiff": changelogs/pptxdiff.md - "Changelog: @pptxdiff/cli": changelogs/pptxdiff-cli.md - "Changelog: @pptxdiff/server": changelogs/pptxdiff-server.md + - "Changelog: @pptxdiff/binaries": changelogs/pptxdiff-binaries.md - VS Code Extension(s): - "Changelog: pptxdiff-vscode": changelogs/pptxdiff-vscode.md - Homebrew Formula(s): diff --git a/src/pptxdiff/docs-site/scripts/coverage_registry.yml b/src/pptxdiff/docs-site/scripts/coverage_registry.yml index 79ace1a..945f027 100644 --- a/src/pptxdiff/docs-site/scripts/coverage_registry.yml +++ b/src/pptxdiff/docs-site/scripts/coverage_registry.yml @@ -84,6 +84,9 @@ features: - id: homebrew-formula title: "Homebrew formula for pptxdiff" source: SPEC.md §32-§33 + - id: native-binaries + title: "Standalone native binaries (@pptxdiff/binaries, x64+arm64 for win/mac/linux)" + source: SPEC.md §36 limitations: - id: rendering-limitations @@ -128,3 +131,6 @@ limitations: - id: homebrew-formula-limitations title: "Homebrew formula: not tapped anywhere yet, not verified against real brew locally" source: GAP_ANALYSIS.md "Packaging" + - id: native-binaries-limitations + title: "Standalone binaries: unsigned/ad-hoc-signed, macOS not cross-compiled, not on GitHub Releases" + source: GAP_ANALYSIS.md "Standalone native binaries"