@a11ign/screenreader-fleet 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -93,3 +93,40 @@ the only real lifecycle — a cold boot to ready is 15–45 s, which is fine.
93
93
 
94
94
  Not exported: `host-metrics`, `worker-stats`, `fleet-consistency`. They are measurement internals whose shapes
95
95
  change every time something new gets measured.
96
+
97
+ ## Working here
98
+
99
+ 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
100
+ [`a11ign/a11ign`](https://github.com/a11ign/a11ign) with its history. It depends on
101
+ [`@a11ign/screenreader-worker`](https://github.com/a11ign/screenreader-worker) and `@a11ign/judge` **by name, from the registry**.
102
+
103
+ **The fleet drives workers that have no authentication.** Anyone who can reach a worker's port can drive the browser and the screen
104
+ reader on that machine (`SECURITY.md` in `a11ign/a11ign` says what else somebody must know first). Run it only on a network you control.
105
+
106
+ ```bash
107
+ pnpm install --frozen-lockfile
108
+ pnpm test # what the `gate` check runs on every pull request and merge-queue entry, with lint, typecheck and layout-check
109
+ ```
110
+
111
+ `main` takes pull requests only, each with one approving review, through the merge queue. `pnpm exec layout-check` is the repository-layout
112
+ check from `@a11ign/toolchain`: it fails a workspace of one package, a directory not named for its package, a second README and a leftover
113
+ `lerna.json`.
114
+
115
+ ### What did not come across: 27 tests
116
+
117
+ 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:
118
+ `packages/control`'s playbooks and inventories, `packages/lab`'s capture clients, the private `guards` package, or the root's
119
+ `scripts/`. Run here, they fail on a file they cannot find, so they were **not** carried into this repository; they stay in
120
+ `a11ign/a11ign`, beside what they read, until its row #3504 relocates them. Their names are the `COUPLED` list in
121
+ `packages/lab/src/packaging/screenreader-fleet-extraction.test.ts` there. The other 26 run here and are what `gate` runs, with
122
+ `scripts/package-boundary.test.ts` holding the package to its own directory.
123
+
124
+ ### Releasing
125
+
126
+ This repository releases on its own, not with `a11ign/a11ign`. A change that should reach npm carries a changeset
127
+ (`pnpm exec changeset`), and **merging it to `main` is the release**: `.github/workflows/release.yml` calls the one
128
+ reusable per-merge workflow in `a11ign/toolchain`, which versions the merge on a detached commit, publishes, tags and
129
+ releases it. There is no version pull request and nothing is written to `main`, so its `package.json` version and
130
+ `CHANGELOG.md` lag the last tag (`.changeset/README.md`). The publish uses npm trusted publishing over OIDC with
131
+ provenance and no stored token. The first release, 0.1.0, was published before this workflow existed;
132
+ no git tag names it, so the first per-merge release bases on `main`'s 0.1.0.
package/dist/doctor.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { execFile, execFileSync } from "node:child_process";
3
3
  import { promisify } from "node:util";
4
4
  import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
5
- import { resolve } from "node:path";
5
+ import { join, resolve } from "node:path";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
7
7
  import { homedir } from "node:os";
8
8
  import { createRequire } from "node:module";
@@ -361,7 +361,7 @@ async function checkDegradedWorkers(probed) {
361
361
  for (const w of probed){
362
362
  if (!w.health) continue;
363
363
  const { degraded, reason } = assessWorker(w.health.vitals);
364
- if (degraded) add(`worker ${w.name}`, true, `DEGRADED — ${reason}`, `re-provision ${w.name}: packages/worker-fleet/src/provisioning/provision-nvda-worker.ps1, elevated, in the interactive session`);
364
+ if (degraded) add(`worker ${w.name}`, true, `DEGRADED — ${reason}`, `re-provision ${w.name}: ${join(fleetScriptPaths().provisioning, "provision-nvda-worker.ps1")}, elevated, in the interactive session`);
365
365
  }
366
366
  }
367
367
  function checkFleetConsistency(probed, configured) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Every non-test source file under `packages/`, as `[relativePath, source]`.
2
+ * Every non-test source file under the repository root, as `[relativePath, source]`.
3
3
  *
4
4
  * @param {{ root?: string }} [options]
5
5
  * @returns {Array<[string, string]>}
@@ -7,5 +7,5 @@
7
7
  export function sourceFiles({ root }?: {
8
8
  root?: string;
9
9
  }): Array<[string, string]>;
10
- /** The `packages/` directory, resolved from this module rather than from the caller's cwd. */
11
- export const PACKAGES: string;
10
+ /** The repository root, resolved from this module rather than from the caller's cwd. */
11
+ export const REPOSITORY: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a11ign/screenreader-fleet",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "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.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -73,9 +73,16 @@
73
73
  },
74
74
  "devDependencies": {
75
75
  "@a11ign/evidence": "0.1.0",
76
- "@a11ign/toolchain": "0.1.2",
76
+ "@a11ign/toolchain": "0.1.5",
77
+ "@changesets/cli": "3.0.3",
78
+ "@eslint/js": "^10.0.1",
77
79
  "@rslib/core": "1.0.3",
80
+ "@rstest/core": "0.12.3",
81
+ "@types/node": "^26.6.4",
82
+ "eslint": "^10.12.0",
83
+ "globals": "^17.13.0",
78
84
  "typescript": "^6.0.3",
85
+ "typescript-eslint": "^8.71.1",
79
86
  "yaml": "^2.9.1"
80
87
  },
81
88
  "engines": {
@@ -84,11 +91,6 @@
84
91
  "publishConfig": {
85
92
  "access": "public"
86
93
  },
87
- "repository": {
88
- "type": "git",
89
- "url": "git+https://github.com/a11ign/screenreader-fleet.git",
90
- "directory": "packages/worker-fleet"
91
- },
92
94
  "homepage": "https://github.com/a11ign/screenreader-fleet",
93
95
  "keywords": [
94
96
  "accessibility",
@@ -99,7 +101,14 @@
99
101
  "utm",
100
102
  "vm"
101
103
  ],
104
+ "repository": {
105
+ "type": "git",
106
+ "url": "git+https://github.com/a11ign/screenreader-fleet.git"
107
+ },
102
108
  "scripts": {
103
- "build": "rslib build"
109
+ "build": "rslib build",
110
+ "lint": "eslint .",
111
+ "typecheck": "tsc --noEmit",
112
+ "test": "rstest run"
104
113
  }
105
114
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # Build a local NVDA capture worker VM on Apple Silicon, unattended.
3
3
  #
4
- # ./packages/worker-fleet/src/local-worker/build-vm.sh /path/to/Win11_ARM64.iso
4
+ # ./src/local-worker/build-vm.sh /path/to/Win11_ARM64.iso
5
5
  #
6
6
  # Produces a self-contained VM directory (default ~/a11y-worker-vm) holding the disk
7
7
  # image, UEFI vars and a run script. That directory IS the portable artifact: copy it
@@ -40,7 +40,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
40
40
  die() { echo "error: $*" >&2; exit 1; }
41
41
  info() { echo "==> $*"; }
42
42
 
43
- [ -n "$WIN_ISO" ] || die "usage: $0 <windows-11-arm64.iso> (build one with CrystalFetch, or packages/worker-fleet/src/local-worker/fetch-windows-iso.sh)"
43
+ [ -n "$WIN_ISO" ] || die "usage: $0 <windows-11-arm64.iso> (build one with CrystalFetch, or src/local-worker/fetch-windows-iso.sh)"
44
44
  [ -f "$WIN_ISO" ] || die "not found: $WIN_ISO"
45
45
  command -v qemu-system-aarch64 >/dev/null || die "qemu missing: brew install qemu"
46
46
  command -v qemu-img >/dev/null || die "qemu-img missing: brew install qemu"
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # Clone the local NVDA worker VM into an additional, independent worker.
3
3
  #
4
- # ./packages/worker-fleet/src/local-worker/clone-worker.sh [new-name] # default: a11y-worker-2
4
+ # ./src/local-worker/clone-worker.sh [new-name] # default: a11y-worker-2
5
5
  #
6
6
  # One worker serves one capture at a time by design (one desktop, one foreground window, one
7
7
  # NVDA), so throughput scales by running more of them. On APFS the clone is copy-on-write, so
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # Create the NVDA worker VM in UTM, fully from the CLI. No GUI clicking.
3
3
  #
4
- # ./packages/worker-fleet/src/local-worker/create-utm-vm.sh <windows-arm64.iso> [support.iso]
4
+ # ./src/local-worker/create-utm-vm.sh <windows-arm64.iso> [support.iso]
5
5
  #
6
6
  # Why UTM rather than plain QEMU: homebrew QEMU + HVF cannot boot Windows 11 ARM64 on
7
7
  # Apple Silicon (open upstream bug, https://gitlab.com/qemu-project/qemu/-/issues/2893,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # Build an official Windows 11 ARM64 ISO on macOS, from the CLI.
3
3
  #
4
- # ./packages/worker-fleet/src/local-worker/fetch-windows-iso.sh [outdir]
4
+ # ./src/local-worker/fetch-windows-iso.sh [outdir]
5
5
  #
6
6
  # Microsoft's ARM64 ISO download is a session-token web flow that does not script
7
7
  # cleanly, so this uses UUP dump: it fetches the same Unified Update Platform packages
@@ -16,7 +16,7 @@ set -euo pipefail
16
16
 
17
17
  # architecture-audit.md §8: builds an ISO for a local UTM worker VM, which is deprecated -- "The UTM is
18
18
  # deprecated, that was a testing thing." (repository owner, 2026-09-05). Bare-metal boxes install via
19
- # PXE/autounattend.xml instead — see packages/worker-fleet/src/provisioning/bare-metal/.
19
+ # PXE/autounattend.xml instead — see src/provisioning/bare-metal/.
20
20
  echo "DEPRECATED: fetch-windows-iso.sh feeds a local UTM worker VM build. UTM was a testing path and is not the fleet." >&2
21
21
  echo "Capture on the bare-metal fleet instead: npm run fleet:status, npm run fleet:deploy." >&2
22
22
 
@@ -100,7 +100,7 @@ cd "$OUT_DIR"
100
100
  EXISTING="$(ls -t "$OUT_DIR"/*.ISO "$OUT_DIR"/*.iso 2>/dev/null | grep -vi support | head -1 || true)"
101
101
  if [ -n "$EXISTING" ] && xorriso -indev "$EXISTING" -report_el_torito plain 2>/dev/null | grep -q UEFI; then
102
102
  echo "ISO ready (already built): $EXISTING"
103
- echo "Next: ./packages/worker-fleet/src/local-worker/create-utm-vm.sh \"$EXISTING\""
103
+ echo "Next: ./src/local-worker/create-utm-vm.sh \"$EXISTING\""
104
104
  exit 0
105
105
  fi
106
106
  if [ -n "$EXISTING" ]; then
@@ -234,5 +234,5 @@ fi
234
234
 
235
235
  echo
236
236
  echo "ISO ready: $ISO"
237
- echo "Next: ./packages/worker-fleet/src/local-worker/build-vm.sh \"$ISO\""
238
- echo " ./packages/worker-fleet/src/local-worker/create-utm-vm.sh \"$ISO\""
237
+ echo "Next: ./src/local-worker/build-vm.sh \"$ISO\""
238
+ echo " ./src/local-worker/create-utm-vm.sh \"$ISO\""
@@ -3,20 +3,20 @@
3
3
  # while you are not capturing.
4
4
  #
5
5
  # npm run worker:ctl -- up # make it ready (start or resume), wait for /health
6
- # ./packages/worker-fleet/src/local-worker/worker-ctl.sh pause # freeze it: ~0.6% CPU, instant resume, RAM not guaranteed
7
- # ./packages/worker-fleet/src/local-worker/worker-ctl.sh stop # shut it down: nothing held, ~15 s to come back
8
- # ./packages/worker-fleet/src/local-worker/worker-ctl.sh status # state, resource use, health
9
- # ./packages/worker-fleet/src/local-worker/worker-ctl.sh json # the same, machine-readable (used by the CLI)
10
- # ./packages/worker-fleet/src/local-worker/worker-ctl.sh pool # every a11y-worker* VM, as JSON
11
- # ./packages/worker-fleet/src/local-worker/worker-ctl.sh pool-up # start them all, wait for health
12
- # ./packages/worker-fleet/src/local-worker/worker-ctl.sh pool-stop # shut the whole pool down
13
- # ./packages/worker-fleet/src/local-worker/worker-ctl.sh pool-pause # freeze the whole pool
6
+ # ./src/local-worker/worker-ctl.sh pause # freeze it: ~0.6% CPU, instant resume, RAM not guaranteed
7
+ # ./src/local-worker/worker-ctl.sh stop # shut it down: nothing held, ~15 s to come back
8
+ # ./src/local-worker/worker-ctl.sh status # state, resource use, health
9
+ # ./src/local-worker/worker-ctl.sh json # the same, machine-readable (used by the CLI)
10
+ # ./src/local-worker/worker-ctl.sh pool # every a11y-worker* VM, as JSON
11
+ # ./src/local-worker/worker-ctl.sh pool-up # start them all, wait for health
12
+ # ./src/local-worker/worker-ctl.sh pool-stop # shut the whole pool down
13
+ # ./src/local-worker/worker-ctl.sh pool-pause # freeze the whole pool
14
14
  #
15
15
  # One VM serves one capture at a time, so throughput comes from more VMs. `pool` reports the
16
16
  # lot; add one with clone-worker.sh (which handles the duplicate-MAC trap).
17
17
  # Operate on a single named VM with A11Y_VM_NAME=a11y-worker-2.
18
- # ./packages/worker-fleet/src/local-worker/worker-ctl.sh idle-pause 15 # watch, then pause after 15 idle minutes
19
- # ./packages/worker-fleet/src/local-worker/worker-ctl.sh idle-stop 30 # same but shut down instead
18
+ # ./src/local-worker/worker-ctl.sh idle-pause 15 # watch, then pause after 15 idle minutes
19
+ # ./src/local-worker/worker-ctl.sh idle-stop 30 # same but shut down instead
20
20
  #
21
21
  # Measured on an M4 Max, 4 vCPU / 8 GB guest. Every number here was observed on this
22
22
  # machine; none is an estimate:
@@ -117,7 +117,7 @@ resolve_uuid() {
117
117
  [ -n "${A11Y_VM_UUID:-}" ] && { echo "$A11Y_VM_UUID"; return; }
118
118
  local matches
119
119
  matches="$(utmctl list | awk -v n="$VM_NAME" '$3 == n { print $1 }')"
120
- [ -n "$matches" ] || die "no VM named '$VM_NAME' (create one: packages/worker-fleet/src/local-worker/create-utm-vm.sh)"
120
+ [ -n "$matches" ] || die "no VM named '$VM_NAME' (create one: src/local-worker/create-utm-vm.sh)"
121
121
  if [ "$(echo "$matches" | wc -l | tr -d ' ')" -gt 1 ]; then
122
122
  # Do NOT guess, and do NOT suggest deleting one. Duplicate registrations under the same
123
123
  # name point at the SAME <name>.utm bundle, so `utmctl delete` on either removes that
@@ -317,7 +317,7 @@ case "$CMD" in
317
317
  echo " it is down for 5-10s during a restart; if it persists:" >&2
318
318
  echo " utmctl exec <uuid> --cmd powershell.exe -NoProfile -Command 'Start-ScheduledTask -TaskName a11ysrv'" >&2
319
319
  else
320
- # `$0` is this file's path, which after M6 is `packages/worker-fleet/src/local-worker/worker-ctl.sh` — true,
320
+ # `$0` is this file's path, which after M6 is `src/local-worker/worker-ctl.sh` — true,
321
321
  # and not what anyone wants to type. The npm alias is the stable way to say it, and it is what the docs use.
322
322
  echo " no guest IP either, so the VM itself is not ready. Try 'npm run worker:ctl -- up'." >&2
323
323
  fi
@@ -22,7 +22,7 @@
22
22
  # and run-server.cmd (so every worker start re-applies it for that session).
23
23
  #
24
24
  # Style note: `#` line comments and no param() block, matching the other scripts here --
25
- # see packages/worker-fleet/src/provisioning/diagnose-nvda-worker.ps1 for why.
25
+ # see src/provisioning/diagnose-nvda-worker.ps1 for why.
26
26
 
27
27
  $ErrorActionPreference = 'Stop'
28
28
 
@@ -3,7 +3,7 @@
3
3
  # Run this ONCE, in the VM, in an elevated PowerShell, right after Windows setup:
4
4
  #
5
5
  # Set-ExecutionPolicy -Scope Process Bypass -Force
6
- # irm https://raw.githubusercontent.com/a11ign/a11ign/main/packages/worker-fleet/src/provisioning/bootstrap-windows-worker.ps1 | iex
6
+ # irm https://raw.githubusercontent.com/a11ign/screenreader-fleet/main/src/provisioning/bootstrap-windows-worker.ps1 | iex
7
7
  #
8
8
  # ...or, if you already have the repo, just run this file. It installs the
9
9
  # prerequisites, makes the box reachable over SSH, clones the repo, and then hands
@@ -4,7 +4,7 @@
4
4
  # VERDICT per layer rather than raw dumps, so the first FAIL is the thing to fix.
5
5
  # Exits non-zero if any check failed. Copy it over and run it with -File:
6
6
  #
7
- # scp packages/worker-fleet/src/provisioning/diagnose-nvda-worker.ps1 user@host:C:/Users/user/
7
+ # scp src/provisioning/diagnose-nvda-worker.ps1 user@host:C:/Users/user/
8
8
  # ssh user@host "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\user\diagnose-nvda-worker.ps1"
9
9
  #
10
10
  # Do NOT pipe this to `powershell -Command -`. That mode silently truncated this
@@ -47,8 +47,8 @@ param(
47
47
  Set-StrictMode -Version Latest
48
48
  $ErrorActionPreference = 'Stop'
49
49
 
50
- # TWO OF THE FIVE PATHS ARE NOT WRITTEN HERE (ADR 0039 item 6d, #3397). Where the worker layer lives is
51
- # declared in `packages/control/layers.json`, and what its launchers reach outside it in the layer's own
50
+ # THREE OF THE FIVE PATHS ARE NOT WRITTEN HERE (ADR 0039 item 6d, #3397; this repository's own file since the flat layout, a11ign/a11ign#4216).
51
+ # Where the worker layer and this fleet layer live is declared in `packages/control/layers.json`, and what its launchers reach outside it in the layer's own
52
52
  # `src/launcher-reach.cmd`, which `run-capture-check.cmd` `call`s. Both are READ, so a path cannot change in
53
53
  # the launcher and stay behind in the stamp. A declaration that is absent or does not say THROWS: the stamp
54
54
  # must not fall back to a literal, because a literal that was right yesterday is the stamp describing less
@@ -77,7 +77,7 @@ $FOREGROUND_LOCK = Get-DeclaredReach -Name 'FLT'
77
77
  # The single definition. Explicit paths rather than a filename search, because `main.yml` is not unique
78
78
  # in this repo and a search would silently pick the wrong one.
79
79
  $ENVIRONMENT_FILES = @(
80
- 'packages/worker-fleet/src/provisioning/provision-nvda-worker.ps1'
80
+ (Get-LayerFile -Layer 'screenreader-fleet' -Relative 'src/provisioning/provision-nvda-worker.ps1')
81
81
  $RUN_SERVER
82
82
  $FOREGROUND_LOCK
83
83
  'packages/control/ansible/roles/worker/defaults/main.yml'