@openmixer/plugin-qualify 0.20.1 → 0.20.4
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/bin/plugin-qualify-sync.mjs +1 -0
- package/catalogs/ca.json +251 -0
- package/catalogs/de.json +49 -0
- package/catalogs/es.json +49 -0
- package/catalogs/fr.json +49 -0
- package/catalogs/it.json +49 -0
- package/catalogs/pt.json +49 -0
- package/dist/clap-loader.js +2 -1
- package/dist/clap-loader.js.map +1 -1
- package/dist/clap-qualify.d.ts +11 -0
- package/dist/clap-qualify.js +16 -0
- package/dist/clap-qualify.js.map +1 -1
- package/dist/compare.d.ts +12 -11
- package/dist/compare.js +71 -38
- package/dist/compare.js.map +1 -1
- package/dist/host-admission.d.ts +25 -0
- package/dist/host-admission.js +41 -5
- package/dist/host-admission.js.map +1 -1
- package/dist/hostd-pin-files.d.ts +6 -2
- package/dist/hostd-pin-files.js +10 -6
- package/dist/hostd-pin-files.js.map +1 -1
- package/dist/hostd-search-path.d.ts +38 -0
- package/dist/hostd-search-path.js +58 -0
- package/dist/hostd-search-path.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/jack-registry.js +1 -1
- package/dist/lv2-loader.d.ts +33 -1
- package/dist/lv2-loader.js +82 -19
- package/dist/lv2-loader.js.map +1 -1
- package/dist/measurement/vocabulary.js +3 -3
- package/dist/measurement/vocabulary.js.map +1 -1
- package/dist/measurement-grid.d.ts +1 -1
- package/dist/measurement-grid.js +5 -3
- package/dist/measurement-grid.js.map +1 -1
- package/dist/pins-file.d.ts +3 -2
- package/dist/pins-file.js +3 -2
- package/dist/pins-file.js.map +1 -1
- package/dist/plugin-db-cli.d.ts +28 -0
- package/dist/plugin-db-cli.js +43 -0
- package/dist/plugin-db-cli.js.map +1 -0
- package/dist/plugin-db-pin.d.ts +8 -0
- package/dist/plugin-db-pin.js +29 -0
- package/dist/plugin-db-pin.js.map +1 -0
- package/dist/probe-judge.js +5 -0
- package/dist/probe-judge.js.map +1 -1
- package/dist/probe-output.d.ts +10 -0
- package/dist/probe-output.js +16 -0
- package/dist/probe-output.js.map +1 -1
- package/dist/probe-runner.js +24 -5
- package/dist/probe-runner.js.map +1 -1
- package/dist/qualification-record.d.ts +5 -0
- package/dist/qualification-record.js +13 -1
- package/dist/qualification-record.js.map +1 -1
- package/dist/qualify-adapter-pass.d.ts +1 -2
- package/dist/qualify-adapter-pass.js +6 -6
- package/dist/qualify-adapter-pass.js.map +1 -1
- package/dist/qualify-declarations.d.ts +23 -6
- package/dist/qualify-declarations.js +25 -6
- package/dist/qualify-declarations.js.map +1 -1
- package/dist/qualify-isolated.d.ts +9 -0
- package/dist/qualify-isolated.js +16 -5
- package/dist/qualify-isolated.js.map +1 -1
- package/dist/qualify-pool.js +4 -3
- package/dist/qualify-pool.js.map +1 -1
- package/dist/qualify-rejudge.js +3 -3
- package/dist/qualify-rejudge.js.map +1 -1
- package/dist/qualify-sync.d.ts +12 -5
- package/dist/qualify-sync.js +51 -30
- package/dist/qualify-sync.js.map +1 -1
- package/dist/record-figures.d.ts +52 -0
- package/dist/record-figures.js +58 -0
- package/dist/record-figures.js.map +1 -0
- package/dist/refused-stderr-kept.d.ts +12 -0
- package/dist/refused-stderr-kept.js +67 -0
- package/dist/refused-stderr-kept.js.map +1 -0
- package/dist/run-home.d.ts +4 -0
- package/dist/run-home.js +26 -0
- package/dist/run-home.js.map +1 -0
- package/dist/text-catalog.d.ts +41 -0
- package/dist/text-catalog.js +97 -0
- package/dist/text-catalog.js.map +1 -0
- package/dist/text-codes.d.ts +29 -0
- package/dist/text-codes.generated.d.ts +88 -0
- package/dist/text-codes.generated.js +96 -0
- package/dist/text-codes.generated.js.map +1 -0
- package/dist/text-codes.js +19 -0
- package/dist/text-codes.js.map +1 -0
- package/package.json +8 -6
- package/schema/codes.json +386 -0
- package/src/clap-loader.ts +2 -1
- package/src/clap-qualify.ts +25 -0
- package/src/compare.ts +85 -45
- package/src/host-admission.ts +52 -5
- package/src/hostd-pin-files.ts +13 -8
- package/src/hostd-search-path.ts +85 -0
- package/src/index.ts +4 -0
- package/src/jack-registry.ts +1 -1
- package/src/lv2-loader.ts +89 -17
- package/src/measurement/vocabulary.ts +3 -3
- package/src/measurement-grid.ts +5 -3
- package/src/pins-file.ts +3 -2
- package/src/plugin-db-cli.ts +78 -0
- package/src/plugin-db-pin.ts +33 -0
- package/src/probe-judge.ts +5 -0
- package/src/probe-output.ts +23 -0
- package/src/probe-runner.ts +25 -4
- package/src/qualification-record.ts +13 -1
- package/src/qualify-adapter-pass.ts +6 -5
- package/src/qualify-declarations.ts +27 -6
- package/src/qualify-isolated.ts +18 -5
- package/src/qualify-pool.ts +4 -3
- package/src/qualify-rejudge.ts +3 -3
- package/src/qualify-sync.ts +55 -33
- package/src/record-figures.ts +114 -0
- package/src/refused-stderr-kept.ts +84 -0
- package/src/run-home.ts +28 -0
- package/src/text-catalog.ts +116 -0
- package/src/text-codes.generated.ts +97 -0
- package/src/text-codes.ts +35 -0
- package/tools/_declarations.py +2 -1
- package/tools/catalogs-gate.sh +22 -0
- package/tools/compare-host.mjs +1 -1
- package/tools/compare-run.mjs +10 -6
- package/tools/descriptors.py +40 -3
- package/tools/gen-python-declarations.mjs +1 -0
- package/tools/gen-text-codes.d.mts +8 -0
- package/tools/gen-text-codes.mjs +77 -0
- package/tools/isolated-step.mjs +24 -3
- package/tools/mod-host-admit.mjs +57 -1
- package/tools/scan.py +29 -5
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
|
+
/**
|
|
4
|
+
* The sentence a surface shows for one of plugin-qualify's own {@link TextRef}s: the catalog's, for the given
|
|
5
|
+
* language, else the source sentence `schema/codes.json` declares; its `{placeholder}`s filled from `args`
|
|
6
|
+
* An entry is the sentence, or the sentence with how it was made (omx-contract 3.16.0's `catalogEntry`); a model's
|
|
7
|
+
* entry made from another source sentence is stale and skipped. This is the lookup of
|
|
8
|
+
* `@openmixer/omx-contract/catalog` (`sentenceOf`, `fill`), made here: that module reads its schema through ajv, which
|
|
9
|
+
* this package does not depend on, and the npm floor is below the release that knows an entry's provenance.
|
|
10
|
+
*
|
|
11
|
+
* `schema/codes.json` and `catalogs/<lang>.json` ship inside this package (`package.json`'s `files`), read relative
|
|
12
|
+
* to this module so it works from `src/` under vitest and from `dist/` once built alike. `PLUGIN_QUALIFY_CATALOGS_DIR`
|
|
13
|
+
* names another catalogs directory instead, for a test that wants its own fixture catalogs.
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
import { dirname, join, resolve } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import type { ReasonText, TextRef } from "./text-codes.js";
|
|
20
|
+
|
|
21
|
+
export const OWNER = "plugin-qualify";
|
|
22
|
+
export const CATALOGS_DIR_ENV = "PLUGIN_QUALIFY_CATALOGS_DIR";
|
|
23
|
+
|
|
24
|
+
const PKG = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
25
|
+
|
|
26
|
+
/** Where this package's own `catalogs/<lang>.json` live: `PLUGIN_QUALIFY_CATALOGS_DIR`, else beside this module's package. */
|
|
27
|
+
export function catalogsDir(env: Readonly<Record<string, string | undefined>> = process.env): string {
|
|
28
|
+
return env[CATALOGS_DIR_ENV] ?? join(PKG, "catalogs");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** One code of `schema/codes.json`: the code, the args its sentence names and the sentence. */
|
|
32
|
+
export interface DeclaredCode {
|
|
33
|
+
readonly code: string;
|
|
34
|
+
readonly doc?: string;
|
|
35
|
+
readonly text: { readonly en: string };
|
|
36
|
+
readonly args: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** One catalog entry: the sentence, or the sentence with how it was made (omx-contract's `catalogEntry`). */
|
|
40
|
+
export type CatalogEntry = string | { readonly text: string; readonly by: "human" | "machine"; readonly model?: string; readonly source?: string };
|
|
41
|
+
|
|
42
|
+
/** Where the codes are declared: `schema/codes.json` of this package. */
|
|
43
|
+
export const CODES_FILE = join(PKG, "schema", "codes.json");
|
|
44
|
+
|
|
45
|
+
let declared: ReadonlyMap<string, DeclaredCode> | undefined;
|
|
46
|
+
|
|
47
|
+
/** Every code `schema/codes.json` declares, by its code; none where the file does not read. */
|
|
48
|
+
export function declaredCodes(): ReadonlyMap<string, DeclaredCode> {
|
|
49
|
+
if (declared !== undefined) return declared;
|
|
50
|
+
let codes: Record<string, DeclaredCode> = {};
|
|
51
|
+
try {
|
|
52
|
+
codes = (JSON.parse(readFileSync(CODES_FILE, "utf8")) as { codes?: Record<string, DeclaredCode> }).codes ?? {};
|
|
53
|
+
} catch {
|
|
54
|
+
codes = {};
|
|
55
|
+
}
|
|
56
|
+
declared = new Map(Object.values(codes).map((c) => [c.code, c]));
|
|
57
|
+
return declared;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const messagesCache = new Map<string, Record<string, CatalogEntry> | undefined>();
|
|
61
|
+
|
|
62
|
+
/** `catalogs/<lang>.json`'s `messages`, or `undefined` where the file is absent or does not read as one. */
|
|
63
|
+
export function loadMessages(lang: string, dir: string = catalogsDir()): Record<string, CatalogEntry> | undefined {
|
|
64
|
+
const cacheKey = `${dir}\0${lang}`;
|
|
65
|
+
if (messagesCache.has(cacheKey)) return messagesCache.get(cacheKey);
|
|
66
|
+
let messages: Record<string, CatalogEntry> | undefined;
|
|
67
|
+
try {
|
|
68
|
+
const catalog = JSON.parse(readFileSync(join(dir, `${lang}.json`), "utf8")) as { messages?: Record<string, CatalogEntry> };
|
|
69
|
+
messages = catalog.messages;
|
|
70
|
+
} catch {
|
|
71
|
+
messages = undefined;
|
|
72
|
+
}
|
|
73
|
+
messagesCache.set(cacheKey, messages);
|
|
74
|
+
return messages;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** `sentence` with each `{name}` replaced by `args[name]`; a placeholder with no value is left as written. */
|
|
78
|
+
export function fill(sentence: string, args: Readonly<Record<string, string | number>> = {}): string {
|
|
79
|
+
return sentence.replace(/\{([^{}]*)\}/g, (m, n: string) => (Object.hasOwn(args, n) ? String(args[n]) : m));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The sha256 of a source sentence, lowercase hex of its UTF-8 bytes: what a model's entry records as `source`. */
|
|
83
|
+
export const sourceHash = (sentence: string): string => createHash("sha256").update(sentence, "utf8").digest("hex");
|
|
84
|
+
|
|
85
|
+
/** The sentence of `entry`; `undefined` for no entry, or a model's entry made from another sentence than `source`. */
|
|
86
|
+
export function entrySentence(entry: CatalogEntry | undefined, source: string): string | undefined {
|
|
87
|
+
if (entry === undefined || typeof entry === "string") return entry;
|
|
88
|
+
return entry.source !== undefined && entry.source !== sourceHash(source) ? undefined : entry.text;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The source sentence `code` was declared with (`schema/codes.json`); throws on a code this package never declared. */
|
|
92
|
+
export function sourceOf(code: string): string {
|
|
93
|
+
const source = declaredCodes().get(code)?.text.en;
|
|
94
|
+
if (source === undefined) throw new Error(`plugin-qualify declares no source sentence for ${code}`);
|
|
95
|
+
return source;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** `ref` rendered for `lang`: the catalog's sentence, else the source, its `{placeholder}`s filled. `lang` undefined or "en" skips the catalog. */
|
|
99
|
+
export function textOf(ref: TextRef, lang?: string): string {
|
|
100
|
+
const source = sourceOf(ref.code);
|
|
101
|
+
const sentence = lang === undefined || lang === "en" ? source : (entrySentence(loadMessages(lang)?.[ref.code], source) ?? source);
|
|
102
|
+
return fill(sentence, ref.args ?? {});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** A {@link ReasonText} rendered for `lang`: another tool's own words pass straight through; plugin-qualify's own go through {@link textOf}. */
|
|
106
|
+
export function reasonText(reason: ReasonText, lang?: string): string {
|
|
107
|
+
return typeof reason === "string" ? reason : textOf(reason, lang);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The language of `LC_ALL`, else `LANG` (the POSIX precedence), as the two letters before `_` or `.`; `undefined` unset, unparsed, or `C`/`POSIX` (textOf's own fallback to the source sentence is the same as leaving this undefined). */
|
|
111
|
+
export function localeOf(env: Readonly<Record<string, string | undefined>> = process.env): string | undefined {
|
|
112
|
+
const raw = env["LC_ALL"] || env["LANG"];
|
|
113
|
+
if (raw === undefined || raw === "") return undefined;
|
|
114
|
+
const m = /^([a-z]{2})(?:[_.].*)?$/.exec(raw);
|
|
115
|
+
return m?.[1];
|
|
116
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
|
+
//
|
|
4
|
+
// GENERATED by tools/gen-text-codes.mjs from schema/codes.json — do not hand-edit.
|
|
5
|
+
//
|
|
6
|
+
// Regenerate: node tools/gen-text-codes.mjs --write
|
|
7
|
+
// Verify: node tools/gen-text-codes.mjs --check
|
|
8
|
+
|
|
9
|
+
/** plugin-qualify's own codes, by key (schema/codes.json). */
|
|
10
|
+
export const TEXT_CODE = Object.freeze({
|
|
11
|
+
descriptorDuplicateSymbol: "qualify.descriptor.duplicate-symbol",
|
|
12
|
+
compareHostNotInstalled: "qualify.compare.host-not-installed",
|
|
13
|
+
compareHostDidNotStart: "qualify.compare.host-did-not-start",
|
|
14
|
+
compareHostNotAsked: "qualify.compare.host-not-asked",
|
|
15
|
+
compareHostDidNotRun: "qualify.compare.host-did-not-run",
|
|
16
|
+
compareFormatNotTaken: "qualify.compare.format-not-taken",
|
|
17
|
+
compareRtNotReported: "qualify.compare.rt-not-reported",
|
|
18
|
+
compareRtNoCallback: "qualify.compare.rt-no-callback",
|
|
19
|
+
compareRtNoWarmUp: "qualify.compare.rt-no-warm-up",
|
|
20
|
+
compareRtTrylocksOnly: "qualify.compare.rt-trylocks-only",
|
|
21
|
+
compareRtUnsafe: "qualify.compare.rt-unsafe",
|
|
22
|
+
compareLoadHostEnded: "qualify.compare.load-host-ended",
|
|
23
|
+
compareLoadResp: "qualify.compare.load-resp",
|
|
24
|
+
compareLoadTimeout: "qualify.compare.load-timeout",
|
|
25
|
+
compareNotLoaded: "qualify.compare.not-loaded",
|
|
26
|
+
compareNoPort: "qualify.compare.no-port",
|
|
27
|
+
compareNotRunning: "qualify.compare.not-running",
|
|
28
|
+
compareNoAudioOutput: "qualify.compare.no-audio-output",
|
|
29
|
+
compareOutputNotFinite: "qualify.compare.output-not-finite",
|
|
30
|
+
compareNoStimulusSilent: "qualify.compare.no-stimulus-silent",
|
|
31
|
+
compareSilentUnderStimulus: "qualify.compare.silent-under-stimulus",
|
|
32
|
+
compareCyclesNotRun: "qualify.compare.cycles-not-run",
|
|
33
|
+
compareCycleHostEnded: "qualify.compare.cycle-host-ended",
|
|
34
|
+
compareCycleFailed: "qualify.compare.cycle-failed",
|
|
35
|
+
compareCycleFailedAt: "qualify.compare.cycle-failed-at",
|
|
36
|
+
compareSoakHostEnded: "qualify.compare.soak-host-ended",
|
|
37
|
+
compareSoakEnded: "qualify.compare.soak-ended",
|
|
38
|
+
compareLatencyPortObservable: "qualify.compare.latency-port-observable",
|
|
39
|
+
compareLatencyHostdPortObservable: "qualify.compare.latency-hostd-port-observable",
|
|
40
|
+
compareLatencyNotObservableModHost: "qualify.compare.latency-not-observable-mod-host",
|
|
41
|
+
compareLatencyUnreadable: "qualify.compare.latency-unreadable",
|
|
42
|
+
compareSkippedReason: "qualify.compare.skipped-reason",
|
|
43
|
+
compareDoesBetterOn: "qualify.compare.does-better-on",
|
|
44
|
+
compareBeatDescription: "qualify.compare.beat-description",
|
|
45
|
+
compareNotPublishableWithMissing: "qualify.compare.not-publishable-with-missing",
|
|
46
|
+
compareNotPublishableNoFlag: "qualify.compare.not-publishable-no-flag",
|
|
47
|
+
pinHostdNotInstalled: "qualify.pin.hostd-not-installed",
|
|
48
|
+
pinHostdNoPinFiles: "qualify.pin.hostd-no-pin-files",
|
|
49
|
+
pinHostdExited: "qualify.pin.hostd-exited",
|
|
50
|
+
pinHostdNoRootOrFile: "qualify.pin.hostd-no-root-or-file",
|
|
51
|
+
pinFileUnreadable: "qualify.pin.file-unreadable",
|
|
52
|
+
} as const);
|
|
53
|
+
|
|
54
|
+
/** The args each code's source sentence names, by the same key. */
|
|
55
|
+
export const TEXT_CODE_ARGS = Object.freeze({
|
|
56
|
+
descriptorDuplicateSymbol: ["symbol"],
|
|
57
|
+
compareHostNotInstalled: ["program"],
|
|
58
|
+
compareHostDidNotStart: ["program"],
|
|
59
|
+
compareHostNotAsked: [],
|
|
60
|
+
compareHostDidNotRun: ["program"],
|
|
61
|
+
compareFormatNotTaken: ["program", "format"],
|
|
62
|
+
compareRtNotReported: [],
|
|
63
|
+
compareRtNoCallback: [],
|
|
64
|
+
compareRtNoWarmUp: [],
|
|
65
|
+
compareRtTrylocksOnly: ["trylocks", "cycles"],
|
|
66
|
+
compareRtUnsafe: ["cycles", "allocations", "locks", "syscalls"],
|
|
67
|
+
compareLoadHostEnded: ["died"],
|
|
68
|
+
compareLoadResp: ["resp"],
|
|
69
|
+
compareLoadTimeout: [],
|
|
70
|
+
compareNotLoaded: [],
|
|
71
|
+
compareNoPort: [],
|
|
72
|
+
compareNotRunning: [],
|
|
73
|
+
compareNoAudioOutput: [],
|
|
74
|
+
compareOutputNotFinite: ["count"],
|
|
75
|
+
compareNoStimulusSilent: [],
|
|
76
|
+
compareSilentUnderStimulus: ["stimulus"],
|
|
77
|
+
compareCyclesNotRun: [],
|
|
78
|
+
compareCycleHostEnded: ["cycle", "died"],
|
|
79
|
+
compareCycleFailed: ["cycle"],
|
|
80
|
+
compareCycleFailedAt: ["cycle", "step"],
|
|
81
|
+
compareSoakHostEnded: ["died"],
|
|
82
|
+
compareSoakEnded: [],
|
|
83
|
+
compareLatencyPortObservable: [],
|
|
84
|
+
compareLatencyHostdPortObservable: [],
|
|
85
|
+
compareLatencyNotObservableModHost: [],
|
|
86
|
+
compareLatencyUnreadable: [],
|
|
87
|
+
compareSkippedReason: ["reason"],
|
|
88
|
+
compareDoesBetterOn: ["host", "count", "detail"],
|
|
89
|
+
compareBeatDescription: ["id", "on", "over"],
|
|
90
|
+
compareNotPublishableWithMissing: ["missing"],
|
|
91
|
+
compareNotPublishableNoFlag: [],
|
|
92
|
+
pinHostdNotInstalled: [],
|
|
93
|
+
pinHostdNoPinFiles: [],
|
|
94
|
+
pinHostdExited: ["status"],
|
|
95
|
+
pinHostdNoRootOrFile: [],
|
|
96
|
+
pinFileUnreadable: ["error"],
|
|
97
|
+
} as const);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
|
+
/**
|
|
4
|
+
* plugin-qualify's own verdict and reason sentences, as codes (omx-contract#2, "codes then catalogs", owner
|
|
5
|
+
* ruling 2026-11-09: a code carries its identifier, its arguments and one source sentence in English with named
|
|
6
|
+
* placeholders; translations live in catalogs the owner ships, keyed by code, checked for placeholders against the
|
|
7
|
+
* code's `args`; a surface looks up the sentence for the user's locale and falls back to this source sentence).
|
|
8
|
+
*
|
|
9
|
+
* plugin-qualify is the owner of these codes (the hosting.* codes of {@link ../hosting-codes.js} are omx-clap-core's
|
|
10
|
+
* instead, and that package owns their catalogs). A code, its args and its source sentence are written once, in
|
|
11
|
+
* `schema/codes.json`; {@link TEXT_CODE} is that file's `key: code` map, generated (`tools/gen-text-codes.mjs`), so a
|
|
12
|
+
* call site names a code by its key and no sentence is written in a module. `catalogs/<lang>.json`, read by
|
|
13
|
+
* {@link ../text-catalog.js}, hold each listed language's sentences; `src/text-catalog-conformance.test.ts` turns red
|
|
14
|
+
* on a code without its source sentence or without an entry in a language, and on a sentence whose placeholders are
|
|
15
|
+
* not its code's `args`.
|
|
16
|
+
*/
|
|
17
|
+
import { TEXT_CODE, TEXT_CODE_ARGS } from "./text-codes.generated.js";
|
|
18
|
+
|
|
19
|
+
export { TEXT_CODE, TEXT_CODE_ARGS };
|
|
20
|
+
|
|
21
|
+
/** A key of {@link TEXT_CODE}. */
|
|
22
|
+
export type TextCodeKey = keyof typeof TEXT_CODE;
|
|
23
|
+
|
|
24
|
+
/** A code of {@link TEXT_CODE} with the arguments its source sentence names. A reader renders it; it is never prose itself. */
|
|
25
|
+
export interface TextRef {
|
|
26
|
+
readonly code: string;
|
|
27
|
+
readonly args?: Readonly<Record<string, string | number>>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A reason as plugin-qualify writes it: its own sentence as a {@link TextRef}, or another tool's own words passed
|
|
32
|
+
* through unclassified (the shape `UnmeasuredFigure.detail` already uses elsewhere: a prober's raw text is never
|
|
33
|
+
* coded, since nothing here can translate words it did not write).
|
|
34
|
+
*/
|
|
35
|
+
export type ReasonText = TextRef | string;
|
package/tools/_declarations.py
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# Verify: node tools/gen-python-declarations.mjs --check
|
|
12
12
|
|
|
13
13
|
COST_REFERENCE_RATE = 96000
|
|
14
|
-
QUANTUM_FRAMES =
|
|
14
|
+
QUANTUM_FRAMES = 256
|
|
15
15
|
MEASURE_BASE_RATE = 48000
|
|
16
16
|
OPERATIONAL_RATE_FLOOR = 48000
|
|
17
17
|
QUALIFY_RATES = (44100, 48000, 96000, 192000)
|
|
@@ -33,3 +33,4 @@ DESCRIPTOR_CODE_SCHEMA_MISSING = "qualify.descriptor.schema-missing"
|
|
|
33
33
|
DESCRIPTOR_CODE_SCHEMA_FLOOR = "qualify.descriptor.schema-floor"
|
|
34
34
|
DESCRIPTOR_CODE_BINARY_UNHASHED = "qualify.descriptor.binary-unhashed"
|
|
35
35
|
DESCRIPTOR_CODE_DIR_UNWRITABLE = "qualify.descriptor.dir-unwritable"
|
|
36
|
+
DESCRIPTOR_CODE_DUPLICATE_SYMBOL = "qualify.descriptor.duplicate-symbol"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
# Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
4
|
+
#
|
|
5
|
+
# The catalogs through omx-contract's release gate (translate --check, 3.16.0): every listed language has its
|
|
6
|
+
# catalog, no key is missing and no entry is stale. The codes are a bare item file (schema/codes.json); the gate reads
|
|
7
|
+
# a vocabulary, so it is handed one holding that item alone.
|
|
8
|
+
#
|
|
9
|
+
# tools/catalogs-gate.sh [translate options] (default --check; e.g. --lang es --model <alias> to make a language)
|
|
10
|
+
|
|
11
|
+
set -eu
|
|
12
|
+
cd "$(dirname "$0")/.."
|
|
13
|
+
owner=$(python3 -c 'import json; print(json.load(open("package.json"))["name"].split("/")[-1])')
|
|
14
|
+
data=$(mktemp -d)
|
|
15
|
+
trap 'rm -r "$data"' EXIT
|
|
16
|
+
python3 - "$data/codes.json" "$owner" <<'EOF'
|
|
17
|
+
import json, sys
|
|
18
|
+
item = sys.argv[2].upper().replace("-", "_") + "_CODES"
|
|
19
|
+
json.dump({item: json.load(open("schema/codes.json"))}, open(sys.argv[1], "w"), ensure_ascii=False)
|
|
20
|
+
EOF
|
|
21
|
+
[ "$#" -gt 0 ] || set -- --check
|
|
22
|
+
exec omx-contract translate "$@" --data "$data" --catalogs catalogs --package "$owner"
|
package/tools/compare-host.mjs
CHANGED
|
@@ -316,7 +316,7 @@ async function protocolHost() {
|
|
|
316
316
|
for (let i = 0; i < CYCLES; i++) {
|
|
317
317
|
const base = ports() ?? [];
|
|
318
318
|
const a = await ask(`add ${spec} ${INSTANCE}`);
|
|
319
|
-
if (i === 0) obs.load = a.resp !== undefined ? { ok: a.resp >= 0, resp: a.resp } : { ok: false, ...(a.died ? { died: a.died } : {
|
|
319
|
+
if (i === 0) obs.load = a.resp !== undefined ? { ok: a.resp >= 0, resp: a.resp } : { ok: false, ...(a.died ? { died: a.died } : {}) };
|
|
320
320
|
if (a.resp === undefined || a.resp < 0) {
|
|
321
321
|
obs.cycles = { done, of: CYCLES, failedAt: "add", ...(a.died ? { died: a.died } : {}) };
|
|
322
322
|
if (!alive() || closed) crashed(i === 0 ? "load" : `cycle ${String(i + 1)}`);
|
package/tools/compare-run.mjs
CHANGED
|
@@ -32,8 +32,12 @@ import {
|
|
|
32
32
|
summaryHelpText,
|
|
33
33
|
summaryTable,
|
|
34
34
|
writeFileWhole,
|
|
35
|
+
localeOf,
|
|
36
|
+
reasonText,
|
|
37
|
+
TEXT_CODE,
|
|
35
38
|
} from "../dist/index.js";
|
|
36
39
|
|
|
40
|
+
const LANG = localeOf();
|
|
37
41
|
const DRIVER = fileURLToPath(new URL("./compare-host.mjs", import.meta.url));
|
|
38
42
|
const log = (line) => process.stderr.write(`plugin-qualify compare: ${line}\n`);
|
|
39
43
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -62,7 +66,7 @@ function presence(hosts) {
|
|
|
62
66
|
const program = HOST_TRAITS[host].program;
|
|
63
67
|
const w = sh("sh", ["-c", `command -v "$1"`, "sh", program]);
|
|
64
68
|
const path = w.status === 0 ? w.stdout.trim() : "";
|
|
65
|
-
if (!path) return { host, installed: false, version: null, reason:
|
|
69
|
+
if (!path) return { host, installed: false, version: null, reason: { code: TEXT_CODE.compareHostNotInstalled, args: { program } } };
|
|
66
70
|
return { host, installed: true, version: versionOf(program, path) };
|
|
67
71
|
});
|
|
68
72
|
}
|
|
@@ -187,7 +191,7 @@ function writeSummary(dir, records, present, meta, format) {
|
|
|
187
191
|
writeFileWhole(join(dir, COMPARISON_SUMMARY_FILE), `${JSON.stringify(summary, null, 2)}\n`);
|
|
188
192
|
log(`summary ${join(dir, COMPARISON_SUMMARY_FILE)}`);
|
|
189
193
|
}
|
|
190
|
-
process.stdout.write(format === "json" ? `${JSON.stringify(summary, null, 2)}\n` : summaryTable(summary));
|
|
194
|
+
process.stdout.write(format === "json" ? `${JSON.stringify(summary, null, 2)}\n` : summaryTable(summary, LANG));
|
|
191
195
|
return summary;
|
|
192
196
|
}
|
|
193
197
|
|
|
@@ -209,7 +213,7 @@ export async function compareSummaryMain(argv) {
|
|
|
209
213
|
const present = presenceFromRecords(records);
|
|
210
214
|
const missing = missingReferences(present);
|
|
211
215
|
if (parsed.args.publish && missing.length) {
|
|
212
|
-
for (const m of missing) log(`not publishable: ${m.host}: ${m.reason}`);
|
|
216
|
+
for (const m of missing) log(`not publishable: ${m.host}: ${reasonText(m.reason, LANG)}`);
|
|
213
217
|
return 1;
|
|
214
218
|
}
|
|
215
219
|
writeSummary(parsed.args.dir, records, present, { hostClass: parsed.args.hostClass, set: parsed.args.set, publish: parsed.args.publish }, "text");
|
|
@@ -229,11 +233,11 @@ export async function compareMain(argv) {
|
|
|
229
233
|
return 0;
|
|
230
234
|
}
|
|
231
235
|
const present = presence(a.hosts);
|
|
232
|
-
for (const p of present) log(p.installed ? `host ${p.host}: ${p.version ?? "version unknown"}` : `host ${p.host}: skipped, ${p.reason}`);
|
|
236
|
+
for (const p of present) log(p.installed ? `host ${p.host}: ${p.version ?? "version unknown"}` : `host ${p.host}: skipped, ${reasonText(p.reason, LANG)}`);
|
|
233
237
|
const missing = missingReferences(present);
|
|
234
238
|
if (a.publish && missing.length) {
|
|
235
239
|
// a publishable comparison is against both references: refused before anything runs, each missing one named
|
|
236
|
-
for (const m of missing) log(`--publish refused: ${m.host}: ${m.reason}`);
|
|
240
|
+
for (const m of missing) log(`--publish refused: ${m.host}: ${reasonText(m.reason, LANG)}`);
|
|
237
241
|
log(`install them (see the README, "Compare with mod-host and jalv"), or run without --publish for a local look`);
|
|
238
242
|
return 1;
|
|
239
243
|
}
|
|
@@ -337,7 +341,7 @@ export async function compareMain(argv) {
|
|
|
337
341
|
rows.push(row);
|
|
338
342
|
const c = row.checks;
|
|
339
343
|
log(
|
|
340
|
-
`[${String(i + 1)}/${String(targets.length)}] ${t.uri} ${p.host}: ${row.status === "ran" ? `${row.runsClean ? "clean" : "not clean"}; load ${c.loads.result}, output ${c.output.result}, cycles ${c.cycles.result}, soak ${c.soak.result}, rt ${c.rtSafe.result}${row.crash ? `, crashed at ${row.crash.step} (${row.crash.signal})` : ""}` : `${row.status}: ${row.reason}`}`,
|
|
344
|
+
`[${String(i + 1)}/${String(targets.length)}] ${t.uri} ${p.host}: ${row.status === "ran" ? `${row.runsClean ? "clean" : "not clean"}; load ${c.loads.result}, output ${c.output.result}, cycles ${c.cycles.result}, soak ${c.soak.result}, rt ${c.rtSafe.result}${row.crash ? `, crashed at ${row.crash.step} (${row.crash.signal})` : ""}` : `${row.status}: ${reasonText(row.reason, LANG)}`}`,
|
|
341
345
|
);
|
|
342
346
|
}
|
|
343
347
|
const rec = comparisonRecordOf({ format: t.format, id: t.uri, ...(t.binary ? { binary: t.binary } : {}) }, stimulus, conditions, new Date().toISOString(), rows);
|
package/tools/descriptors.py
CHANGED
|
@@ -30,6 +30,7 @@ import sys
|
|
|
30
30
|
from _declarations import (
|
|
31
31
|
DESCRIPTOR_CODE_BINARY_UNHASHED,
|
|
32
32
|
DESCRIPTOR_CODE_DIR_UNWRITABLE,
|
|
33
|
+
DESCRIPTOR_CODE_DUPLICATE_SYMBOL,
|
|
33
34
|
DESCRIPTOR_CODE_SCHEMA,
|
|
34
35
|
DESCRIPTOR_CODE_SCHEMA_MISSING,
|
|
35
36
|
DESCRIPTOR_DIR_STATE_SUBDIR,
|
|
@@ -49,12 +50,44 @@ FEATURE_WORDS = ("audio-effect", "instrument", "note-effect", "analyzer")
|
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
class DescriptorRefused(Exception):
|
|
52
|
-
"""A descriptor that is not written, with the code it is refused by.
|
|
53
|
+
"""A descriptor that is not written, with the code it is refused by (and, for a code `schema/codes.json`
|
|
54
|
+
declares, the args its sentence names)."""
|
|
53
55
|
|
|
54
|
-
def __init__(self, code, problem):
|
|
56
|
+
def __init__(self, code, problem, args=None):
|
|
55
57
|
super().__init__(f"{code}: {problem}")
|
|
56
58
|
self.code = code
|
|
57
59
|
self.problem = problem
|
|
60
|
+
self.code_args = dict(args or {})
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ---- the codes' sentences -----------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
CODES_FILE = os.path.join(PKG, "schema", "codes.json")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def code_sentence(code, args, path=CODES_FILE):
|
|
69
|
+
"""The English source sentence of `code` (schema/codes.json) with `args` filled in. A code the file does not
|
|
70
|
+
declare, or no file, reads as the code and its args, so a refusal is never lost for want of its words."""
|
|
71
|
+
try:
|
|
72
|
+
with open(path, encoding="utf-8") as f:
|
|
73
|
+
entries = {e["code"]: e for e in json.load(f)["codes"].values()}
|
|
74
|
+
return entries[code]["text"]["en"].format(**args)
|
|
75
|
+
except (OSError, ValueError, KeyError, IndexError):
|
|
76
|
+
return f"{code} " + " ".join(f"{k}={v}" for k, v in sorted(args.items()))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def refused(code, **args):
|
|
80
|
+
"""A refusal by `code`, its args and its sentence."""
|
|
81
|
+
return DescriptorRefused(code, code_sentence(code, args), args)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def duplicate_symbol(symbols):
|
|
85
|
+
"""The first (sorted) symbol `symbols` holds twice, else None: two ports with one symbol cannot be addressed
|
|
86
|
+
by it (EQ10Q's eq1qs..eq10qs name both output meters out_2_vu_ctl, plugin-qualify#27)."""
|
|
87
|
+
seen, twice = set(), set()
|
|
88
|
+
for s in symbols:
|
|
89
|
+
(twice if s in seen else seen).add(s)
|
|
90
|
+
return min(twice) if twice else None
|
|
58
91
|
|
|
59
92
|
|
|
60
93
|
# ---- the directory and the name -----------------------------------------------------------------
|
|
@@ -71,7 +104,11 @@ def descriptor_dir(named=None, system=False, env=None, home=None):
|
|
|
71
104
|
|
|
72
105
|
|
|
73
106
|
def _safe(text):
|
|
74
|
-
|
|
107
|
+
"""each byte of the UTF-8 text outside [A-Za-z0-9._-] as one '_', plugin-hostd-protocol's descriptor name rule"""
|
|
108
|
+
return "".join(chr(b) if chr(b) in _SAFE else "_" for b in text.encode("utf-8"))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
_SAFE = frozenset("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-")
|
|
75
112
|
|
|
76
113
|
|
|
77
114
|
def descriptor_file_name(plugin_id, version, sha256):
|
|
@@ -49,6 +49,7 @@ const PY_NAMES = [
|
|
|
49
49
|
'DESCRIPTOR_CODE_SCHEMA_FLOOR',
|
|
50
50
|
'DESCRIPTOR_CODE_BINARY_UNHASHED',
|
|
51
51
|
'DESCRIPTOR_CODE_DIR_UNWRITABLE',
|
|
52
|
+
'DESCRIPTOR_CODE_DUPLICATE_SYMBOL',
|
|
52
53
|
];
|
|
53
54
|
|
|
54
55
|
/** A Python literal: a tuple, a string (JSON's quoting is Python's for these ASCII words) or a number. */
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
|
+
/** Where the codes are declared, `schema/codes.json`. */
|
|
4
|
+
export const SOURCE: string;
|
|
5
|
+
/** The committed generated module, `src/text-codes.generated.ts`. */
|
|
6
|
+
export const OUT: string;
|
|
7
|
+
/** The generated module's text for the `codes` of a codes file. */
|
|
8
|
+
export function textCodesFile(codes: Readonly<Record<string, { readonly code: string; readonly args?: readonly string[] }>>): string;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
4
|
+
/*
|
|
5
|
+
* gen-text-codes.mjs — `src/text-codes.generated.ts`, the codes of schema/codes.json as one `key: code` map
|
|
6
|
+
* and the args each one names, as plain data.
|
|
7
|
+
*
|
|
8
|
+
* schema/codes.json is where a code, its args and its English source sentence are written, once. The call sites
|
|
9
|
+
* (src/compare.ts, src/hostd-pin-files.ts) name a code by its key; they reach it as a generated module rather than a read at
|
|
10
|
+
* import, so every module a public subpath reaches stays free of `node:` imports and bundles for a browser (#134).
|
|
11
|
+
* No sentence is in the generated module: the sentence is read from schema/codes.json where it is rendered
|
|
12
|
+
* (src/text-catalog.ts). The file is committed and checked identical, as src/hosting-codes.generated.ts is.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* node tools/gen-text-codes.mjs --check # exit 1 if the committed file is stale
|
|
16
|
+
* node tools/gen-text-codes.mjs --write # (re)write the committed file
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
19
|
+
import { dirname, join, resolve } from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
|
|
22
|
+
const PKG = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
23
|
+
export const SOURCE = join(PKG, 'schema/codes.json');
|
|
24
|
+
export const OUT = join(PKG, 'src/text-codes.generated.ts');
|
|
25
|
+
|
|
26
|
+
/** The generated module's text for the `codes` of a codes file. */
|
|
27
|
+
export function textCodesFile(codes) {
|
|
28
|
+
const entries = Object.entries(codes);
|
|
29
|
+
const code = entries.map(([key, c]) => ` ${key}: ${JSON.stringify(c.code)},\n`);
|
|
30
|
+
const args = entries.map(([key, c]) => ` ${key}: [${(c.args ?? []).map((a) => JSON.stringify(a)).join(', ')}],\n`);
|
|
31
|
+
return (
|
|
32
|
+
`// SPDX-License-Identifier: GPL-3.0-or-later\n` +
|
|
33
|
+
`// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>\n` +
|
|
34
|
+
`//\n` +
|
|
35
|
+
`// GENERATED by tools/gen-text-codes.mjs from schema/codes.json — do not hand-edit.\n` +
|
|
36
|
+
`//\n` +
|
|
37
|
+
`// Regenerate: node tools/gen-text-codes.mjs --write\n` +
|
|
38
|
+
`// Verify: node tools/gen-text-codes.mjs --check\n` +
|
|
39
|
+
`\n` +
|
|
40
|
+
`/** plugin-qualify's own codes, by key (schema/codes.json). */\n` +
|
|
41
|
+
`export const TEXT_CODE = Object.freeze({\n` +
|
|
42
|
+
code.join('') +
|
|
43
|
+
`} as const);\n` +
|
|
44
|
+
`\n` +
|
|
45
|
+
`/** The args each code's source sentence names, by the same key. */\n` +
|
|
46
|
+
`export const TEXT_CODE_ARGS = Object.freeze({\n` +
|
|
47
|
+
args.join('') +
|
|
48
|
+
`} as const);\n`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function main() {
|
|
53
|
+
const mode = process.argv[2];
|
|
54
|
+
if (!['--check', '--write'].includes(mode)) {
|
|
55
|
+
console.error('usage: node tools/gen-text-codes.mjs --check|--write');
|
|
56
|
+
process.exit(2);
|
|
57
|
+
}
|
|
58
|
+
const content = textCodesFile(JSON.parse(readFileSync(SOURCE, 'utf8')).codes);
|
|
59
|
+
let onDisk;
|
|
60
|
+
try {
|
|
61
|
+
onDisk = readFileSync(OUT, 'utf8');
|
|
62
|
+
} catch {
|
|
63
|
+
onDisk = undefined;
|
|
64
|
+
}
|
|
65
|
+
if (mode === '--check') {
|
|
66
|
+
if (onDisk !== content) {
|
|
67
|
+
console.error(`gen-text-codes: ${OUT} is stale against ${SOURCE} — run with --write`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
console.log('gen-text-codes: up to date');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (onDisk !== content) writeFileSync(OUT, content);
|
|
74
|
+
console.log(`gen-text-codes: ${onDisk === content ? 'up to date' : 'wrote'} ${OUT}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) main();
|
package/tools/isolated-step.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// added alone, worker_list read, a sine through it for a window, and worker_list read again. Prints one JSON line for
|
|
6
6
|
// src/qualify-isolated.ts to judge. A host that never listened says started: false: nothing was learned about the plugin.
|
|
7
7
|
import { spawn, spawnSync } from "node:child_process";
|
|
8
|
-
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
8
|
+
import { closeSync, mkdtempSync, openSync, readFileSync, rmSync } from "node:fs";
|
|
9
9
|
import { createConnection } from "node:net";
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
@@ -25,7 +25,11 @@ const CLOCK_TICKS = 100;
|
|
|
25
25
|
const WORKER_SPAWN = -503;
|
|
26
26
|
|
|
27
27
|
const state = mkdtempSync(join(tmpdir(), "plugin-qualify-isolated-"));
|
|
28
|
-
|
|
28
|
+
// the daemon says why it refused a plugin on stderr only: kept, so a refused add carries that reason into the record
|
|
29
|
+
const hostLog = join(state, "plugin-hostd.stderr");
|
|
30
|
+
const hostLogFd = openSync(hostLog, "a");
|
|
31
|
+
const host = spawn(hostd, ["-n", "-p", String(port)], { stdio: ["ignore", "ignore", hostLogFd], detached: true, env: { ...process.env, XDG_STATE_HOME: state } });
|
|
32
|
+
closeSync(hostLogFd);
|
|
29
33
|
let ended = false;
|
|
30
34
|
host.on("exit", () => (ended = true));
|
|
31
35
|
host.on("error", () => (ended = true));
|
|
@@ -78,6 +82,19 @@ function cpuTicks(pid) {
|
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
/** The last thing the daemon said of `uri` beyond its having no pin yet (first-use learns one at add): "" when nothing. */
|
|
86
|
+
function daemonReason() {
|
|
87
|
+
let text = "";
|
|
88
|
+
try {
|
|
89
|
+
text = readFileSync(hostLog, "utf8");
|
|
90
|
+
} catch {
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
const prefix = `plugin-hostd: ${uri}: `;
|
|
94
|
+
const said = text.split("\n").filter((l) => l.startsWith(prefix)).map((l) => l.slice(prefix.length).trim()).filter((l) => l !== "" && l !== "no pin");
|
|
95
|
+
return said.at(-1) ?? "";
|
|
96
|
+
}
|
|
97
|
+
|
|
81
98
|
async function main() {
|
|
82
99
|
const sock = await connect();
|
|
83
100
|
if (sock === undefined) return { started: false, problem: `${hostd} did not listen` };
|
|
@@ -85,7 +102,11 @@ async function main() {
|
|
|
85
102
|
const added = await send(`add ${pinWord(uri)} ${String(INSTANCE)}`);
|
|
86
103
|
// WORKER_SPAWN: no worker came up to hold the plugin, which says nothing of the plugin. The next pass tries it again.
|
|
87
104
|
if (added?.code === WORKER_SPAWN) return { started: false, problem: `no worker came up: add answered ${String(WORKER_SPAWN)}` };
|
|
88
|
-
if (added === undefined || added.code < 0)
|
|
105
|
+
if (added === undefined || added.code < 0) {
|
|
106
|
+
const why = daemonReason();
|
|
107
|
+
const answered = added === undefined ? "the host gave no answer to add" : `add answered ${String(added.code)}`;
|
|
108
|
+
return { started: true, added: false, problem: why === "" ? answered : `${answered}: ${why}` };
|
|
109
|
+
}
|
|
89
110
|
const before = workersOf(await send("worker_list"));
|
|
90
111
|
const worker = before[0];
|
|
91
112
|
const ticksBefore = worker === undefined ? undefined : cpuTicks(worker.pid);
|