@a11ign/screenreader-fleet

中文文档
0.13.3发布于 2026/10/25AGPL-3.0-or-laterTypeScriptES Module构建来源已验证近 30 天下载 5934

Host-side lifecycle, health and capacity for a fleet of Windows NVDA capture workers: lease one, judge whether it is degrading, and know how many the host can afford.

原始使用文档
文档目录7 节
  1. A lease, not a lifecycle you own
  2. Capacity is measured, never assumed
  3. Health: watch recoveries , not failures
  4. The provisioning scripts ship with the package
  5. Working here
  6. What did not come across: 27 tests
  7. Releasing

@a11ign/screenreader-fleet

Host-side lifecycle, health and capacity for a fleet of Windows NVDA capture workers. Runs on the machine that drives the workers — no NVDA, no guidepup, no Windows.

npm install @a11ign/screenreader-fleet
npx a11ign-doctor          # can I run right now? every check names its own fix
import { leaseWorker } from "@a11ign/screenreader-fleet";

const lease = await leaseWorker({ after: "stop" });
try {
  // ... capture against lease.url
} finally {
  await lease.release();   // puts the VM back the way it was found
}

A lease, not a lifecycle you own

leaseWorker decides what to capture against in priority order: an explicit worker you named, then inventory.yml's bare-metal fleet if one is declared, then a local UTM VM (deprecated — "The UTM is deprecated, that was a testing thing," repository owner, 2026-09-05), then the historical http://localhost:8765 default. Reaching the UTM branch prints a warning naming fleet:* as the replacement; it is not refused outright because some machines still only have a UTM guest to reach, and deleting the ~2,460 lines that manage it is a separate decision from warning about it. Only the VM case has a lifecycle to manage — a bare-metal box is always on — so release() is a no-op for every other source. It starts what is missing and puts a VM back as it found it: one that was already running is left running, a stopped one is stopped again. A long run must not shut down something another run is using.

Stopped worker VMs are the correct resting state. all stopped is a READY state, not a fault — a11ign-doctor says so explicitly, because "go and start a worker" is the wrong instinct and costs time.

The local UTM VM is not how this project's own corpus is captured. It is the DEPRECATED default for a solo contributor with no bare-metal fleet, kept working precisely because it is still the right path for that case — this repo's own corpus is captured on ten bare-metal workers driven by inventory.yml, which leaseWorker checks first. If you are consuming this package standalone with your own Windows box, the VM lifecycle below still applies to you exactly as written.

Capacity is measured, never assumed

import { availableHostMemoryMb, workersHostCanRun } from "@a11ign/screenreader-fleet/capacity";

A worker VM costs the host ~8 GB, not the 4 GB it is configured with — QEMU's overhead on top of guest RAM that Windows dirties and never returns. So three do not fit on a 36 GB Mac, and over-committing does not merely slow a run: the same page took 44.5 s with three guests up and 27.4 s with one, and the starved guests produced "NVDA is running but not speaking" failures and /health blackouts. From outside that reads as the workers are degrading, which is how it was misdiagnosed for a day.

Two rules follow, both learned the hard way:

  • Never os.freemem(). It reported 402 MB on a host with ~12 GB to give, because macOS counts compressed and inactive pages as used. The reading comes from vm_stat.
  • vm_stat is distorted by the very condition it must detect — a swapped-out guest's pages count as available, so the estimate rises as the host gets sicker; it advertised 13.7 GB free while two guests were starving. The cap is therefore the lower of that estimate and a ceiling derived from physical RAM, which no feedback loop can move.

Health: watch recoveries, not failures

import { assessWorker } from "@a11ign/screenreader-fleet/health";

The worst worker fault this fleet has had produced zero failures. One guest's NVDA went mute on 4 of 4 captures, the worker's own retry absorbed every one, so every capture succeeded and the eviction rule — three consecutive failures — could never fire. The only symptom was 122.9 s per capture against a healthy peer's 40.6 s.

vitals.recoveries counts the faults a worker papered over. It is the number that rises while everything still appears to work.

The provisioning scripts ship with the package

import { fleetScriptPaths } from "@a11ign/screenreader-fleet";
fleetScriptPaths().workerCtl;   // absolute path to worker-ctl.sh

They are shell, so a consumer spawns them, and they are resolved from the module rather than the cwd — a cwd-relative path is right exactly when the cwd is the repo root. Getting that wrong is not subtle: during this extraction doctor reported "no local VM tooling here" on a host with three registered VMs.

macOS + UTM is what the VM lifecycle assumes. utmctl needs the UTM app running, or a perfectly healthy VM reports its state as unknown; and UTM cannot suspend a guest with an emulated NVMe device, so stop/start is the only real lifecycle — a cold boot to ready is 15–45 s, which is fine.

Not exported: host-metrics, worker-stats, fleet-consistency. They are measurement internals whose shapes change every time something new gets measured.

Working here

This repository is the package: package.json, src/ and this README sit at the root, and the README is the npm page. It moved here from a11ign/a11ign with its history. It depends on @a11ign/screenreader-worker and @a11ign/judge by name, from the registry.

The fleet drives workers that have no authentication. Anyone who can reach a worker's port can drive the browser and the screen reader on that machine (SECURITY.md in a11ign/a11ign says what else somebody must know first). Run it only on a network you control.

pnpm install --frozen-lockfile
pnpm test        # what the `gate` check runs on every pull request and merge-queue entry, with lint, typecheck and layout-check

main takes pull requests only, each with one approving review, through the merge queue. pnpm exec layout-check is the repository-layout check from @a11ign/toolchain: it fails a workspace of one package, a directory not named for its package, a second README and a leftover lerna.json.

What did not come across: 27 tests

The package's test directory was written for the monorepo, and 27 of its 53 test files read something that is not in this repository: packages/control's playbooks and inventories, packages/lab's capture clients, the private guards package, or the root's scripts/. Run here, they fail on a file they cannot find, so they were not carried into this repository; they stay in a11ign/a11ign, beside what they read, until its row #3504 relocates them. Their names are the COUPLED list in packages/lab/src/packaging/screenreader-fleet-extraction.test.ts there. The other 26 run here and are what gate runs, with scripts/package-boundary.test.ts holding the package to its own directory.

Releasing

This repository releases on its own, not with a11ign/a11ign. A change that should reach npm carries a changeset (pnpm exec changeset), and merging it to main is the release: .github/workflows/release.yml calls the one reusable per-merge workflow in a11ign/toolchain, which versions the merge on a detached commit, publishes, tags and releases it. There is no version pull request and nothing is written to main, so its package.json version and CHANGELOG.md lag the last tag (.changeset/README.md). The publish uses npm trusted publishing over OIDC with provenance and no stored token. The first release, 0.1.0, was published before this workflow existed; no git tag names it, so the first per-merge release bases on main's 0.1.0.

版本与发布记录

@a11ign/screenreader-fleet 共发布 28 个版本,首次发布于 2026/10/04;过去 12 个月发布了 28 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.13.3latest2026/10/2513 天前
0.13.22026/10/2414 天前
0.13.12026/10/1523 天前
0.13.02026/10/1523 天前
0.12.02026/10/1325 天前
0.11.02026/10/1325 天前

其他发布通道:reserved → 0.0.0-reserved.0。

关于 @a11ign/screenreader-fleet 的常见问题

@a11ign/screenreader-fleet 是什么?

Host-side lifecycle, health and capacity for a fleet of Windows NVDA capture workers: lease one, judge whether it is degrading, and know how many the host can afford.

如何安装 @a11ign/screenreader-fleet?

在项目目录运行 npm install @a11ign/screenreader-fleet,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 a11ign-doctor、a11ign-guest-run、a11ign-worker-ctl、a11ign-worker-code、a11ign-worker-compare、a11ign-normalise-fleet,可以用 npx a11ign-doctor 直接运行。

@a11ign/screenreader-fleet 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@a11ign/screenreader-fleet 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@a11ign/screenreader-fleet 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.15.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@a11ign/screenreader-fleet 可以商用吗?

@a11ign/screenreader-fleet 使用 AGPL-3.0-or-later 许可。最严格的 Copyleft:除分发外,通过网络向用户提供基于它的服务也需要开源对应源码。闭源 SaaS 使用前务必评估。以上为概括,不构成法律意见,请以许可原文为准。

@a11ign/screenreader-fleet 的下载量有多少?

根据 npm 官方下载统计,@a11ign/screenreader-fleet 在 2026/10/05 – 2026/11/03 这 30 天内被下载 5,934 次,日均约 198 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@a11ign/screenreader-fleet 还在维护吗?

最新版本 0.13.3 发布于 2026/10/25(13 天前);至今共发布 28 个版本,其中过去 12 个月发布了 28 个;首次发布于 2026/10/04。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。