@agiledigital/pingone-aic-script-tester 0.1.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.
- package/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +274 -0
- package/dist/bin/fetch-jar.d.ts +2 -0
- package/dist/bin/fetch-jar.js +13 -0
- package/dist/bin/pull-profile.d.ts +2 -0
- package/dist/bin/pull-profile.js +40 -0
- package/dist/bin/show-log.d.ts +2 -0
- package/dist/bin/show-log.js +31 -0
- package/dist/classes/.complete +12 -0
- package/dist/classes/AmClassShutter.class +0 -0
- package/dist/classes/AmContextFactory$ObservedContext.class +0 -0
- package/dist/classes/AmContextFactory.class +0 -0
- package/dist/classes/AmScopes.class +0 -0
- package/dist/classes/HostOps.class +0 -0
- package/dist/classes/JsValues.class +0 -0
- package/dist/classes/Json$Parser.class +0 -0
- package/dist/classes/Json.class +0 -0
- package/dist/classes/LanguageVersions.class +0 -0
- package/dist/classes/Runner.class +0 -0
- package/dist/classes/ScriptContextScope.class +0 -0
- package/dist/src/aic/callbacks.d.ts +35 -0
- package/dist/src/aic/callbacks.js +130 -0
- package/dist/src/aic/conform.d.ts +90 -0
- package/dist/src/aic/conform.js +193 -0
- package/dist/src/aic/constants.d.ts +13 -0
- package/dist/src/aic/constants.js +13 -0
- package/dist/src/aic/diff.d.ts +25 -0
- package/dist/src/aic/diff.js +201 -0
- package/dist/src/aic/emit-journey.d.ts +64 -0
- package/dist/src/aic/emit-journey.js +254 -0
- package/dist/src/aic/emit-result.d.ts +10 -0
- package/dist/src/aic/emit-result.js +39 -0
- package/dist/src/aic/emit-session.d.ts +26 -0
- package/dist/src/aic/emit-session.js +132 -0
- package/dist/src/aic/emit-subject.d.ts +40 -0
- package/dist/src/aic/emit-subject.js +210 -0
- package/dist/src/aic/file-lease.d.ts +35 -0
- package/dist/src/aic/file-lease.js +543 -0
- package/dist/src/aic/http.d.ts +21 -0
- package/dist/src/aic/http.js +127 -0
- package/dist/src/aic/idm.d.ts +10 -0
- package/dist/src/aic/idm.js +112 -0
- package/dist/src/aic/index.d.ts +35 -0
- package/dist/src/aic/index.js +21 -0
- package/dist/src/aic/js.d.ts +8 -0
- package/dist/src/aic/js.js +11 -0
- package/dist/src/aic/lease-identity.d.ts +38 -0
- package/dist/src/aic/lease-identity.js +72 -0
- package/dist/src/aic/lease-lock.d.ts +45 -0
- package/dist/src/aic/lease-lock.js +173 -0
- package/dist/src/aic/managed.d.ts +28 -0
- package/dist/src/aic/managed.js +199 -0
- package/dist/src/aic/provider.d.ts +156 -0
- package/dist/src/aic/provider.js +416 -0
- package/dist/src/aic/record.d.ts +28 -0
- package/dist/src/aic/record.js +183 -0
- package/dist/src/aic/resource-snapshot.d.ts +12 -0
- package/dist/src/aic/resource-snapshot.js +116 -0
- package/dist/src/aic/run.d.ts +102 -0
- package/dist/src/aic/run.js +450 -0
- package/dist/src/aic/tenant.d.ts +107 -0
- package/dist/src/aic/tenant.js +307 -0
- package/dist/src/aic/trace.d.ts +25 -0
- package/dist/src/aic/trace.js +56 -0
- package/dist/src/aic/txid.d.ts +11 -0
- package/dist/src/aic/txid.js +25 -0
- package/dist/src/aic/unsupported.d.ts +15 -0
- package/dist/src/aic/unsupported.js +60 -0
- package/dist/src/bindings/allowlist.d.ts +42 -0
- package/dist/src/bindings/allowlist.js +62 -0
- package/dist/src/bindings/harvest.d.ts +7 -0
- package/dist/src/bindings/harvest.js +137 -0
- package/dist/src/bindings/index.d.ts +4 -0
- package/dist/src/bindings/index.js +3 -0
- package/dist/src/bindings/preamble.d.ts +23 -0
- package/dist/src/bindings/preamble.js +36 -0
- package/dist/src/bindings/run.d.ts +33 -0
- package/dist/src/bindings/run.js +55 -0
- package/dist/src/case/equal.d.ts +2 -0
- package/dist/src/case/equal.js +40 -0
- package/dist/src/case/index.d.ts +5 -0
- package/dist/src/case/index.js +4 -0
- package/dist/src/case/portable.d.ts +10 -0
- package/dist/src/case/portable.js +28 -0
- package/dist/src/case/session.d.ts +34 -0
- package/dist/src/case/session.js +66 -0
- package/dist/src/case/state.d.ts +4 -0
- package/dist/src/case/state.js +47 -0
- package/dist/src/case/types.d.ts +266 -0
- package/dist/src/case/types.js +106 -0
- package/dist/src/case/util.d.ts +7 -0
- package/dist/src/case/util.js +115 -0
- package/dist/src/case/validate.d.ts +8 -0
- package/dist/src/case/validate.js +463 -0
- package/dist/src/case/verdict.d.ts +6 -0
- package/dist/src/case/verdict.js +810 -0
- package/dist/src/diagnostics.d.ts +7 -0
- package/dist/src/diagnostics.js +5 -0
- package/dist/src/emit-dts.d.ts +2 -0
- package/dist/src/emit-dts.js +82 -0
- package/dist/src/emit-js.d.ts +3 -0
- package/dist/src/emit-js.js +149 -0
- package/dist/src/harness/failures.d.ts +26 -0
- package/dist/src/harness/failures.js +113 -0
- package/dist/src/harness/idm.d.ts +12 -0
- package/dist/src/harness/idm.js +44 -0
- package/dist/src/harness/index.d.ts +11 -0
- package/dist/src/harness/index.js +6 -0
- package/dist/src/harness/lease.d.ts +136 -0
- package/dist/src/harness/lease.js +286 -0
- package/dist/src/harness/residue.d.ts +23 -0
- package/dist/src/harness/residue.js +73 -0
- package/dist/src/harness/show-log.d.ts +43 -0
- package/dist/src/harness/show-log.js +253 -0
- package/dist/src/harness/spec.d.ts +67 -0
- package/dist/src/harness/spec.js +189 -0
- package/dist/src/harness/step.d.ts +80 -0
- package/dist/src/harness/step.js +97 -0
- package/dist/src/harness/types.d.ts +100 -0
- package/dist/src/harness/types.js +1 -0
- package/dist/src/harness/vitest.d.ts +52 -0
- package/dist/src/harness/vitest.js +155 -0
- package/dist/src/idents.d.ts +4 -0
- package/dist/src/idents.js +42 -0
- package/dist/src/jvm.d.ts +124 -0
- package/dist/src/jvm.js +432 -0
- package/dist/src/load.d.ts +2 -0
- package/dist/src/load.js +104 -0
- package/dist/src/paths.d.ts +36 -0
- package/dist/src/paths.js +50 -0
- package/dist/src/profile/index.d.ts +5 -0
- package/dist/src/profile/index.js +3 -0
- package/dist/src/profile/normalise.d.ts +16 -0
- package/dist/src/profile/normalise.js +106 -0
- package/dist/src/profile/pull.d.ts +22 -0
- package/dist/src/profile/pull.js +36 -0
- package/dist/src/profile/seed.d.ts +11 -0
- package/dist/src/profile/seed.js +28 -0
- package/dist/src/profile/store.d.ts +13 -0
- package/dist/src/profile/store.js +60 -0
- package/dist/src/profile/types.d.ts +45 -0
- package/dist/src/profile/types.js +1 -0
- package/dist/src/profile/validate.d.ts +38 -0
- package/dist/src/profile/validate.js +129 -0
- package/dist/src/project.d.ts +37 -0
- package/dist/src/project.js +93 -0
- package/dist/src/protocol.d.ts +62 -0
- package/dist/src/protocol.js +68 -0
- package/dist/src/provider-module.d.ts +11 -0
- package/dist/src/provider-module.js +46 -0
- package/dist/src/runner.d.ts +39 -0
- package/dist/src/runner.js +337 -0
- package/dist/src/schema.d.ts +48 -0
- package/dist/src/schema.js +93 -0
- package/generated/scripted-decision-mocks.cjs +722 -0
- package/generated/scripted-decision-mocks.d.ts +626 -0
- package/generated/scripted-decision-next.json +3640 -0
- package/java/AmClassShutter.java +98 -0
- package/java/AmContextFactory.java +69 -0
- package/java/AmScopes.java +49 -0
- package/java/HostOps.java +180 -0
- package/java/JsValues.java +39 -0
- package/java/Json.java +441 -0
- package/java/LanguageVersions.java +55 -0
- package/java/Runner.java +289 -0
- package/java/ScriptContextScope.java +147 -0
- package/package.json +88 -0
- package/src/bindings/rhino/runtime.cjs +3021 -0
package/dist/src/jvm.js
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { delimiter, join } from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import { javaSourceDir, prebuiltClassesDir } from "./paths.js";
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
export const JVM_LANE_ENV = "AIC_SCRIPT_TESTER_JVM";
|
|
10
|
+
export function laneFromEnv(env = process.env) {
|
|
11
|
+
const raw = env[JVM_LANE_ENV];
|
|
12
|
+
if (raw === undefined || raw === "" || raw === "host") {
|
|
13
|
+
return "host";
|
|
14
|
+
}
|
|
15
|
+
if (raw === "container" || raw === "both") {
|
|
16
|
+
return raw;
|
|
17
|
+
}
|
|
18
|
+
throw new Error(`rhino-local: ${JVM_LANE_ENV} must be host, container or both, not ${JSON.stringify(raw)}`);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The Rhino AM 8.1.1 ships. MEASURED 2026-09-28: the jar in
|
|
22
|
+
* `WEB-INF/lib` of the AM image is byte-identical to this Maven Central
|
|
23
|
+
* artefact (same SHA-1 as the `.sha1` Central publishes), so there is no Ping
|
|
24
|
+
* code to extract and nothing about Rhino that needs the image.
|
|
25
|
+
*/
|
|
26
|
+
export const RHINO_JAR = {
|
|
27
|
+
fileName: "rhino-1.7.14.1.jar",
|
|
28
|
+
url: "https://repo1.maven.org/maven2/org/mozilla/rhino/1.7.14.1/rhino-1.7.14.1.jar",
|
|
29
|
+
sha256: "407d4af6521494061d86b208e99804e0bf5c2fa38449c48f24b2f5a7154b4d8d",
|
|
30
|
+
bytes: 1_389_188,
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The AM image the `container` lane runs, pinned by digest so the reference
|
|
34
|
+
* cannot move under a re-pushed tag; override to calibrate another image.
|
|
35
|
+
*/
|
|
36
|
+
export const DEFAULT_AM_IMAGE = "us-docker.pkg.dev/forgeops-public/images/am:2026.3.1-2053" +
|
|
37
|
+
"@sha256:358d7e1e13b27619b742a759fd5a85d4fcc57cc75811027b9f3c0019a0bd9be3";
|
|
38
|
+
/**
|
|
39
|
+
* The JVM AM runs, as the AM image reports it (MEASURED 2026-09-28):
|
|
40
|
+
* Temurin-25.0.4+7, `/etc/localtime` → `Etc/UTC`, `LANG=en_US.UTF-8`. The
|
|
41
|
+
* runner reports what it actually got at startup, and {@link checkEnvironment}
|
|
42
|
+
* refuses a JVM that differs in anything a script could observe.
|
|
43
|
+
*/
|
|
44
|
+
export const EXPECTED_ENVIRONMENT = {
|
|
45
|
+
javaFeature: 25,
|
|
46
|
+
timezone: "Etc/UTC",
|
|
47
|
+
locale: "en-US",
|
|
48
|
+
charset: "UTF-8",
|
|
49
|
+
rhino: "Rhino 1.7.14.1",
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Flags for the host lane only. The properties pin what the AM image's JVM
|
|
53
|
+
* gets from its OS (UTC, en_US.UTF-8), which a host would otherwise take from
|
|
54
|
+
* its own settings. The heap cap, serial collector and C1-only JIT are what
|
|
55
|
+
* keep a runner small on an 8GB box.
|
|
56
|
+
*
|
|
57
|
+
* The container lane gets **none** of these: it is the reference, so it runs
|
|
58
|
+
* the image's JVM on the image's defaults, as the old launcher did. That makes
|
|
59
|
+
* `both` compare the constrained host against an unconstrained AM JVM, so a
|
|
60
|
+
* script that only works with more heap or a faster JIT shows up as a
|
|
61
|
+
* divergence rather than agreeing with itself.
|
|
62
|
+
*/
|
|
63
|
+
export const HOST_JVM_FLAGS = [
|
|
64
|
+
"-Duser.timezone=Etc/UTC",
|
|
65
|
+
"-Duser.language=en",
|
|
66
|
+
"-Duser.country=US",
|
|
67
|
+
"-Dfile.encoding=UTF-8",
|
|
68
|
+
"-Dstdout.encoding=UTF-8",
|
|
69
|
+
"-Dstderr.encoding=UTF-8",
|
|
70
|
+
"-Xmx256m",
|
|
71
|
+
"-XX:+UseSerialGC",
|
|
72
|
+
"-XX:TieredStopAtLevel=1",
|
|
73
|
+
];
|
|
74
|
+
export function parseEnvironment(readyLine) {
|
|
75
|
+
const json = readyLine.replace(/^.*?rhino-local-runner ready\s*/, "");
|
|
76
|
+
let raw;
|
|
77
|
+
try {
|
|
78
|
+
raw = JSON.parse(json);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
throw new Error(`rhino-local: the runner's ready line carries no environment (${JSON.stringify(readyLine)}); ` +
|
|
82
|
+
"its classes predate this client — delete the rhino-local cache");
|
|
83
|
+
}
|
|
84
|
+
if (typeof raw !== "object" || raw === null) {
|
|
85
|
+
throw new Error(`rhino-local: runner environment is not an object: ${json}`);
|
|
86
|
+
}
|
|
87
|
+
const r = raw;
|
|
88
|
+
const str = (key) => {
|
|
89
|
+
const value = r[key];
|
|
90
|
+
if (typeof value !== "string") {
|
|
91
|
+
throw new Error(`rhino-local: runner environment is missing ${key}: ${json}`);
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
};
|
|
95
|
+
if (typeof r.javaFeature !== "number") {
|
|
96
|
+
throw new Error(`rhino-local: runner environment is missing javaFeature: ${json}`);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
javaFeature: r.javaFeature,
|
|
100
|
+
javaVersion: str("javaVersion"),
|
|
101
|
+
javaVendorVersion: typeof r.javaVendorVersion === "string" ? r.javaVendorVersion : null,
|
|
102
|
+
timezone: str("timezone"),
|
|
103
|
+
locale: str("locale"),
|
|
104
|
+
charset: str("charset"),
|
|
105
|
+
rhino: str("rhino"),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Every field a script can observe must match AM's. The Java patch level and
|
|
110
|
+
* vendor are not checked here: they are what the `both` lane measures, and a
|
|
111
|
+
* developer's Temurin 25.0.4.1 is not wrong merely for not being 25.0.4.
|
|
112
|
+
*/
|
|
113
|
+
export function checkEnvironment(env, lane) {
|
|
114
|
+
const problems = [];
|
|
115
|
+
for (const key of Object.keys(EXPECTED_ENVIRONMENT)) {
|
|
116
|
+
if (env[key] !== EXPECTED_ENVIRONMENT[key]) {
|
|
117
|
+
problems.push(`${key} is ${JSON.stringify(env[key])}, AM's is ${JSON.stringify(EXPECTED_ENVIRONMENT[key])}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (problems.length > 0) {
|
|
121
|
+
const remedy = lane === "host" && env.javaFeature !== EXPECTED_ENVIRONMENT.javaFeature
|
|
122
|
+
? ` Point ${JAVA_HOME_ENV} (or JAVA_HOME) at a Java ${EXPECTED_ENVIRONMENT.javaFeature} — AM runs Temurin ${EXPECTED_ENVIRONMENT.javaFeature}.`
|
|
123
|
+
: "";
|
|
124
|
+
throw new Error(`rhino-local: the ${lane} JVM does not match AM: ${problems.join("; ")}.${remedy}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export const JAVA_HOME_ENV = "AIC_SCRIPT_TESTER_JAVA_HOME";
|
|
128
|
+
/** `bin/<tool>` under AIC_SCRIPT_TESTER_JAVA_HOME, then JAVA_HOME, else the bare name for PATH lookup. */
|
|
129
|
+
export function javaTool(tool, env = process.env) {
|
|
130
|
+
const home = env[JAVA_HOME_ENV] || env.JAVA_HOME;
|
|
131
|
+
return home ? join(home, "bin", tool) : tool;
|
|
132
|
+
}
|
|
133
|
+
export function cacheDir(env = process.env) {
|
|
134
|
+
if (env.AIC_SCRIPT_TESTER_CACHE) {
|
|
135
|
+
return env.AIC_SCRIPT_TESTER_CACHE;
|
|
136
|
+
}
|
|
137
|
+
return join(env.XDG_CACHE_HOME || join(homedir(), ".cache"), "aic-script-tester");
|
|
138
|
+
}
|
|
139
|
+
function sha256(bytes) {
|
|
140
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
141
|
+
}
|
|
142
|
+
const javacVersions = new Map();
|
|
143
|
+
async function javacVersion(javac) {
|
|
144
|
+
let version = javacVersions.get(javac);
|
|
145
|
+
if (!version) {
|
|
146
|
+
version = execFileAsync(javac, ["-version"], { timeout: 30_000, maxBuffer: 100_000 })
|
|
147
|
+
.then(({ stdout, stderr }) => `${stdout}${stderr}`.trim())
|
|
148
|
+
.catch((error) => {
|
|
149
|
+
javacVersions.delete(javac);
|
|
150
|
+
throw error;
|
|
151
|
+
});
|
|
152
|
+
javacVersions.set(javac, version);
|
|
153
|
+
}
|
|
154
|
+
return version;
|
|
155
|
+
}
|
|
156
|
+
function outputFiles(root, relative = "") {
|
|
157
|
+
const dir = relative ? join(root, relative) : root;
|
|
158
|
+
const files = [];
|
|
159
|
+
for (const entry of readdirSync(dir)) {
|
|
160
|
+
const child = relative ? join(relative, entry) : entry;
|
|
161
|
+
const path = join(root, child);
|
|
162
|
+
if (lstatSync(path).isDirectory())
|
|
163
|
+
files.push(...outputFiles(root, child));
|
|
164
|
+
else
|
|
165
|
+
files.push(child);
|
|
166
|
+
}
|
|
167
|
+
return files.sort();
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The first published directory for `digest` whose manifest still holds.
|
|
171
|
+
* One that does not — a class deleted, altered or added since — is removed:
|
|
172
|
+
* names are never reused, so no worker can be about to publish over it, and
|
|
173
|
+
* a worker already running from it is running damaged classes regardless.
|
|
174
|
+
*/
|
|
175
|
+
function findValidClasses(classesDir, digest) {
|
|
176
|
+
let names;
|
|
177
|
+
try {
|
|
178
|
+
names = readdirSync(classesDir);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
for (const name of names.filter((n) => n.startsWith(`${digest}-`)).sort()) {
|
|
184
|
+
const dir = join(classesDir, name);
|
|
185
|
+
if (validRunnerCache(dir, digest)) {
|
|
186
|
+
return dir;
|
|
187
|
+
}
|
|
188
|
+
rmSync(dir, { recursive: true, force: true });
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
function validRunnerCache(dest, digest) {
|
|
193
|
+
const marker = join(dest, ".complete");
|
|
194
|
+
if (!existsSync(marker))
|
|
195
|
+
return false;
|
|
196
|
+
try {
|
|
197
|
+
const lines = readFileSync(marker, "utf8").trimEnd().split("\n");
|
|
198
|
+
if (lines[0] !== digest || lines.length < 2)
|
|
199
|
+
return false;
|
|
200
|
+
const entries = lines.slice(1);
|
|
201
|
+
const listed = new Set();
|
|
202
|
+
let previous = "";
|
|
203
|
+
for (const line of entries) {
|
|
204
|
+
const match = /^([a-f0-9]{64})\x20{2}(.+)$/.exec(line);
|
|
205
|
+
const relative = match?.[2];
|
|
206
|
+
if (!match || !relative || relative <= previous || relative === ".complete")
|
|
207
|
+
return false;
|
|
208
|
+
previous = relative;
|
|
209
|
+
listed.add(relative);
|
|
210
|
+
if (sha256(readFileSync(join(dest, relative))) !== match[1])
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
return outputFiles(dest)
|
|
214
|
+
.filter((file) => file.endsWith(".class"))
|
|
215
|
+
.every((file) => listed.has(file));
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** A local copy of {@link RHINO_JAR} to use instead of the cache or a download. */
|
|
222
|
+
export const RHINO_JAR_ENV = "AIC_SCRIPT_TESTER_RHINO_JAR";
|
|
223
|
+
async function defaultFetch(url) {
|
|
224
|
+
const response = await fetch(url);
|
|
225
|
+
if (!response.ok) {
|
|
226
|
+
throw new Error(`rhino-local: GET ${url} returned ${response.status}`);
|
|
227
|
+
}
|
|
228
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* The Rhino jar, verified by SHA-256 every time it is used, not only when
|
|
232
|
+
* downloaded: a cache is a file anyone can overwrite, and a different Rhino
|
|
233
|
+
* would make every verdict in this harness a claim about the wrong engine.
|
|
234
|
+
*/
|
|
235
|
+
export async function ensureRhinoJar(cache = cacheDir(), fetcher = defaultFetch, env = process.env) {
|
|
236
|
+
const override = env[RHINO_JAR_ENV];
|
|
237
|
+
if (override !== undefined && override !== "") {
|
|
238
|
+
// For machines that cannot reach Maven Central. Checked like a download:
|
|
239
|
+
// a path is not a promise about what is at it.
|
|
240
|
+
let actual;
|
|
241
|
+
try {
|
|
242
|
+
actual = sha256(readFileSync(override));
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
246
|
+
throw new Error(`rhino-local: ${RHINO_JAR_ENV}=${override} cannot be read: ${detail}`, {
|
|
247
|
+
cause: error,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (actual !== RHINO_JAR.sha256) {
|
|
251
|
+
throw new Error(`rhino-local: ${RHINO_JAR_ENV}=${override} is not the Rhino AM ships ` +
|
|
252
|
+
`(sha256 ${actual}; expected ${RHINO_JAR.sha256}, ${RHINO_JAR.fileName} from ${RHINO_JAR.url})`);
|
|
253
|
+
}
|
|
254
|
+
return override;
|
|
255
|
+
}
|
|
256
|
+
const dir = join(cache, "jars");
|
|
257
|
+
const path = join(dir, RHINO_JAR.fileName);
|
|
258
|
+
if (existsSync(path)) {
|
|
259
|
+
const actual = sha256(readFileSync(path));
|
|
260
|
+
if (actual === RHINO_JAR.sha256) {
|
|
261
|
+
return path;
|
|
262
|
+
}
|
|
263
|
+
rmSync(path, { force: true });
|
|
264
|
+
}
|
|
265
|
+
const bytes = await fetcher(RHINO_JAR.url);
|
|
266
|
+
const actual = sha256(bytes);
|
|
267
|
+
if (actual !== RHINO_JAR.sha256 || bytes.length !== RHINO_JAR.bytes) {
|
|
268
|
+
throw new Error(`rhino-local: ${RHINO_JAR.url} is not the Rhino AM ships ` +
|
|
269
|
+
`(sha256 ${actual}, ${bytes.length} bytes; expected ${RHINO_JAR.sha256}, ${RHINO_JAR.bytes})`);
|
|
270
|
+
}
|
|
271
|
+
mkdirSync(dir, { recursive: true });
|
|
272
|
+
const tmp = join(dir, `.${RHINO_JAR.fileName}.${randomUUID()}`);
|
|
273
|
+
writeFileSync(tmp, bytes);
|
|
274
|
+
renameSync(tmp, path);
|
|
275
|
+
return path;
|
|
276
|
+
}
|
|
277
|
+
function runnerSources(dir) {
|
|
278
|
+
const names = readdirSync(dir)
|
|
279
|
+
.filter((name) => name.endsWith(".java"))
|
|
280
|
+
.sort();
|
|
281
|
+
if (names.length === 0) {
|
|
282
|
+
throw new Error(`rhino-local: no Java sources in ${dir}`);
|
|
283
|
+
}
|
|
284
|
+
const key = createHash("sha256");
|
|
285
|
+
key.update(RHINO_JAR.sha256);
|
|
286
|
+
for (const name of names) {
|
|
287
|
+
key.update(`\0${name}\0`);
|
|
288
|
+
key.update(readFileSync(join(dir, name)));
|
|
289
|
+
}
|
|
290
|
+
return { dir, names, sourceDigest: key.digest("hex").slice(0, 16) };
|
|
291
|
+
}
|
|
292
|
+
async function compileRunner(javac, jar, sources, out, prebuilt) {
|
|
293
|
+
try {
|
|
294
|
+
await execFileAsync(javac, [
|
|
295
|
+
"-encoding",
|
|
296
|
+
"UTF-8",
|
|
297
|
+
"--release",
|
|
298
|
+
String(EXPECTED_ENVIRONMENT.javaFeature),
|
|
299
|
+
"-cp",
|
|
300
|
+
jar,
|
|
301
|
+
"-d",
|
|
302
|
+
out,
|
|
303
|
+
...sources.names.map((name) => join(sources.dir, name)),
|
|
304
|
+
], { timeout: 120_000, maxBuffer: 4_000_000 });
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
throw new Error(noClassesMessage(javac, prebuilt, error), { cause: error });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function noClassesMessage(javac, prebuilt, error) {
|
|
311
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
312
|
+
return (`rhino-local: compiling the runner with ${javac} failed. There are no prebuilt classes ` +
|
|
313
|
+
`for these sources at ${prebuilt}, so it needs a JDK ${EXPECTED_ENVIRONMENT.javaFeature} ` +
|
|
314
|
+
`(set ${JAVA_HOME_ENV} or JAVA_HOME): ${detail}`);
|
|
315
|
+
}
|
|
316
|
+
function writeManifest(dir, digest) {
|
|
317
|
+
const manifest = outputFiles(dir).map((file) => `${sha256(readFileSync(join(dir, file)))} ${file}`);
|
|
318
|
+
writeFileSync(join(dir, ".complete"), `${digest}\n${manifest.join("\n")}\n`);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* The runner classes to launch. First the package's prebuilt classes, when
|
|
322
|
+
* their manifest names these exact sources and every class still hashes as
|
|
323
|
+
* recorded — an installed package, which may have no `javac` at all. Otherwise
|
|
324
|
+
* classes compiled here, keyed by the Java sources, the compiler and the jar
|
|
325
|
+
* they compile against, so an edit to a `.java` file cannot run stale
|
|
326
|
+
* bytecode. Each compile goes to a scratch directory and is renamed to a
|
|
327
|
+
* **fresh** `<digest>-<uuid>` name, never to a shared one: Vitest spawns
|
|
328
|
+
* runners from several workers at once, and a name that is never reused
|
|
329
|
+
* cannot be replaced or moved after another worker has validated and returned
|
|
330
|
+
* it. Two cold workers may both publish; the copies are identical and the
|
|
331
|
+
* lowest valid name wins from then on.
|
|
332
|
+
*/
|
|
333
|
+
export async function ensureRunnerClasses(jar, options = {}) {
|
|
334
|
+
const cache = options.cache ?? cacheDir();
|
|
335
|
+
const sources = runnerSources(options.sources ?? javaSourceDir);
|
|
336
|
+
const prebuilt = options.prebuilt ?? prebuiltClassesDir;
|
|
337
|
+
if (validRunnerCache(prebuilt, sources.sourceDigest)) {
|
|
338
|
+
return prebuilt;
|
|
339
|
+
}
|
|
340
|
+
const javac = options.javac ?? javaTool("javac");
|
|
341
|
+
let compiler;
|
|
342
|
+
try {
|
|
343
|
+
compiler = await javacVersion(javac);
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
throw new Error(noClassesMessage(javac, prebuilt, error), { cause: error });
|
|
347
|
+
}
|
|
348
|
+
const digest = createHash("sha256")
|
|
349
|
+
.update(`${sources.sourceDigest}\0javac\0${compiler}`)
|
|
350
|
+
.digest("hex")
|
|
351
|
+
.slice(0, 16);
|
|
352
|
+
const classesDir = join(cache, "classes");
|
|
353
|
+
const found = findValidClasses(classesDir, digest);
|
|
354
|
+
if (found !== undefined) {
|
|
355
|
+
return found;
|
|
356
|
+
}
|
|
357
|
+
const scratch = join(classesDir, `.${digest}.${randomUUID()}`);
|
|
358
|
+
mkdirSync(scratch, { recursive: true });
|
|
359
|
+
try {
|
|
360
|
+
await compileRunner(javac, jar, sources, scratch, prebuilt);
|
|
361
|
+
writeManifest(scratch, digest);
|
|
362
|
+
const dest = join(classesDir, `${digest}-${randomUUID()}`);
|
|
363
|
+
renameSync(scratch, dest);
|
|
364
|
+
return dest;
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Compile the classes a package ships (`npm run build`). Their manifest is
|
|
372
|
+
* keyed by the sources alone, not the compiler: the consumer running them has
|
|
373
|
+
* no `javac` to key by, and `--release` fixes the class-file target.
|
|
374
|
+
*/
|
|
375
|
+
export async function buildPrebuiltClasses(options = {}) {
|
|
376
|
+
const out = options.out ?? prebuiltClassesDir;
|
|
377
|
+
const jar = await ensureRhinoJar(options.cache ?? cacheDir());
|
|
378
|
+
const sources = runnerSources(options.sources ?? javaSourceDir);
|
|
379
|
+
const scratch = `${out}.${randomUUID()}`;
|
|
380
|
+
mkdirSync(scratch, { recursive: true });
|
|
381
|
+
try {
|
|
382
|
+
await compileRunner(options.javac ?? javaTool("javac"), jar, sources, scratch, out);
|
|
383
|
+
writeManifest(scratch, sources.sourceDigest);
|
|
384
|
+
rmSync(out, { recursive: true, force: true });
|
|
385
|
+
renameSync(scratch, out);
|
|
386
|
+
return out;
|
|
387
|
+
}
|
|
388
|
+
finally {
|
|
389
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
export function hostLaunch(classes, jar, env = process.env) {
|
|
393
|
+
return {
|
|
394
|
+
command: javaTool("java", env),
|
|
395
|
+
args: [...HOST_JVM_FLAGS, "-cp", `${classes}${delimiter}${jar}`, "Runner"],
|
|
396
|
+
// TZ as well as -Duser.timezone: the property wins for java.util, but a
|
|
397
|
+
// host TZ still leaks into anything that asks the OS.
|
|
398
|
+
env: { ...env, TZ: "Etc/UTC" },
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
export function containerLaunch(classes, jar, env = process.env) {
|
|
402
|
+
const containerName = `rhino-local-runner-${randomUUID()}`;
|
|
403
|
+
const image = env.AIC_SCRIPT_TESTER_IMAGE || DEFAULT_AM_IMAGE;
|
|
404
|
+
const user = typeof process.getuid === "function"
|
|
405
|
+
? [`--user`, `${process.getuid()}:${process.getgid?.() ?? 0}`]
|
|
406
|
+
: [];
|
|
407
|
+
return {
|
|
408
|
+
command: "docker",
|
|
409
|
+
args: [
|
|
410
|
+
"run",
|
|
411
|
+
"--rm",
|
|
412
|
+
"-i",
|
|
413
|
+
"--network",
|
|
414
|
+
"none",
|
|
415
|
+
"--name",
|
|
416
|
+
containerName,
|
|
417
|
+
...user,
|
|
418
|
+
"-v",
|
|
419
|
+
`${classes}:/rhino-local/classes:ro`,
|
|
420
|
+
"-v",
|
|
421
|
+
`${jar}:/rhino-local/rhino.jar:ro`,
|
|
422
|
+
"--entrypoint",
|
|
423
|
+
"/opt/java/openjdk/bin/java",
|
|
424
|
+
image,
|
|
425
|
+
"-cp",
|
|
426
|
+
"/rhino-local/classes:/rhino-local/rhino.jar",
|
|
427
|
+
"Runner",
|
|
428
|
+
],
|
|
429
|
+
env,
|
|
430
|
+
containerName,
|
|
431
|
+
};
|
|
432
|
+
}
|
package/dist/src/load.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { JS_TYPES } from "./schema.js";
|
|
3
|
+
const JS_TYPE_SET = new Set(JS_TYPES);
|
|
4
|
+
export function loadContext(path) {
|
|
5
|
+
let parsed;
|
|
6
|
+
try {
|
|
7
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
throw new Error(`rhino-local: cannot parse ${path}: ${String(error)}`, { cause: error });
|
|
11
|
+
}
|
|
12
|
+
return parseDocument(parsed, path);
|
|
13
|
+
}
|
|
14
|
+
function parseDocument(raw, path) {
|
|
15
|
+
if (!isRecord(raw)) {
|
|
16
|
+
throw new Error(`rhino-local: ${path} is not an object`);
|
|
17
|
+
}
|
|
18
|
+
if (typeof raw._id !== "string" || raw._id.length === 0) {
|
|
19
|
+
throw new Error(`rhino-local: ${path} is missing _id`);
|
|
20
|
+
}
|
|
21
|
+
if (!Array.isArray(raw.bindings)) {
|
|
22
|
+
throw new Error(`rhino-local: ${path} is missing bindings[]`);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
_id: raw._id,
|
|
26
|
+
bindings: raw.bindings.map((binding, index) => parseBinding(binding, `${path} bindings[${index}]`)),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function parseBinding(raw, path) {
|
|
30
|
+
if (!isRecord(raw)) {
|
|
31
|
+
throw new Error(`rhino-local: ${path} is not an object`);
|
|
32
|
+
}
|
|
33
|
+
if (typeof raw.name !== "string") {
|
|
34
|
+
throw new Error(`rhino-local: ${path} is missing name`);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
name: raw.name,
|
|
38
|
+
javaScriptType: parseJsType(raw.javaScriptType, `${path}.javaScriptType`),
|
|
39
|
+
elements: parseElements(raw.elements, `${path}.elements`),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function parseElements(raw, path) {
|
|
43
|
+
if (raw === undefined || raw === null) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
if (!Array.isArray(raw)) {
|
|
47
|
+
throw new Error(`rhino-local: ${path} is not an array`);
|
|
48
|
+
}
|
|
49
|
+
return raw.map((element, index) => parseElement(element, `${path}[${index}]`));
|
|
50
|
+
}
|
|
51
|
+
function parseElement(raw, path) {
|
|
52
|
+
if (!isRecord(raw)) {
|
|
53
|
+
throw new Error(`rhino-local: ${path} is not an object`);
|
|
54
|
+
}
|
|
55
|
+
if (typeof raw.name !== "string") {
|
|
56
|
+
throw new Error(`rhino-local: ${path} is missing name`);
|
|
57
|
+
}
|
|
58
|
+
if (raw.elementType === "method") {
|
|
59
|
+
return {
|
|
60
|
+
elementType: "method",
|
|
61
|
+
name: raw.name,
|
|
62
|
+
parameters: parseParameters(raw.parameters, `${path}.parameters`),
|
|
63
|
+
returnType: parseJsType(raw.returnType, `${path}.returnType`),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (raw.elementType === "field") {
|
|
67
|
+
return {
|
|
68
|
+
elementType: "field",
|
|
69
|
+
name: raw.name,
|
|
70
|
+
javaScriptType: parseJsType(raw.javaScriptType, `${path}.javaScriptType`),
|
|
71
|
+
elements: parseElements(raw.elements, `${path}.elements`),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
throw new Error(`rhino-local: ${path} has unknown elementType ${JSON.stringify(raw.elementType)}`);
|
|
75
|
+
}
|
|
76
|
+
function parseParameters(raw, path) {
|
|
77
|
+
if (raw === undefined || raw === null) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
if (!Array.isArray(raw)) {
|
|
81
|
+
throw new Error(`rhino-local: ${path} is not an array`);
|
|
82
|
+
}
|
|
83
|
+
return raw.map((parameter, index) => {
|
|
84
|
+
if (!isRecord(parameter)) {
|
|
85
|
+
throw new Error(`rhino-local: ${path}[${index}] is not an object`);
|
|
86
|
+
}
|
|
87
|
+
if (typeof parameter.name !== "string") {
|
|
88
|
+
throw new Error(`rhino-local: ${path}[${index}] is missing name`);
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
name: parameter.name,
|
|
92
|
+
javaScriptType: parseJsType(parameter.javaScriptType, `${path}[${index}].javaScriptType`),
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function parseJsType(raw, path) {
|
|
97
|
+
if (typeof raw !== "string" || !JS_TYPE_SET.has(raw)) {
|
|
98
|
+
throw new Error(`rhino-local: ${path} unexpected javaScriptType ${JSON.stringify(raw)}`);
|
|
99
|
+
}
|
|
100
|
+
return raw;
|
|
101
|
+
}
|
|
102
|
+
function isRecord(value) {
|
|
103
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
104
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The directory holding this package's `package.json`: `packages/rhino-local/`
|
|
3
|
+
* in the repo, `node_modules/<name>/` installed. Found by walking up, because
|
|
4
|
+
* the compiled copy of this file sits one level deeper (`dist/src/`) than the
|
|
5
|
+
* source does.
|
|
6
|
+
*/
|
|
7
|
+
export declare const packageRoot: string;
|
|
8
|
+
/**
|
|
9
|
+
* pingone-aic-manager repo root. **Repo-only**: generation and this repo's
|
|
10
|
+
* tests read through it; nothing under `src/` may use it at run time,
|
|
11
|
+
* because an installed package has no repo around it
|
|
12
|
+
* (`test/paths.test.ts` enforces that).
|
|
13
|
+
*/
|
|
14
|
+
export declare const repoRoot: string;
|
|
15
|
+
/** The runner's Java sources (`packages/rhino-local/java/*.java`). */
|
|
16
|
+
export declare const javaSourceDir: string;
|
|
17
|
+
/**
|
|
18
|
+
* Runner classes compiled at build time and shipped, so a consumer needs a
|
|
19
|
+
* `java` but no `javac`. Used only while their manifest matches
|
|
20
|
+
* {@link javaSourceDir}; absent in a checkout until `npm run build`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const prebuiltClassesDir: string;
|
|
23
|
+
/** The captured contexts JSON in this repo's docs. Repo-only; read by `generate`. */
|
|
24
|
+
export declare const sourceBindingsJsonPath: string;
|
|
25
|
+
export declare const generatedDir: string;
|
|
26
|
+
/** `generate`'s verbatim copy of {@link sourceBindingsJsonPath}, shipped with the package. */
|
|
27
|
+
export declare const bindingsJsonPath: string;
|
|
28
|
+
export declare const generatedJsPath: string;
|
|
29
|
+
export declare const generatedDtsPath: string;
|
|
30
|
+
/** Handwritten AM-safe overlay that implements a subset of the generated stubs. */
|
|
31
|
+
export declare const bindingsRuntimePath: string;
|
|
32
|
+
/** Scripted-decision case files (author scripts + defineCase wrappers). */
|
|
33
|
+
export declare const casesDir: string;
|
|
34
|
+
export declare const amRhinoEslintConfigPath: string;
|
|
35
|
+
/** This repo's AM script lint. Repo-only: the lockstep test reads it. */
|
|
36
|
+
export declare const amEslintConfigPath: string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
/**
|
|
6
|
+
* The directory holding this package's `package.json`: `packages/rhino-local/`
|
|
7
|
+
* in the repo, `node_modules/<name>/` installed. Found by walking up, because
|
|
8
|
+
* the compiled copy of this file sits one level deeper (`dist/src/`) than the
|
|
9
|
+
* source does.
|
|
10
|
+
*/
|
|
11
|
+
export const packageRoot = findPackageRoot(here);
|
|
12
|
+
function findPackageRoot(start) {
|
|
13
|
+
for (let dir = start;; dir = dirname(dir)) {
|
|
14
|
+
if (existsSync(join(dir, "package.json"))) {
|
|
15
|
+
return dir;
|
|
16
|
+
}
|
|
17
|
+
if (dirname(dir) === dir) {
|
|
18
|
+
throw new Error(`rhino-local: no package.json above ${start}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* pingone-aic-manager repo root. **Repo-only**: generation and this repo's
|
|
24
|
+
* tests read through it; nothing under `src/` may use it at run time,
|
|
25
|
+
* because an installed package has no repo around it
|
|
26
|
+
* (`test/paths.test.ts` enforces that).
|
|
27
|
+
*/
|
|
28
|
+
export const repoRoot = join(packageRoot, "..", "..");
|
|
29
|
+
/** The runner's Java sources (`packages/rhino-local/java/*.java`). */
|
|
30
|
+
export const javaSourceDir = join(packageRoot, "java");
|
|
31
|
+
/**
|
|
32
|
+
* Runner classes compiled at build time and shipped, so a consumer needs a
|
|
33
|
+
* `java` but no `javac`. Used only while their manifest matches
|
|
34
|
+
* {@link javaSourceDir}; absent in a checkout until `npm run build`.
|
|
35
|
+
*/
|
|
36
|
+
export const prebuiltClassesDir = join(packageRoot, "dist", "classes");
|
|
37
|
+
/** The captured contexts JSON in this repo's docs. Repo-only; read by `generate`. */
|
|
38
|
+
export const sourceBindingsJsonPath = join(repoRoot, "docs", "api", "bindings", "scripted-decision-next.json");
|
|
39
|
+
export const generatedDir = join(packageRoot, "generated");
|
|
40
|
+
/** `generate`'s verbatim copy of {@link sourceBindingsJsonPath}, shipped with the package. */
|
|
41
|
+
export const bindingsJsonPath = join(generatedDir, "scripted-decision-next.json");
|
|
42
|
+
export const generatedJsPath = join(generatedDir, "scripted-decision-mocks.cjs");
|
|
43
|
+
export const generatedDtsPath = join(generatedDir, "scripted-decision-mocks.d.ts");
|
|
44
|
+
/** Handwritten AM-safe overlay that implements a subset of the generated stubs. */
|
|
45
|
+
export const bindingsRuntimePath = join(packageRoot, "src", "bindings", "rhino", "runtime.cjs");
|
|
46
|
+
/** Scripted-decision case files (author scripts + defineCase wrappers). */
|
|
47
|
+
export const casesDir = join(packageRoot, "cases");
|
|
48
|
+
export const amRhinoEslintConfigPath = join(packageRoot, "eslint.am.config.js");
|
|
49
|
+
/** This repo's AM script lint. Repo-only: the lockstep test reads it. */
|
|
50
|
+
export const amEslintConfigPath = join(repoRoot, "src", "scripts", "templates", "am", "eslint.config.js");
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { MANAGED_CONFIG_ENDPOINT, pullProfile } from "./pull.ts";
|
|
2
|
+
export type { PullOptions } from "./pull.ts";
|
|
3
|
+
export { parseProfile, profilePath, readProfile, tryReadProfile, writeProfile } from "./store.ts";
|
|
4
|
+
export { ProfileShapeError } from "./normalise.ts";
|
|
5
|
+
export type { EnvProfile, ObjectSchema, PropertySchema } from "./types.ts";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EnvProfile } from "./types.ts";
|
|
2
|
+
export declare class ProfileShapeError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Normalise `GET /openidm/config/managed` into an `EnvProfile`.
|
|
7
|
+
*
|
|
8
|
+
* Pure, so the wire shape is testable without a tenant. Unknown keys are
|
|
9
|
+
* dropped rather than carried: this profile is consumed by validation, and a
|
|
10
|
+
* field nothing reads is a field that rots silently.
|
|
11
|
+
*/
|
|
12
|
+
export declare function normaliseManagedConfig(document: unknown, meta: {
|
|
13
|
+
tenant: string;
|
|
14
|
+
pulledAt: string;
|
|
15
|
+
endpoint: string;
|
|
16
|
+
}): EnvProfile;
|