From 7bbc3efc5a01528882c11d4ddea00e05255e755d Mon Sep 17 00:00:00 2001 From: Devin Oldenburg Date: Fri, 17 Jul 2026 16:48:08 +0200 Subject: [PATCH 1/2] Document macOS support and harden npm pack checks Add supported-platforms docs, mark the package as darwin-only, and verify every runtime import is present in the packed tarball before publish. --- README.md | 4 ++- docs/supported-platforms.md | 26 ++++++++++++++++++++ package.json | 10 +++++--- scripts/check-package.mjs | 49 +++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 docs/supported-platforms.md create mode 100644 scripts/check-package.mjs diff --git a/README.md b/README.md index 7e053a8..97767dc 100644 --- a/README.md +++ b/README.md @@ -273,10 +273,12 @@ A live single-line progress indicator runs on stderr during interactive sessions ## Requirements -- macOS 27 or newer (Apple's `fm` CLI is preinstalled). +- macOS 27.0 or newer (Apple's `fm` CLI is preinstalled there). - Node.js 20 or newer. - Apple Intelligence enabled on the device. +Benchmark commands refuse to start on older macOS versions and report the detected version plus the latest supported macOS — see [docs/supported-platforms.md](docs/supported-platforms.md). + `pcc` (Private Cloud Compute) availability depends on Apple's current eligibility. `fm-bench` shows it as skipped if `fm available --model pcc` reports unavailable. See [docs/methodology.md](docs/methodology.md) for benchmark methodology and metric references. diff --git a/docs/supported-platforms.md b/docs/supported-platforms.md new file mode 100644 index 0000000..fb2f823 --- /dev/null +++ b/docs/supported-platforms.md @@ -0,0 +1,26 @@ +# Supported platforms + +`fm-bench` benchmarks Apple's `fm` command, so support follows the platforms where Apple ships that CLI. + +## Requirements + +- **macOS 27.0 or newer** — Apple's `fm` CLI is preinstalled starting with macOS 27. Older macOS releases do not ship `fm`, so `fm-bench` cannot run there. +- **Node.js 20 or newer**. +- **Apple Intelligence enabled** on the device. + +`pcc` (Private Cloud Compute) availability additionally depends on Apple's current eligibility. `fm-bench` reports it as skipped when `fm available --model pcc` reports unavailable. + +## Version enforcement + +Every command that launches `fm` — the default `run` benchmark, `models`, and `doctor` — checks the macOS version first and refuses to start on anything older than macOS 27: + +```text +fm-bench: unsupported macOS: detected macOS 26.1, but fm-bench requires macOS 27 or newer (Apple's fm CLI is preinstalled there). +Latest supported: macOS 27.0 or newer (fm is not available on older macOS releases). +``` + +The process exits with code `2`. This is deliberate: running on an unsupported macOS could never produce a valid benchmark, so the CLI fails fast with the exact version it found and the latest supported macOS version. + +Commands that only read local report files — `compare`, `history`, `validate`, `export`, and `legend` — are not gated and work anywhere Node.js runs. + +`fm-bench doctor` prints the detected macOS version and, on unsupported systems, an explicit `macOS support` line plus the latest supported version. diff --git a/package.json b/package.json index 25a0b4f..8c4d301 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,12 @@ "scripts": { "test": "node --test", "lint": "node --check bin/fm-bench.js && find src test -name '*.js' -print0 | xargs -0 -n1 node --check", - "prepack": "npm test && npm run lint", + "prepack": "npm test && npm run lint && npm run check:pack", "release:patch": "npm version patch && git push --follow-tags", "release:minor": "npm version minor && git push --follow-tags", "release:major": "npm version major && git push --follow-tags", - "publish:dry-run": "npm publish --dry-run --access public" + "publish:dry-run": "npm publish --dry-run --access public", + "check:pack": "node scripts/check-package.mjs" }, "keywords": [ "apple", @@ -46,5 +47,8 @@ "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" - } + }, + "os": [ + "darwin" + ] } diff --git a/scripts/check-package.mjs b/scripts/check-package.mjs new file mode 100644 index 0000000..fe42462 --- /dev/null +++ b/scripts/check-package.mjs @@ -0,0 +1,49 @@ +#!/usr/bin/env node +/** + * Verify the packed npm tarball includes every runtime file. + * Runtime imports outside bin/, src/, and package.json are refused so a + * missing "files" entry cannot ship a broken release. + */ +import { execFileSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +const root = join(import.meta.dirname, '..'); +const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')); + +// --ignore-scripts: this script runs from `prepack`, so a nested pack that +// ran lifecycle scripts would recurse forever. +const pack = JSON.parse(execFileSync('npm', ['pack', '--dry-run', '--json', '--ignore-scripts'], { cwd: root, encoding: 'utf8' })); +const files = new Set(pack[0].files.map((file) => file.path)); + +const missing = []; +const check = (path, reason) => { + if (!files.has(path)) missing.push(`${path} (${reason})`); +}; + +const entry = pkg.bin['fm-bench']; +check(entry, 'bin entry'); + +const seen = new Set(); +const queue = [entry]; +const importRe = /from\s+['"](\.\.?\/[^'"]+)['"]/g; +while (queue.length > 0) { + const current = queue.pop(); + if (seen.has(current)) continue; + seen.add(current); + const source = readFileSync(join(root, current), 'utf8'); + for (const match of source.matchAll(importRe)) { + const resolved = join(current, '..', match[1]).replace(/\\/g, '/'); + queue.push(resolved); + check(resolved, `imported by ${current}`); + } +} + +if (!files.has('package.json')) missing.push('package.json'); + +if (missing.length > 0) { + console.error(`Packed tarball is missing runtime files:\n ${missing.join('\n ')}`); + process.exit(1); +} + +console.log(`check:pack ok — ${seen.size} runtime module(s) all present in tarball (${files.size} files total).`); From 7503dafd347056692840ab80775e2f25ea8441e9 Mon Sep 17 00:00:00 2001 From: Devin Oldenburg Date: Fri, 17 Jul 2026 16:49:32 +0200 Subject: [PATCH 2/2] Align supported-platforms docs with doctor diagnostics Doctor remains available on older macOS so it can report the latest supported version; document check:pack in the release guide. --- docs/releasing.md | 3 ++- docs/supported-platforms.md | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/releasing.md b/docs/releasing.md index 071a9b7..058216f 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -17,7 +17,7 @@ ## Option B — Local ```sh -npm ci && npm test && npm run lint +npm ci && npm test && npm run lint && npm run check:pack npm version patch # or minor / major git push --follow-tags ``` @@ -37,6 +37,7 @@ gh release edit v0.6.0 --notes-file /tmp/notes.md --repo devinoldenburg/fm-bench ```sh npm run publish:dry-run +npm run check:pack ``` CI runs the same dry-run on every push to `main` and on pull requests. \ No newline at end of file diff --git a/docs/supported-platforms.md b/docs/supported-platforms.md index fb2f823..5eb1942 100644 --- a/docs/supported-platforms.md +++ b/docs/supported-platforms.md @@ -12,7 +12,7 @@ ## Version enforcement -Every command that launches `fm` — the default `run` benchmark, `models`, and `doctor` — checks the macOS version first and refuses to start on anything older than macOS 27: +Commands that launch `fm` benchmarks — the default `run` command and `models` — check the macOS version first and refuse to start on anything older than macOS 27: ```text fm-bench: unsupported macOS: detected macOS 26.1, but fm-bench requires macOS 27 or newer (Apple's fm CLI is preinstalled there). @@ -23,4 +23,4 @@ The process exits with code `2`. This is deliberate: running on an unsupported m Commands that only read local report files — `compare`, `history`, `validate`, `export`, and `legend` — are not gated and work anywhere Node.js runs. -`fm-bench doctor` prints the detected macOS version and, on unsupported systems, an explicit `macOS support` line plus the latest supported version. +`fm-bench doctor` still runs on unsupported hosts so you can diagnose the environment: it prints the detected macOS version, an explicit `macOS support` line, and the latest supported version.