@a11ign/screenreader-fleet 0.4.2 → 0.5.0
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.
|
@@ -5,7 +5,7 @@ import { execFileSync } from "node:child_process";
|
|
|
5
5
|
import { errorText } from "@a11ign/screenreader-worker/error-text";
|
|
6
6
|
import { fleetScriptPaths } from "./src_fleet-scripts_mjs.mjs";
|
|
7
7
|
import { inventoryWorkerUrls, configuredWorkers, resolveWorkerPool } from "./fleet-env.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import { remedyLines, codeDrift, resolveExpectedWorkerCode } from "./worker-code-check.mjs";
|
|
9
9
|
import { refuseUnknownFlags } from "./cli-flags.mjs";
|
|
10
10
|
import { requestJson } from "./worker-http.mjs";
|
|
11
11
|
refuseUnknownFlags([], {
|
|
@@ -42,9 +42,9 @@ async function versionOf(url) {
|
|
|
42
42
|
return response.json.code ?? "absent";
|
|
43
43
|
}
|
|
44
44
|
async function main() {
|
|
45
|
-
const expected =
|
|
45
|
+
const { code: expected, note } = await resolveExpectedWorkerCode();
|
|
46
46
|
const { urls, source } = workerUrls();
|
|
47
|
-
console.log(`this checkout: ${expected}`);
|
|
47
|
+
console.log(`this checkout: ${expected} (from ${note})`);
|
|
48
48
|
if (!urls.length) {
|
|
49
49
|
console.log("no worker configured, running locally, or listed in inventory.yml — nothing to compare");
|
|
50
50
|
process.exit(0);
|
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hash every worker is expected to be serving, and WHERE IT CAME FROM. ONE function, asked by `a11ign-worker-code` and by
|
|
3
|
+
* `assertFleetRunsThisCheckout` alike, so the two cannot be given different hashers again (a11ign/a11ign#3781).
|
|
4
|
+
*
|
|
5
|
+
* With a layer clone present it is the CLONE's: the clone's own `code-version.mjs` over the clone's `src/`, which is what
|
|
6
|
+
* `layerCodeVersion("nvda-worker")` computes for the deploy and the lab, and what a guest is told to be on (`--layer-ref`). Asking the
|
|
7
|
+
* installed package instead made every worker read stale the first time a guest was deployed at a sha whose `.mjs` differed from the
|
|
8
|
+
* release, with a remedy ("redeploy") that could not clear it.
|
|
9
|
+
*
|
|
10
|
+
* With none it is the installed package's (a11ign/a11ign#3740: the one the package was RELEASED with, `codeVersion()` and no
|
|
11
|
+
* directory, because the built package's `workerSourceDir()` is `dist/` and holds none of the files a guest runs), and `source` and
|
|
12
|
+
* `note` say so, so a reading is never silent about which it was.
|
|
13
|
+
*
|
|
14
|
+
* ASYNC because the clone's hasher can only be imported dynamically, as `layerCodeVersion` does.
|
|
15
|
+
*
|
|
16
|
+
* @param {{ checkoutRoot?: string }} [options]
|
|
17
|
+
* @returns {Promise<{ code: string, source: "clone" | "installed", sourceDir: string, note: string }>}
|
|
18
|
+
*/
|
|
19
|
+
export function resolveExpectedWorkerCode({ checkoutRoot }?: {
|
|
20
|
+
checkoutRoot?: string;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
code: string;
|
|
23
|
+
source: "clone" | "installed";
|
|
24
|
+
sourceDir: string;
|
|
25
|
+
note: string;
|
|
26
|
+
}>;
|
|
1
27
|
/**
|
|
2
28
|
* Refuse to capture with a fleet that is not running this checkout.
|
|
3
29
|
*
|
|
@@ -10,15 +36,15 @@
|
|
|
10
36
|
* A thin wrapper over `assertWorkersServe`, supplying the one thing only this file can compute: the hash.
|
|
11
37
|
*
|
|
12
38
|
* @param {string[]} workers
|
|
13
|
-
* @param {{when?: string, allow?: boolean, read?: (url: string) => Promise<string|null>, bareMetalUrls?: string[]}} options
|
|
39
|
+
* @param {{when?: string, allow?: boolean, read?: (url: string) => Promise<string|null>, bareMetalUrls?: string[], checkoutRoot?: string}} options
|
|
14
40
|
*/
|
|
15
41
|
export function assertFleetRunsThisCheckout(workers: string[], options?: {
|
|
16
42
|
when?: string;
|
|
17
43
|
allow?: boolean;
|
|
18
44
|
read?: (url: string) => Promise<string | null>;
|
|
19
45
|
bareMetalUrls?: string[];
|
|
46
|
+
checkoutRoot?: string;
|
|
20
47
|
}): Promise<void>;
|
|
21
|
-
export function expectedWorkerCode(): string;
|
|
22
48
|
import { codeDrift } from "./code-drift.mjs";
|
|
23
49
|
import { describeCodeDrift } from "./code-drift.mjs";
|
|
24
50
|
import { describeEmptyPool } from "./code-drift.mjs";
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { codeVersion, workerSourceDir } from "@a11ign/screenreader-worker/code-version";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
3
6
|
import { requestJson } from "./worker-http.mjs";
|
|
4
7
|
import { sandboxGitEnv } from "./src_git-safe-env_mjs.mjs";
|
|
5
8
|
const HEALTH_TIMEOUT_MS = 15000;
|
|
@@ -119,11 +122,49 @@ async function assertWorkersServe(expected, workers, options) {
|
|
|
119
122
|
process.stderr.write(refusal);
|
|
120
123
|
process.exit(3);
|
|
121
124
|
}
|
|
122
|
-
const
|
|
125
|
+
const NVDA_WORKER_LAYER = "nvda-worker";
|
|
126
|
+
function layerClone(checkoutRoot) {
|
|
127
|
+
const manifestPath = join(checkoutRoot, "packages", "control", "layers.json");
|
|
128
|
+
if (!existsSync(manifestPath)) return {
|
|
129
|
+
absent: `no packages/control/layers.json under ${checkoutRoot}`
|
|
130
|
+
};
|
|
131
|
+
const layer = JSON.parse(readFileSync(manifestPath, "utf8")).layers?.[NVDA_WORKER_LAYER];
|
|
132
|
+
if (!layer?.path) return {
|
|
133
|
+
absent: `${manifestPath} does not declare "${NVDA_WORKER_LAYER}"`
|
|
134
|
+
};
|
|
135
|
+
const dir = resolve(checkoutRoot, layer.path);
|
|
136
|
+
return existsSync(dir) ? {
|
|
137
|
+
dir
|
|
138
|
+
} : {
|
|
139
|
+
absent: `no layer clone at ${dir}`
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async function resolveExpectedWorkerCode({ checkoutRoot = process.cwd() } = {}) {
|
|
143
|
+
const clone = layerClone(checkoutRoot);
|
|
144
|
+
if ("absent" in clone) return {
|
|
145
|
+
code: codeVersion(),
|
|
146
|
+
source: "installed",
|
|
147
|
+
sourceDir: workerSourceDir(),
|
|
148
|
+
note: `the installed @a11ign/screenreader-worker (${clone.absent})`
|
|
149
|
+
};
|
|
150
|
+
const sourceDir = `${join(clone.dir, "src")}/`;
|
|
151
|
+
const hasher = await import(pathToFileURL(join(sourceDir, "code-version.mjs")).href);
|
|
152
|
+
return {
|
|
153
|
+
code: hasher.codeVersion(sourceDir),
|
|
154
|
+
source: "clone",
|
|
155
|
+
sourceDir,
|
|
156
|
+
note: `the layer clone at ${clone.dir}`
|
|
157
|
+
};
|
|
158
|
+
}
|
|
123
159
|
async function assertFleetRunsThisCheckout(workers, options = {}) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
160
|
+
const { checkoutRoot, ...rest } = options;
|
|
161
|
+
const expected = await resolveExpectedWorkerCode({
|
|
162
|
+
checkoutRoot
|
|
163
|
+
});
|
|
164
|
+
if (!options.allow) process.stdout.write(`Expected worker code ${expected.code}, from ${expected.note}.\n`);
|
|
165
|
+
return assertWorkersServe(expected.code, workers, {
|
|
166
|
+
...rest,
|
|
167
|
+
sourceDir: expected.sourceDir
|
|
127
168
|
});
|
|
128
169
|
}
|
|
129
|
-
export { assertFleetRunsThisCheckout, codeDrift, describeCodeDrift, describeEmptyPool,
|
|
170
|
+
export { assertFleetRunsThisCheckout, codeDrift, describeCodeDrift, describeEmptyPool, readWorkerCode, remedyLines, resolveExpectedWorkerCode, workerSourceDirty };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a11ign/screenreader-fleet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|