@openmixer/plugin-qualify 0.13.0 → 0.14.1
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 +34 -0
- package/dist/hosting-codes.d.ts +15 -89
- package/dist/hosting-codes.generated.d.ts +76 -0
- package/dist/hosting-codes.generated.js +84 -0
- package/dist/hosting-codes.generated.js.map +1 -0
- package/dist/hosting-codes.js +10 -87
- package/dist/hosting-codes.js.map +1 -1
- package/dist/qualify-declarations.d.ts +26 -0
- package/dist/qualify-declarations.js +26 -0
- package/dist/qualify-declarations.js.map +1 -1
- package/package.json +3 -3
- package/src/hosting-codes.generated.ts +84 -0
- package/src/hosting-codes.ts +12 -178
- package/src/qualify-declarations.ts +29 -0
- package/tools/_declarations.py +11 -0
- package/tools/audit-overlap.sh +250 -0
- package/tools/clap-core-tsdir.d.mts +16 -0
- package/tools/clap-core-tsdir.mjs +93 -0
- package/tools/descriptors.py +382 -0
- package/tools/gen-hosting-codes.d.mts +8 -0
- package/tools/gen-hosting-codes.mjs +92 -0
- package/tools/gen-python-declarations.mjs +13 -1
- package/tools/scan.py +260 -4
package/src/hosting-codes.ts
CHANGED
|
@@ -4,184 +4,18 @@
|
|
|
4
4
|
* The hosting verdict's message codes. Codes, never prose: the surface renders the operator's
|
|
5
5
|
* locale.
|
|
6
6
|
*
|
|
7
|
-
* omx-clap-core declares every hosting code
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
7
|
+
* omx-clap-core declares every hosting code: one `HOSTING_<DIMENSION>_CODES` table per dimension,
|
|
8
|
+
* in the TypeScript modules its package installs under `pkg-config --variable=tsdir omx-clap-core`
|
|
9
|
+
* (BUILDING.md). {@link HOSTING_CODE} is those tables flattened into one `key: code` map, written
|
|
10
|
+
* as plain data into `hosting-codes.generated.ts` by `tools/gen-hosting-codes.mjs` at build time:
|
|
11
|
+
* a code omx-clap-core does not declare cannot be named here, and this module reads no file and
|
|
12
|
+
* imports no `node:` module, so every public subpath that reaches it bundles for a browser.
|
|
13
|
+
* `src/contract-vocabulary-conformance.test.ts` holds the generated map to omx-clap-core's real
|
|
14
|
+
* modules, either way.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
17
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
-
import { join } from "node:path";
|
|
19
|
-
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { HOSTING_CODE } from "./hosting-codes.generated.js";
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
const CLAP_CORE_TS_MODULES = ["omx-clap-core-vocabulary.js", "omx-clap-core-hosting.js"] as const;
|
|
18
|
+
export { HOSTING_CODE };
|
|
23
19
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/** `native/fetch-deps.sh`'s own checkout of omx-clap-core, for a build with no omx-clap-core-devel installed. */
|
|
28
|
-
const DEPS_TS_DIR = join(PACKAGE_ROOT, "native/deps/omx-clap-core/ts");
|
|
29
|
-
|
|
30
|
-
/** Where `make install` of omx-clap-host puts them (BUILDING.md), for a run with no `pkg-config` binary at all. */
|
|
31
|
-
const DEFAULT_SYSTEM_TS_DIR = "/usr/share/omx-clap-core/ts";
|
|
32
|
-
|
|
33
|
-
/** `pkg-config --variable=tsdir omx-clap-core`, or `undefined` where pkg-config or the package is absent. */
|
|
34
|
-
function pkgConfigTsDir(env: Readonly<Record<string, string | undefined>>): string | undefined {
|
|
35
|
-
try {
|
|
36
|
-
const out = execFileSync(env.PKG_CONFIG ?? "pkg-config", ["--variable=tsdir", "omx-clap-core"], {
|
|
37
|
-
encoding: "utf8",
|
|
38
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
39
|
-
}).trim();
|
|
40
|
-
return out === "" ? undefined : out;
|
|
41
|
-
} catch {
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Where omx-clap-core's TypeScript modules are looked for, in order: `OMX_CLAP_CORE_TSDIR` when
|
|
48
|
-
* set (and only that), else `pkg-config --variable=tsdir omx-clap-core`, else the checkout
|
|
49
|
-
* `native/fetch-deps.sh` stages, else the path `make install` of omx-clap-host uses by default —
|
|
50
|
-
* a packaged plugin-qualify's runtime has no `pkg-config` binary to ask.
|
|
51
|
-
*/
|
|
52
|
-
export function clapCoreTsDirCandidates(env: Readonly<Record<string, string | undefined>> = process.env): readonly string[] {
|
|
53
|
-
const set = env.OMX_CLAP_CORE_TSDIR;
|
|
54
|
-
if (set !== undefined && set !== "") return [set];
|
|
55
|
-
const fromPkgConfig = pkgConfigTsDir(env);
|
|
56
|
-
return [...(fromPkgConfig === undefined ? [] : [fromPkgConfig]), DEPS_TS_DIR, DEFAULT_SYSTEM_TS_DIR];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function locateClapCoreTsDir(): string {
|
|
60
|
-
const candidates = clapCoreTsDirCandidates();
|
|
61
|
-
const dir = candidates.find((d) => CLAP_CORE_TS_MODULES.every((m) => existsSync(join(d, m))));
|
|
62
|
-
if (dir === undefined) {
|
|
63
|
-
throw new Error(
|
|
64
|
-
`omx-clap-core's TypeScript modules (${CLAP_CORE_TS_MODULES.join(", ")}) were not found in any of: ` +
|
|
65
|
-
`${candidates.join(", ")}. Install omx-clap-core-devel (pkg-config omx-clap-core), set ` +
|
|
66
|
-
"OMX_CLAP_CORE_TSDIR to its tsdir, or run native/fetch-deps.sh.",
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
return dir;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** True for an export name that is one of omx-clap-core's hosting tables. */
|
|
73
|
-
export function isHostingTableName(name: string): boolean {
|
|
74
|
-
return /^HOSTING_[A-Z0-9_]+_CODES$/.test(name);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
type HostingEntries = Record<string, { readonly code: string }>;
|
|
78
|
-
|
|
79
|
-
async function loadHostingTables(): Promise<Record<string, HostingEntries>> {
|
|
80
|
-
const dir = locateClapCoreTsDir();
|
|
81
|
-
const tables: Record<string, HostingEntries> = {};
|
|
82
|
-
for (const name of CLAP_CORE_TS_MODULES) {
|
|
83
|
-
// The modules are self-contained ES modules in a directory with no package.json: Node 18 reads such a .js as
|
|
84
|
-
// CommonJS and refuses `export`, so the source is imported as a data: module, which is a module on every Node.
|
|
85
|
-
const source = readFileSync(join(dir, name), "utf8");
|
|
86
|
-
const mod: Record<string, unknown> = await import(`data:text/javascript;base64,${Buffer.from(source).toString("base64")}`);
|
|
87
|
-
for (const [exportName, table] of Object.entries(mod)) {
|
|
88
|
-
if (isHostingTableName(exportName)) tables[exportName] = table as HostingEntries;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return tables;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function flatten(tables: Record<string, HostingEntries>): HostingCodeTable {
|
|
95
|
-
const table: Record<string, string> = {};
|
|
96
|
-
for (const [name, entries] of Object.entries(tables)) {
|
|
97
|
-
for (const [key, entry] of Object.entries(entries)) {
|
|
98
|
-
if (key in table) throw new Error(`omx-clap-core declares the hosting key ${key} twice (${name})`);
|
|
99
|
-
table[key] = entry.code;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return Object.freeze(table) as unknown as HostingCodeTable;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* omx-clap-core's hosting codes, by key, across its two TypeScript modules — hand-kept here
|
|
107
|
-
* (not derived from a static import: the source is a runtime-resolved install directory, not an
|
|
108
|
-
* npm dependency) and held to the real modules by
|
|
109
|
-
* `src/contract-vocabulary-conformance.test.ts`, which turns red on any drift either way.
|
|
110
|
-
*/
|
|
111
|
-
export interface HostingCodeTable {
|
|
112
|
-
readonly clapExtensionsNotCovered: "hosting.clap.extensions-not-covered";
|
|
113
|
-
readonly clapFormatNotHosted: "hosting.clap.format-not-hosted";
|
|
114
|
-
readonly clapHeadlessFailed: "hosting.clap.headless-failed";
|
|
115
|
-
readonly clapLatencyMismatch: "hosting.clap.latency-mismatch";
|
|
116
|
-
readonly clapNoteInput: "hosting.clap.note-input";
|
|
117
|
-
readonly clapProcessError: "hosting.clap.process-error";
|
|
118
|
-
readonly clapThreadViolation: "hosting.clap.thread-violation";
|
|
119
|
-
readonly clapUnqualified: "hosting.clap.unqualified";
|
|
120
|
-
readonly costAboveCeiling: "hosting.cost.above-ceiling";
|
|
121
|
-
readonly costFieldsMissing: "hosting.cost.fields-missing";
|
|
122
|
-
readonly costSpiky: "hosting.cost.spiky-tail";
|
|
123
|
-
readonly costUnmeasured: "hosting.cost.unmeasured-at-rate-and-quantum";
|
|
124
|
-
readonly costWithinCeiling: "hosting.cost.within-ceiling";
|
|
125
|
-
readonly featuresAtomInUnfed: "hosting.features.atom-in-unfed";
|
|
126
|
-
readonly featuresDisqualifying: "hosting.features.host-not-provided";
|
|
127
|
-
readonly featuresMidiIn: "hosting.features.midi-in-fed-empty";
|
|
128
|
-
readonly featuresMissing: "hosting.features.missing";
|
|
129
|
-
readonly featuresPortsUnreadable: "hosting.features.ports-unreadable";
|
|
130
|
-
readonly featuresProvided: "hosting.features.all-provided";
|
|
131
|
-
readonly featuresUnscanned: "hosting.features.scan-predates-field";
|
|
132
|
-
readonly idleHoldRefused: "hosting.idle.hold-refused";
|
|
133
|
-
readonly latencyNonconforming: "hosting.latency.nonconforming";
|
|
134
|
-
readonly latencyParameterDependent: "hosting.latency.parameter-dependent";
|
|
135
|
-
readonly latencyResolved: "hosting.latency.resolved";
|
|
136
|
-
readonly latencyUnmeasured: "hosting.latency.unmeasured";
|
|
137
|
-
readonly outputNonFinite: "hosting.output.non-finite";
|
|
138
|
-
readonly overrideIsolated: "hosting.override.curator-isolated";
|
|
139
|
-
readonly patchBusy: "hosting.patch.busy";
|
|
140
|
-
readonly patchNotWritable: "hosting.patch.not-writable";
|
|
141
|
-
readonly patchTooLarge: "hosting.patch.too-large";
|
|
142
|
-
readonly presetNotFound: "hosting.preset.not-found";
|
|
143
|
-
readonly hostProvenanceDiffers: "hosting.provenance.host-differs";
|
|
144
|
-
readonly sweepControlDidNotFire: "hosting.provenance.sweep-control-did-not-fire";
|
|
145
|
-
readonly disabled: "hosting.disabled";
|
|
146
|
-
readonly linked: "hosting.linked";
|
|
147
|
-
readonly noRealisation: "hosting.no-realisation";
|
|
148
|
-
readonly realisedInProcess: "hosting.realised.in-process";
|
|
149
|
-
readonly unjudged: "hosting.unjudged";
|
|
150
|
-
readonly rtSafetyClean: "hosting.rt-safety.clean-under-sweep";
|
|
151
|
-
readonly rtSafetyNotReproducible: "hosting.rt-safety.not-reproducible";
|
|
152
|
-
readonly rtSafetyNotesClean: "hosting.rt-safety.clean-under-notes";
|
|
153
|
-
readonly rtSafetyNotesUnmeasured: "hosting.rt-safety.notes-unmeasured";
|
|
154
|
-
readonly rtSafetyNotesViolations: "hosting.rt-safety.violations-under-notes";
|
|
155
|
-
readonly rtSafetyUnmeasured: "hosting.rt-safety.unmeasured";
|
|
156
|
-
readonly rtSafetyViolations: "hosting.rt-safety.violations-in-run";
|
|
157
|
-
readonly stabilityBelowFloor: "hosting.stability.cycles-below-floor";
|
|
158
|
-
readonly stabilityCrashUnattributed: "hosting.stability.crash-unattributed";
|
|
159
|
-
readonly stabilityCrashedAttributed: "hosting.stability.crashed-attributed-plugin";
|
|
160
|
-
readonly stabilityCrashedLive: "hosting.stability.crashed-live-on-this-rig";
|
|
161
|
-
readonly stabilityLinksGuiToolkit: "hosting.stability.links-gui-toolkit";
|
|
162
|
-
readonly stabilitySoakFailedAtRate: "hosting.stability.soak-failed-at-rate";
|
|
163
|
-
readonly stabilitySoaked: "hosting.stability.soaked";
|
|
164
|
-
readonly stabilitySpawnsThreads: "hosting.stability.spawns-threads";
|
|
165
|
-
readonly stabilityUnmeasured: "hosting.stability.unmeasured";
|
|
166
|
-
readonly stabilityUnsoaked: "hosting.stability.unsoaked";
|
|
167
|
-
readonly stateNotACopy: "hosting.state.not-a-copy";
|
|
168
|
-
readonly statePathOnAudio: "hosting.state.path-on-audio";
|
|
169
|
-
readonly statePathOutside: "hosting.state.path-outside";
|
|
170
|
-
readonly statePathSymlink: "hosting.state.path-symlink";
|
|
171
|
-
readonly statePathTooLong: "hosting.state.path-too-long";
|
|
172
|
-
readonly statePathUnmakeable: "hosting.state.path-unmakeable";
|
|
173
|
-
readonly stateUnreadable: "hosting.state.unreadable";
|
|
174
|
-
readonly topologyAmbisonicConfig: "hosting.topology.ambisonic-config";
|
|
175
|
-
readonly topologyChannelMapUnknown: "hosting.topology.channel-map-unknown";
|
|
176
|
-
readonly topologyInstrument: "hosting.topology.instrument";
|
|
177
|
-
readonly topologyMatched: "hosting.topology.matched";
|
|
178
|
-
readonly topologyNoAudioInput: "hosting.topology.no-audio-input";
|
|
179
|
-
readonly topologyNoAudioOutput: "hosting.topology.no-audio-output";
|
|
180
|
-
readonly topologyPortTooWide: "hosting.topology.port-too-wide";
|
|
181
|
-
readonly topologyPortWithoutChannels: "hosting.topology.port-without-channels";
|
|
182
|
-
readonly topologyTooManyPorts: "hosting.topology.too-many-ports";
|
|
183
|
-
readonly topologyWiderThanStrip: "hosting.topology.wider-than-strip";
|
|
184
|
-
readonly workerNonconforming: "hosting.worker.nonconforming";
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export const HOSTING_CODE: HostingCodeTable = flatten(await loadHostingTables());
|
|
20
|
+
/** {@link HOSTING_CODE}'s type: each key's literal code. */
|
|
21
|
+
export type HostingCodeTable = typeof HOSTING_CODE;
|
|
@@ -147,6 +147,35 @@ export const LV2_HOSTING_LIFECYCLES = 30;
|
|
|
147
147
|
export const LV2_HOSTING_REPEATS = 3;
|
|
148
148
|
export const LV2_HOSTING_TIMEOUT_S = 240;
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* The scanned descriptors (owner spec 6.6 and 6.9): both scanners write one file per plugin into the
|
|
152
|
+
* directory the key `descriptor_dir` names, `<id>@<version>-<binary sha256 first DESCRIPTOR_HASH_PREFIX>.json`.
|
|
153
|
+
* With no directory named it is `$XDG_STATE_HOME/<DESCRIPTOR_DIR_STATE_SUBDIR>`, else
|
|
154
|
+
* `~/.local/state/<DESCRIPTOR_DIR_STATE_SUBDIR>`; a system service's is `DESCRIPTOR_DIR_SYSTEM`.
|
|
155
|
+
*/
|
|
156
|
+
export const DESCRIPTOR_DIR_KEY = "descriptor_dir";
|
|
157
|
+
export const DESCRIPTOR_DIR_STATE_SUBDIR = "omx/descriptors";
|
|
158
|
+
export const DESCRIPTOR_DIR_SYSTEM = "/var/lib/omx/descriptors";
|
|
159
|
+
export const DESCRIPTOR_HASH_PREFIX = 12;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The one descriptor schema both scanners hold a file to: omx-contract's, named by `DESCRIPTOR_SCHEMA_ENV`
|
|
163
|
+
* when set (a test, or a checkout pointing at another contract), else the omx-contract package this one
|
|
164
|
+
* depends on, else `DESCRIPTOR_SCHEMA_INSTALLED`. A written descriptor requires `DESCRIPTOR_SCHEMA_FLOOR`.
|
|
165
|
+
*/
|
|
166
|
+
export const DESCRIPTOR_SCHEMA_ENV = "OMX_CONTRACT_SCHEMA";
|
|
167
|
+
export const DESCRIPTOR_SCHEMA_INSTALLED = "/usr/share/omx-contract/omx-contract.schema.json";
|
|
168
|
+
export const DESCRIPTOR_SCHEMA_FLOOR = ">= 3.0.0";
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Why a scanned descriptor is not written. A file the schema refuses is omx-contract's own
|
|
172
|
+
* `contract.file.invalid`; the others are the scanners' own.
|
|
173
|
+
*/
|
|
174
|
+
export const DESCRIPTOR_CODE_SCHEMA = "contract.file.invalid";
|
|
175
|
+
export const DESCRIPTOR_CODE_SCHEMA_MISSING = "qualify.descriptor.schema-missing";
|
|
176
|
+
export const DESCRIPTOR_CODE_BINARY_UNHASHED = "qualify.descriptor.binary-unhashed";
|
|
177
|
+
export const DESCRIPTOR_CODE_DIR_UNWRITABLE = "qualify.descriptor.dir-unwritable";
|
|
178
|
+
|
|
150
179
|
|
|
151
180
|
|
|
152
181
|
|
package/tools/_declarations.py
CHANGED
|
@@ -19,3 +19,14 @@ QUALIFY_QUANTA = (128, 256, 512, 1024)
|
|
|
19
19
|
PLUGIN_TIMEOUT_S = 15
|
|
20
20
|
BOOT_TIMEOUT_S = 120
|
|
21
21
|
COST_TIMEOUT_S = 60
|
|
22
|
+
DESCRIPTOR_DIR_KEY = "descriptor_dir"
|
|
23
|
+
DESCRIPTOR_DIR_STATE_SUBDIR = "omx/descriptors"
|
|
24
|
+
DESCRIPTOR_DIR_SYSTEM = "/var/lib/omx/descriptors"
|
|
25
|
+
DESCRIPTOR_HASH_PREFIX = 12
|
|
26
|
+
DESCRIPTOR_SCHEMA_ENV = "OMX_CONTRACT_SCHEMA"
|
|
27
|
+
DESCRIPTOR_SCHEMA_INSTALLED = "/usr/share/omx-contract/omx-contract.schema.json"
|
|
28
|
+
DESCRIPTOR_SCHEMA_FLOOR = ">= 3.0.0"
|
|
29
|
+
DESCRIPTOR_CODE_SCHEMA = "contract.file.invalid"
|
|
30
|
+
DESCRIPTOR_CODE_SCHEMA_MISSING = "qualify.descriptor.schema-missing"
|
|
31
|
+
DESCRIPTOR_CODE_BINARY_UNHASHED = "qualify.descriptor.binary-unhashed"
|
|
32
|
+
DESCRIPTOR_CODE_DIR_UNWRITABLE = "qualify.descriptor.dir-unwritable"
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
# Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
4
|
+
#
|
|
5
|
+
# audit-overlap.sh — every number in docs/audit-qualify-vs-compat.md, from source, by one command each.
|
|
6
|
+
#
|
|
7
|
+
# tools/audit-overlap.sh <omx-clap-core dir> <plugin-hostd dir>
|
|
8
|
+
# tools/audit-overlap.sh --self-test
|
|
9
|
+
#
|
|
10
|
+
# It prints one line per unit (a function, or a set of functions, in one repository):
|
|
11
|
+
# unit <id> <repo> <file>:<first>-<last> lines=<n>
|
|
12
|
+
# one line per pair that does the same job in two places:
|
|
13
|
+
# pair <a> <b> shared=<s> a=<na> b=<nb> overlap=<s/min(na,nb) %> diff=<changed lines or "-"> strings=<both/a/b>
|
|
14
|
+
# and one line per counted marker:
|
|
15
|
+
# count <id> <repo> <n>
|
|
16
|
+
# A unit is found by its name (a C or TypeScript function runs to the first column-0 `}`, a Python def or class
|
|
17
|
+
# to the next column-0 statement), never by a line number, so the numbers follow the code. The audited commits are
|
|
18
|
+
# printed first (`tree <repo> <commit>`); the document states which ones it quotes.
|
|
19
|
+
#
|
|
20
|
+
# Similarity: both bodies are normalised (comments, blank lines and lone braces dropped, whitespace squeezed), then
|
|
21
|
+
# `shared` is the number of normalised lines the two have in common (comm -12 over the sorted lines, duplicates
|
|
22
|
+
# kept) and `diff` the number of lines `diff` adds or removes between them. Code in two languages shares no line;
|
|
23
|
+
# for it the pair prints overlap=0 and diff=-. `strings` is shared/a/b of the distinct string literals (three or
|
|
24
|
+
# more characters) each body spells: messages, field names and codes, which survive a change of brace style or of
|
|
25
|
+
# language, so a port of the same code shows there when its lines do not.
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
export LC_ALL=C
|
|
28
|
+
Q="$(cd "$(dirname "$0")/.." && pwd)"
|
|
29
|
+
|
|
30
|
+
die() { echo "audit-overlap: $*" >&2; exit 2; }
|
|
31
|
+
|
|
32
|
+
# span <lang> <file> <name> — print "<first> <last>" of the named unit, nothing when there is none
|
|
33
|
+
span() { # <lang> <file> <name>
|
|
34
|
+
awk -v lang="$1" -v name="$3" '
|
|
35
|
+
function starts(l) {
|
|
36
|
+
if (lang == "py") return l ~ ("^(def|class) " name "[(:]")
|
|
37
|
+
if (lang == "ts") return l ~ ("^(export )?(async )?function " name "[(<]")
|
|
38
|
+
return l ~ ("^[A-Za-z].*[ *]" name "\\(") && l !~ /;[ \t]*$/
|
|
39
|
+
}
|
|
40
|
+
!on && starts($0) { on = 1; first = NR; last = NR; next }
|
|
41
|
+
on && lang != "py" && /^}/ { last = NR; print first, last; exit }
|
|
42
|
+
on && lang == "py" && /^[^ \t#\n]/ { print first, last; exit }
|
|
43
|
+
on && lang == "py" && /[^ \t]/ { last = NR }
|
|
44
|
+
END { if (on && lang == "py") print first, last }
|
|
45
|
+
' "$2"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
|
|
49
|
+
declare -A ROOT LANG_OF
|
|
50
|
+
|
|
51
|
+
# unit <id> <repo> <lang> <file> <name>... — one unit, the names' bodies concatenated
|
|
52
|
+
unit() {
|
|
53
|
+
local id="$1" repo="$2" lang="$3" file="$4"; shift 4
|
|
54
|
+
local root="${ROOT[$repo]}" n total=0 where="" s
|
|
55
|
+
: > "$WORK/$id"
|
|
56
|
+
for n in "$@"; do
|
|
57
|
+
s="$(span "$lang" "$root/$file" "$n")"
|
|
58
|
+
[ -n "$s" ] || die "$repo $file: no $lang unit named $n"
|
|
59
|
+
local a b; read -r a b <<<"$s"
|
|
60
|
+
sed -n "${a},${b}p" "$root/$file" >> "$WORK/$id"
|
|
61
|
+
total=$((total + b - a + 1)); where="${where:+$where,}$a-$b"
|
|
62
|
+
done
|
|
63
|
+
LANG_OF[$id]="$lang"
|
|
64
|
+
echo "unit $id $repo $file:$where lines=$total"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# whole <id> <repo> <lang> <file> — a whole file as one unit
|
|
68
|
+
whole() {
|
|
69
|
+
local id="$1" repo="$2" lang="$3" file="$4" n
|
|
70
|
+
cp "${ROOT[$repo]}/$file" "$WORK/$id"; n=$(wc -l < "$WORK/$id")
|
|
71
|
+
LANG_OF[$id]="$lang"
|
|
72
|
+
echo "unit $id $repo $file:1-$n lines=$n"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
norm() { # <lang> < body
|
|
76
|
+
case "$1" in
|
|
77
|
+
py) sed -E 's/#.*$//' ;;
|
|
78
|
+
*) sed -E 's#//.*$##; s#/\*.*\*/##g; /^[[:space:]]*\*( |\/|$)/d; /^[[:space:]]*\/\*/d' ;;
|
|
79
|
+
esac | sed -E 's/[[:space:]]+/ /g; s/^ //; s/ $//' | grep -vE '^[{}();, ]*$' || true
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
lits() { grep -oE "\"[^\"\\\\]{3,}\"|'[^'\\\\]{3,}'" | sed -E "s/^.(.*).$/\\1/"; } # quotes dropped: '' and "" alike
|
|
83
|
+
|
|
84
|
+
pair() { # <a> <b>
|
|
85
|
+
local a="$1" b="$2" na nb s d m l
|
|
86
|
+
norm "${LANG_OF[$a]}" < "$WORK/$a" | sort > "$WORK/$a.n"
|
|
87
|
+
norm "${LANG_OF[$b]}" < "$WORK/$b" | sort > "$WORK/$b.n"
|
|
88
|
+
na=$(wc -l < "$WORK/$a.n"); nb=$(wc -l < "$WORK/$b.n")
|
|
89
|
+
s=$(comm -12 "$WORK/$a.n" "$WORK/$b.n" | wc -l)
|
|
90
|
+
m=$(( na < nb ? na : nb ))
|
|
91
|
+
if [ "${LANG_OF[$a]}" = "${LANG_OF[$b]}" ]; then
|
|
92
|
+
norm "${LANG_OF[$a]}" < "$WORK/$a" > "$WORK/$a.o"; norm "${LANG_OF[$b]}" < "$WORK/$b" > "$WORK/$b.o"
|
|
93
|
+
d=$(diff "$WORK/$a.o" "$WORK/$b.o" | grep -c '^[<>]' || true)
|
|
94
|
+
else d="-"; fi
|
|
95
|
+
# the string literals (messages, field names, codes) both spell: what survives a change of style or language
|
|
96
|
+
norm "${LANG_OF[$a]}" < "$WORK/$a" | lits | sort -u > "$WORK/$a.l" || true
|
|
97
|
+
norm "${LANG_OF[$b]}" < "$WORK/$b" | lits | sort -u > "$WORK/$b.l" || true
|
|
98
|
+
l="$(comm -12 "$WORK/$a.l" "$WORK/$b.l" | wc -l)/$(wc -l < "$WORK/$a.l")/$(wc -l < "$WORK/$b.l")"
|
|
99
|
+
echo "pair $a $b shared=$s a=$na b=$nb overlap=$(( m ? 100 * s / m : 0 ))% diff=$d strings=$l"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
count() { # <id> <repo> <git grep args> -- <pathspec>... (this audit's own two files are never counted)
|
|
103
|
+
local id="$1" repo="$2"; shift 2
|
|
104
|
+
local self=(":(exclude)tools/audit-overlap.sh" ":(exclude)docs/audit-qualify-vs-compat.md")
|
|
105
|
+
echo "count $id $repo $(git -C "${ROOT[$repo]}" grep -IE "$@" "${self[@]}" | wc -l)"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
audit() {
|
|
109
|
+
local r
|
|
110
|
+
for r in qualify clap-core hostd; do echo "tree $r $(git -C "${ROOT[$r]}" rev-parse --short HEAD)"; done
|
|
111
|
+
|
|
112
|
+
# 1. plugin enumeration
|
|
113
|
+
unit q.clap-walk qualify c native/clap-qualify.c is_clap walk scan
|
|
114
|
+
unit c.clap-walk clap-core c src/scan.c has_suffix walk main
|
|
115
|
+
unit q.clap-fork qualify c native/clap-qualify.c scan_file
|
|
116
|
+
unit c.clap-fork clap-core c src/scan.c scan_file
|
|
117
|
+
unit q.clap-describe qualify c native/clap-qualify.c scan_file_entry scan_plugin scan_params scan_audio_ports
|
|
118
|
+
unit c.clap-describe clap-core c src/scan.c put_file put_plugin put_descriptor put_params put_audio_ports
|
|
119
|
+
unit q.clap-path qualify ts src/clap-loader.ts clapSearchPath
|
|
120
|
+
unit c.clap-path clap-core c src/scan.c default_paths
|
|
121
|
+
unit q.lv2-manifest qualify ts src/lv2-loader.ts pluginsInManifest lv2BundlesOnPath
|
|
122
|
+
unit c.lv2-manifest clap-core c src/lv2_locate.c lv2_locate_manifest_names walk_dir lv2_locate
|
|
123
|
+
unit q.lv2-lilv qualify py tools/scan.py Scanner
|
|
124
|
+
unit c.lv2-lilv clap-core py tests/lv2_parity.py lilv_facts
|
|
125
|
+
pair q.clap-walk c.clap-walk
|
|
126
|
+
pair q.clap-fork c.clap-fork
|
|
127
|
+
pair q.clap-describe c.clap-describe
|
|
128
|
+
pair q.clap-path c.clap-path
|
|
129
|
+
pair q.lv2-manifest c.lv2-manifest
|
|
130
|
+
pair q.lv2-lilv c.lv2-lilv
|
|
131
|
+
count q.calls-omx-clap-scan qualify -e 'omx-clap-scan' -- src
|
|
132
|
+
# clap-qualify's whole scan mode (--json), which writes the document omx-clap-scan writes, plus labels and pages
|
|
133
|
+
unit q.scan-mode qualify c native/clap-qualify.c put_labels scan_params scan_remote_controls scan_audio_ports \
|
|
134
|
+
scan_plugin put_file_error scan_file_entry scan_file list_add is_clap walk scan
|
|
135
|
+
count c.scan-labels-pages clap-core -i -e 'remote_controls|enum_labels|"labels"' -- src/scan.c
|
|
136
|
+
|
|
137
|
+
# 2. deterministic input
|
|
138
|
+
count q.lcg-nr qualify -e '1664525u' -- native
|
|
139
|
+
count q.lcg-glibc qualify -e '1103515245u' -- native
|
|
140
|
+
count c.lcg-nr clap-core -e '1664525u' -- tests src
|
|
141
|
+
unit q.noise-lv2 qualify c native/lv2-measure.c fill_input
|
|
142
|
+
unit c.signal clap-core c tests/jack_parity.c noise signal_at
|
|
143
|
+
pair q.noise-lv2 c.signal
|
|
144
|
+
unit q.notes qualify c native/clap-qualify.c note_event note_midi note_key note_block note_dialect
|
|
145
|
+
unit q.notes-py qualify py tools/benchmark.py held_keys midi_sequence
|
|
146
|
+
unit c.notes clap-core c tests/jack_parity.c midi_events
|
|
147
|
+
pair q.notes c.notes
|
|
148
|
+
pair q.notes-py q.notes
|
|
149
|
+
|
|
150
|
+
# 3. isolated PipeWire, the feeder
|
|
151
|
+
count q.pipewire qualify -i -e 'pipewire|unshare' -- src native tools bin
|
|
152
|
+
count c.unshare clap-core -e 'unshare' -- tests
|
|
153
|
+
unit c.pw-parity clap-core py tests/lv2_parity.py start_pipewire
|
|
154
|
+
unit c.pw-coverage clap-core py tests/clap_coverage.py start_pipewire
|
|
155
|
+
unit c.ns-parity clap-core py tests/lv2_parity.py inside_run cmd_run
|
|
156
|
+
unit c.ns-coverage clap-core py tests/clap_coverage.py inside_host cmd_host
|
|
157
|
+
unit c.clock clap-core c tests/jack_parity.c clock_process clock_state clock_wait clock_start clock_cycle clock_run
|
|
158
|
+
pair c.pw-parity c.pw-coverage
|
|
159
|
+
pair c.ns-parity c.ns-coverage
|
|
160
|
+
|
|
161
|
+
# 4. host launch, teardown, crash and hang
|
|
162
|
+
unit q.probe-runner qualify ts src/probe-runner.ts runOne probeTarget
|
|
163
|
+
unit h.run-child hostd c src/probe.c apply_limits kill_tree run_child child_end
|
|
164
|
+
unit q.bench-worker qualify py tools/benchmark.py WorkerQueue _Worker
|
|
165
|
+
unit c.host-parity clap-core py tests/lv2_parity.py Host run_plugin
|
|
166
|
+
unit c.host-coverage clap-core py tests/clap_coverage.py Host
|
|
167
|
+
pair c.host-parity c.host-coverage
|
|
168
|
+
pair q.bench-worker c.host-parity
|
|
169
|
+
count h.runs-clap-qualify hostd -e 'clap-qualify|clap_qualify' -- src
|
|
170
|
+
count h.runs-lv2-measure hostd -e 'lv2_measure|lv2-measure' -- src
|
|
171
|
+
|
|
172
|
+
# 5. records, classification, vocabulary, reports
|
|
173
|
+
unit q.records qualify ts src/measurement/sweep-jsonl.ts measuredPluginsFromSweep parseJsonl
|
|
174
|
+
unit c.tsv-parity clap-core py tests/lv2_parity.py read_tsv write_tsv
|
|
175
|
+
unit c.tsv-coverage clap-core py tests/clap_coverage.py read_tsv write_tsv append_tsv
|
|
176
|
+
pair c.tsv-parity c.tsv-coverage
|
|
177
|
+
unit q.classify qualify ts src/hosting-suitability.ts classifyPluginHosting
|
|
178
|
+
unit c.classify clap-core py tests/lv2_parity.py _classify
|
|
179
|
+
pair q.classify c.classify
|
|
180
|
+
unit c.report clap-core py tests/lv2_parity.py report_text
|
|
181
|
+
count q.contract-imports qualify -e "from \"@openmixer/omx-contract" -- src
|
|
182
|
+
count c.contract-macros clap-core -e 'OMX_HOSTING_[A-Z_]+' -- src lv2
|
|
183
|
+
count q.handtyped-hosting qualify -e '"hosting\.[a-z-]+\.[a-z-]+"' -- 'src/*.ts' ':!src/*.test.ts'
|
|
184
|
+
|
|
185
|
+
# 6. cost and latency
|
|
186
|
+
unit q.pct-c qualify c native/clap-qualify.c percentile_sorted
|
|
187
|
+
unit q.pct-py qualify py tools/benchmark.py percentile
|
|
188
|
+
unit q.cost-c qualify c native/clap-qualify.c cost_rate
|
|
189
|
+
unit c.stage-bench clap-core c tests/stage_bench.c main
|
|
190
|
+
pair q.pct-c q.pct-py
|
|
191
|
+
pair q.cost-c c.stage-bench
|
|
192
|
+
|
|
193
|
+
# the probing programs plugin-hostd's probe runs as its children today, and what the move into its child deletes
|
|
194
|
+
whole q.prober-clap qualify c native/clap-qualify.c
|
|
195
|
+
whole q.prober-lv2 qualify c native/lv2-measure.c
|
|
196
|
+
whole q.benchmark qualify py tools/benchmark.py
|
|
197
|
+
|
|
198
|
+
# 7. mod-host in plugin-qualify: code that runs it, and words that name it
|
|
199
|
+
count q.modhost-spawn qualify -e "spawn[A-Za-z]*\\(['\"]mod-host|execvp?\\(\"mod-host" -- src native tools bin
|
|
200
|
+
count q.modhost-words qualify -i -e 'mod-host' -- .
|
|
201
|
+
count c.modhost-words clap-core -i -e 'mod-host' -- tests
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
self_test() {
|
|
205
|
+
T="$(mktemp -d)"; local t="$T"; trap 'rm -rf "$T" "$WORK"' EXIT
|
|
206
|
+
for r in a b c; do mkdir -p "$t/$r"; git -C "$t/$r" init -q; done
|
|
207
|
+
cat > "$t/a/x.c" <<'EOF'
|
|
208
|
+
static int one(int v)
|
|
209
|
+
{
|
|
210
|
+
/* a comment */
|
|
211
|
+
return v + !"same"; /* "comment" */
|
|
212
|
+
}
|
|
213
|
+
static int two(int v) {
|
|
214
|
+
return v * 2;
|
|
215
|
+
}
|
|
216
|
+
EOF
|
|
217
|
+
cat > "$t/b/y.py" <<'EOF'
|
|
218
|
+
def one(v):
|
|
219
|
+
# a comment
|
|
220
|
+
return v + len("same") # "comment"
|
|
221
|
+
|
|
222
|
+
X = 1
|
|
223
|
+
EOF
|
|
224
|
+
cp "$t/a/x.c" "$t/c/z.c"; sed -i 's/v \* 2/v * 3/' "$t/c/z.c"
|
|
225
|
+
for r in a b c; do git -C "$t/$r" add -A; git -C "$t/$r" -c user.name=t -c user.email=t@t commit -qm t; done
|
|
226
|
+
ROOT[qualify]="$t/a"; ROOT[clap-core]="$t/b"; ROOT[hostd]="$t/c"
|
|
227
|
+
local out fail=0
|
|
228
|
+
expect() { grep -qxF -- "$1" <<<"$out" || { echo "self-test: missing: $1" >&2; fail=1; }; }
|
|
229
|
+
out="$(unit u1 qualify c x.c one; unit u2 qualify c x.c two; unit p1 clap-core py y.py one
|
|
230
|
+
unit h1 hostd c z.c one two; unit h2 qualify c x.c one two
|
|
231
|
+
pair h2 h1; pair u1 p1; count n qualify -e 'return' -- .)"
|
|
232
|
+
expect "unit u1 qualify x.c:1-5 lines=5"
|
|
233
|
+
expect "unit u2 qualify x.c:6-8 lines=3"
|
|
234
|
+
expect "unit p1 clap-core y.py:1-3 lines=3"
|
|
235
|
+
expect "pair h2 h1 shared=3 a=4 b=4 overlap=75% diff=2 strings=1/1/1"
|
|
236
|
+
expect "pair u1 p1 shared=0 a=2 b=2 overlap=0% diff=- strings=1/1/1"
|
|
237
|
+
expect "count n qualify 2"
|
|
238
|
+
# a name that is not there is refused, never counted as zero
|
|
239
|
+
if (unit u3 qualify c x.c three) 2>/dev/null; then echo "self-test: a missing unit was not refused" >&2; fail=1; fi
|
|
240
|
+
[ "$fail" = 0 ] && echo "audit-overlap self-test: ok"
|
|
241
|
+
return "$fail"
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
case "${1:-}" in
|
|
245
|
+
--self-test) self_test ;;
|
|
246
|
+
-h|--help|"") sed -n '5,9p' "$0"; [ -n "${1:-}" ] ;;
|
|
247
|
+
*) [ $# = 2 ] || die "usage: $0 <omx-clap-core dir> <plugin-hostd dir>"
|
|
248
|
+
ROOT[qualify]="$Q"; ROOT[clap-core]="$(cd "$1" && pwd)"; ROOT[hostd]="$(cd "$2" && pwd)"
|
|
249
|
+
audit ;;
|
|
250
|
+
esac
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
|
+
/** The two TypeScript modules omx-clap-core's `tsdir` carries. */
|
|
4
|
+
export const CLAP_CORE_TS_MODULES: readonly string[];
|
|
5
|
+
/** Where omx-clap-core's TypeScript modules are looked for, in order. */
|
|
6
|
+
export function clapCoreTsDirCandidates(env?: Readonly<Record<string, string | undefined>>): readonly string[];
|
|
7
|
+
/** The first candidate holding both modules, or `undefined`. */
|
|
8
|
+
export function findClapCoreTsDir(env?: Readonly<Record<string, string | undefined>>): string | undefined;
|
|
9
|
+
/** True for an export name that is one of omx-clap-core's hosting tables. */
|
|
10
|
+
export function isHostingTableName(name: string): boolean;
|
|
11
|
+
/** One hosting table: key to its entry. */
|
|
12
|
+
export type HostingEntries = Readonly<Record<string, { readonly code: string }>>;
|
|
13
|
+
/** Every hosting table the modules in `dir` export, by export name. */
|
|
14
|
+
export function loadHostingTables(dir: string): Promise<Record<string, HostingEntries>>;
|
|
15
|
+
/** The tables flattened into one `key: code` map. */
|
|
16
|
+
export function flattenHostingTables(tables: Readonly<Record<string, HostingEntries>>): Record<string, string>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
|
+
/*
|
|
4
|
+
* clap-core-tsdir.mjs — where omx-clap-core's TypeScript modules are, and their hosting tables.
|
|
5
|
+
*
|
|
6
|
+
* omx-clap-core declares every hosting code: one `HOSTING_<DIMENSION>_CODES` table per dimension,
|
|
7
|
+
* rendered into the modules its package installs under `pkg-config --variable=tsdir omx-clap-core`
|
|
8
|
+
* (BUILDING.md) — `omx-clap-core-vocabulary.js` for the dimensions carried from omx-contract,
|
|
9
|
+
* `omx-clap-core-hosting.js` for `hosting.idle.*`. `tools/gen-hosting-codes.mjs` reads them here
|
|
10
|
+
* to write `src/hosting-codes.generated.ts`, and the conformance test reads them here to hold that
|
|
11
|
+
* file to them.
|
|
12
|
+
*
|
|
13
|
+
* Node only, and build and test only: no module under src/ imports it, so nothing a public subpath
|
|
14
|
+
* reaches touches the filesystem or a child process.
|
|
15
|
+
*/
|
|
16
|
+
import { execFileSync } from 'node:child_process';
|
|
17
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
|
+
import { dirname, join, resolve } from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
|
|
21
|
+
/** The two TypeScript modules omx-clap-core's `tsdir` carries, each a `HOSTING_<DIMENSION>_CODES` source. */
|
|
22
|
+
export const CLAP_CORE_TS_MODULES = Object.freeze(['omx-clap-core-vocabulary.js', 'omx-clap-core-hosting.js']);
|
|
23
|
+
|
|
24
|
+
/** This package's root. */
|
|
25
|
+
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
26
|
+
|
|
27
|
+
/** `native/fetch-deps.sh`'s own checkout of omx-clap-core, for a build with no omx-clap-core-devel installed. */
|
|
28
|
+
const DEPS_TS_DIR = join(PACKAGE_ROOT, 'native/deps/omx-clap-core/ts');
|
|
29
|
+
|
|
30
|
+
/** Where `make install` of omx-clap-host puts them (BUILDING.md), for a run with no `pkg-config` binary at all. */
|
|
31
|
+
const DEFAULT_SYSTEM_TS_DIR = '/usr/share/omx-clap-core/ts';
|
|
32
|
+
|
|
33
|
+
/** `pkg-config --variable=tsdir omx-clap-core`, or `undefined` where pkg-config or the package is absent. */
|
|
34
|
+
function pkgConfigTsDir(env) {
|
|
35
|
+
try {
|
|
36
|
+
const out = execFileSync(env.PKG_CONFIG ?? 'pkg-config', ['--variable=tsdir', 'omx-clap-core'], {
|
|
37
|
+
encoding: 'utf8',
|
|
38
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
39
|
+
}).trim();
|
|
40
|
+
return out === '' ? undefined : out;
|
|
41
|
+
} catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Where omx-clap-core's TypeScript modules are looked for, in order: `OMX_CLAP_CORE_TSDIR` when
|
|
48
|
+
* set (and only that), else `pkg-config --variable=tsdir omx-clap-core`, else the checkout
|
|
49
|
+
* `native/fetch-deps.sh` stages, else the path `make install` of omx-clap-host uses by default.
|
|
50
|
+
*/
|
|
51
|
+
export function clapCoreTsDirCandidates(env = process.env) {
|
|
52
|
+
const set = env.OMX_CLAP_CORE_TSDIR;
|
|
53
|
+
if (set !== undefined && set !== '') return [set];
|
|
54
|
+
const fromPkgConfig = pkgConfigTsDir(env);
|
|
55
|
+
return [...(fromPkgConfig === undefined ? [] : [fromPkgConfig]), DEPS_TS_DIR, DEFAULT_SYSTEM_TS_DIR];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The first candidate holding both modules, or `undefined`. */
|
|
59
|
+
export function findClapCoreTsDir(env = process.env) {
|
|
60
|
+
return clapCoreTsDirCandidates(env).find((d) => CLAP_CORE_TS_MODULES.every((m) => existsSync(join(d, m))));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** True for an export name that is one of omx-clap-core's hosting tables. */
|
|
64
|
+
export function isHostingTableName(name) {
|
|
65
|
+
return /^HOSTING_[A-Z0-9_]+_CODES$/.test(name);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Every `HOSTING_<DIMENSION>_CODES` table the two modules in `dir` export, by export name, in module order. */
|
|
69
|
+
export async function loadHostingTables(dir) {
|
|
70
|
+
const tables = {};
|
|
71
|
+
for (const name of CLAP_CORE_TS_MODULES) {
|
|
72
|
+
// The modules are self-contained ES modules in a directory with no package.json: Node 18 reads such a .js as
|
|
73
|
+
// CommonJS and refuses `export`, so the source is imported as a data: module, which is a module on every Node.
|
|
74
|
+
const source = readFileSync(join(dir, name), 'utf8');
|
|
75
|
+
const mod = await import(`data:text/javascript;base64,${Buffer.from(source).toString('base64')}`);
|
|
76
|
+
for (const [exportName, table] of Object.entries(mod)) {
|
|
77
|
+
if (isHostingTableName(exportName)) tables[exportName] = table;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return tables;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The tables flattened into one `key: code` map; a key two tables declare is refused. */
|
|
84
|
+
export function flattenHostingTables(tables) {
|
|
85
|
+
const flat = {};
|
|
86
|
+
for (const [name, entries] of Object.entries(tables)) {
|
|
87
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
88
|
+
if (key in flat) throw new Error(`omx-clap-core declares the hosting key ${key} twice (${name})`);
|
|
89
|
+
flat[key] = entry.code;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return flat;
|
|
93
|
+
}
|