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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,12 @@ jobs:
- run: DOCX_LO_ALL=1 bun run test:integration # full fixture sweep in CI

build-binary:
name: Smoke-build compiled binary
runs-on: ubuntu-latest
name: Smoke-build compiled binary (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-15-intel, macos-latest]
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
Expand All @@ -83,5 +87,12 @@ jobs:
restore-keys: bun-${{ runner.os }}-
- run: bun install --frozen-lockfile
- run: bun run build:binary
- name: Sign and verify macOS binary
if: runner.os == 'macOS'
run: sh scripts/sign-macos-binary.sh ./dist/docx
- name: Verify binary runs
run: ./dist/docx --version
run: |
./dist/docx --version
./dist/docx --help > /dev/null
./dist/docx read tests/fixtures/markdown-import.docx > /dev/null
./dist/docx validate tests/fixtures/markdown-import.docx
15 changes: 14 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
include:
- { os: ubuntu-latest, target: bun-linux-x64, name: docx-linux-x64 }
- { os: ubuntu-latest, target: bun-linux-arm64, name: docx-linux-arm64 }
- { os: macos-latest, target: bun-darwin-x64, name: docx-darwin-x64 }
- { os: macos-15-intel, target: bun-darwin-x64, name: docx-darwin-x64 }
- { os: macos-latest, target: bun-darwin-arm64, name: docx-darwin-arm64 }
- { os: windows-latest, target: bun-windows-x64, name: docx-windows-x64.exe }
steps:
Expand All @@ -32,6 +32,17 @@ jobs:
- run: bun install --frozen-lockfile
- name: Compile
run: bun build --compile --target=${{ matrix.target }} --outfile=${{ matrix.name }} src/index.ts
# Sign after compilation (and any future binary rewriting), before upload.
- name: Sign and verify macOS artifact
if: runner.os == 'macOS'
run: sh scripts/sign-macos-binary.sh "./${{ matrix.name }}"
- name: Smoke-test macOS artifact natively
if: runner.os == 'macOS'
run: |
./${{ matrix.name }} --version
./${{ matrix.name }} --help > /dev/null
./${{ matrix.name }} read tests/fixtures/markdown-import.docx > /dev/null
./${{ matrix.name }} validate tests/fixtures/markdown-import.docx
- name: Smoke-test render on Linux artifact
if: matrix.os == 'ubuntu-latest' && matrix.target == 'bun-linux-x64'
run: |
Expand Down Expand Up @@ -67,6 +78,8 @@ jobs:
# the logic, published two ways. See the installer invariant in CLAUDE.md.
run: cp skills/docx-cli/scripts/install.sh ./artifacts/
- name: Generate SHA256SUMS for the release assets
# Hash the final signed bytes downloaded from the build jobs. Never modify
# a binary after this step.
# Published as a release asset so install.sh / bootstrap.sh can verify the
# downloaded binary's integrity, and so install.sh itself can be verified
# before it is run (supply-chain hardening).
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ When a feature's natural brute-force path already works for weak agents (verifie

- `bun run build` → `dist/index.js` — bundled JS that npm publishes (runs under Bun). **Required**: path aliases (`@core/*`) and JSX runtime resolution don't work when consumed from `node_modules`; the bundle pre-resolves everything. Never ship raw `src/`.
- `bun run build:binary` → `dist/docx` — standalone executable for GitHub Releases.
- macOS CI/release builds run `sh scripts/sign-macos-binary.sh BINARY` after all binary modifications, then native version/help/read/validate smoke checks. Signing and strict verification must precede upload; release checksums cover the final signed bytes.

## Docs layout

Expand Down
16 changes: 14 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ bun run check # biome + knip + tsc
bun run test:unit # core + cli tests (fast)
bun run test:integration # LibreOffice round-trip (needs `soffice` on PATH)
bun test # everything
bun run build # produce dist/docx via bun build --compile
bun run build # produce dist/index.js (npm bundle)
bun run build:binary # produce dist/docx via bun build --compile
```

### LibreOffice (for integration tests)
Expand Down Expand Up @@ -102,6 +103,17 @@ GitHub Actions (`.github/workflows/ci.yml`) runs four jobs on push to `main` and
| `check` | `biome check . && knip-bun && tsc --noEmit` |
| `unit-tests` | `bun run test:unit` (core + cli, fast) |
| `integration-tests` | Installs LibreOffice, runs `bun run test:integration` |
| `build-binary` | Smoke-builds via `bun build --compile` and runs `--version` |
| `build-binary` | Builds on Linux and native Intel/ARM macOS; signs/verifies macOS binaries; runs version/help, read, and schema-validation smoke checks |

`.github/workflows/release.yml` triggers on `v*` tags, matrix-builds the five binaries, and uploads them to a GitHub Release via [`softprops/action-gh-release`](https://github.com/softprops/action-gh-release).

macOS release binaries are ad-hoc signed with `sh scripts/sign-macos-binary.sh BINARY`
after compilation and any other binary modifications. The script supplies Bun's
JavaScriptCore entitlements and requires strict signature verification to pass.
Both macOS release jobs run the artifact natively before upload. `SHA256SUMS` is
then generated from the downloaded, signed artifacts; never modify binaries after
signing or checksum generation. The same signing and smoke checks run on PRs.
To reproduce locally on macOS, run `bun run build:binary`, then
`sh scripts/sign-macos-binary.sh ./dist/docx` and the `build-binary` smoke commands
in `.github/workflows/ci.yml`. These checks cover the runner's macOS version;
compatibility with a newer macOS release needs a separate run on that release.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ shasum -a 256 -c SHA256SUMS --ignore-missing # or: sha256sum -c …
sh install.sh
```

macOS artifacts built by the current release workflow are ad-hoc signed and signature-verified before checksumming; they are not Developer ID-signed or notarized.

Honors `PREFIX` (default `$HOME/.local/bin`) and `VERSION` (default `latest`). Pre-built for linux/x64, linux/arm64, darwin/x64, darwin/arm64, windows/x64.

Once installed, **`docx upgrade`** updates a standalone binary — it replaces the binary wherever it already lives (`PREFIX` does not apply), running the same installer embedded in the binary at build time rather than fetched, so the download stays pinned to a release tag and SHA-256-verified. `--to v0.23.0` pins a version, `--dry-run` reports what would change. On an npm/bun install it tells you to use the package manager instead.
Expand Down
5 changes: 5 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ On a standalone-binary install, run **`docx upgrade`** — it replaces the binar

Every release publishes a `SHA256SUMS` manifest alongside the prebuilt binaries. There is one installer — `install.sh` — published two ways: as a release asset, and inside the skill folder where `scripts/bootstrap.sh` delegates to it. It downloads only release **assets** (never a script), tries `sha256sum`, `shasum`, then `openssl`, and **verifies the binary against the manifest**, aborting on a mismatch or a missing manifest.

macOS binaries built by the current release workflow are ad-hoc signed and
strictly signature-verified before upload; `SHA256SUMS` covers those final signed
bytes. Ad-hoc signing provides executable integrity, not Developer ID identity
or Apple notarization.

The one behavioral difference is a parameter, not a second code path — what to do on a machine with no checksum tool at all:

| | Invoked by | No checksum tool available |
Expand Down
1 change: 1 addition & 0 deletions scripts/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- [move.ts](move.ts) — move a TS file and auto-update imports (TS LanguageService-driven).
- [escape-check.ts](escape-check.ts), [fxp-smoke.ts](fxp-smoke.ts), [jsx-smoke.tsx](jsx-smoke.tsx) — ad-hoc probes for XML escaping, fast-xml-parser behavior, and the JSX runtime.
- [word-redlines.sh](word-redlines.sh) — AppleScript oracle that drives Microsoft Word to produce ground-truth track-changes XML (referenced from [src/core/track-changes/replace.tsx](../src/core/track-changes/replace.tsx) and [src/core/notes/CLAUDE.md](../src/core/notes/CLAUDE.md)).
- [sign-macos-binary.sh](sign-macos-binary.sh) — final ad-hoc signing and strict verification for macOS CI/release executables; [macos-entitlements.plist](macos-entitlements.plist) carries Bun's documented JavaScriptCore permissions. Run only after all binary modifications.
- `data/` — sample `.docx` files used for one-off inspection (not test fixtures; those live in [tests/fixtures/](../tests/fixtures/)).

Fixture builders moved to [tests/fixtures/setup/](../tests/fixtures/setup/CLAUDE.md).
18 changes: 18 additions & 0 deletions scripts/macos-entitlements.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<!-- Bun's recommended executable entitlements:
https://bun.sh/guides/runtime/codesign-macos-executable -->
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
14 changes: 14 additions & 0 deletions scripts/sign-macos-binary.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh
# Run after all binary modifications; upload/hash only the resulting signed bytes.
set -eu

if [ "$#" -ne 1 ]; then
echo "Usage: sh scripts/sign-macos-binary.sh BINARY" >&2
exit 2
fi

script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
# Explicit entitlements retain Bun's JavaScriptCore JIT permissions, even when
# the compiler's original signature is absent or invalid. No signing key needed.
codesign --force --sign - --entitlements "$script_dir/macos-entitlements.plist" "$1"
codesign --verify --strict --verbose=2 "$1"
27 changes: 27 additions & 0 deletions skills/docx-cli/references/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,33 @@ It installs to `~/.local/bin/docx` by default. Make sure that directory is on
your `PATH` (`export PATH="$HOME/.local/bin:$PATH"`). Set `PREFIX=/usr/local/bin`
before the install to choose another location.

## macOS kills the standalone binary at startup

An invalid code signature can cause a startup kill (reported for v0.25.0 on
macOS 27). Verify the actual standalone executable, not a version-manager shim:

```sh
# For a mise-managed installation:
codesign --verify --strict --verbose=2 "$(mise which docx)"
# For a standalone binary directly on PATH (not a shim):
codesign --verify --strict --verbose=2 "$(command -v docx)"
```

With Bun installed, the package is a fallback. Run its explicit path so an
older mise installation or shim earlier on PATH cannot shadow it:

```sh
bun add -g bun-docx
"$(bun pm bin -g)/docx" --version
```

Use that explicit path for subsequent commands, or adjust PATH and check
`command -v docx` before using the bare command. A binary that cannot start
cannot run `docx upgrade`; reinstall externally when a fixed release is available.
The updated release workflow ad-hoc signs and verifies macOS binaries before
generating `SHA256SUMS`; existing release assets are unchanged. Ad-hoc signing
does not provide Developer ID identity or Apple notarization.

## `docx render` fails or hangs

`render` is the **only** command that needs an external app. Everything else
Expand Down
Loading